diff --git a/.coveragerc b/.coveragerc index 58b3ff7bbf6..bf2e3c599bd 100644 --- a/.coveragerc +++ b/.coveragerc @@ -31,6 +31,7 @@ omit = homeassistant/components/browser.py homeassistant/components/camera/* homeassistant/components/device_tracker/actiontec.py + homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/asuswrt.py homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/luci.py diff --git a/README.md b/README.md index d6fed371b64..26bb0b998f5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Check out [the website](https://home-assistant.io) for [a demo][demo], installat Examples of devices it can interface it: * Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), and [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/) - * [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Efergy](https://efergy.com) plugs, [Edimax](http://www.edimax.com/) switches, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors + * [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), and [Kodi (XBMC)](http://kodi.tv/) * Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/) * Integrate data from the [Bitcoin](https://bitcoin.org) network, meteorological data from [OpenWeatherMap](http://openweathermap.org/) and [Forecast.io](https://forecast.io/), [Transmission](http://www.transmissionbt.com/), or [SABnzbd](http://sabnzbd.org). diff --git a/docs/architecture-remote.png b/docs/architecture-remote.png deleted file mode 100644 index 3109c921846..00000000000 Binary files a/docs/architecture-remote.png and /dev/null differ diff --git a/docs/architecture.png b/docs/architecture.png deleted file mode 100644 index 7fe62cf3144..00000000000 Binary files a/docs/architecture.png and /dev/null differ diff --git a/docs/screenshots.png b/docs/screenshots.png index 09dff77c894..a5e278b0394 100644 Binary files a/docs/screenshots.png and b/docs/screenshots.png differ diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 19e2cc2993b..d7e81eee13c 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -10,6 +10,15 @@ import homeassistant.config as config_util from homeassistant.const import __version__, EVENT_HOMEASSISTANT_START +def validate_python(): + """ Validate we're running the right Python version. """ + major, minor = sys.version_info[:2] + + if major < 3 or (major == 3 and minor < 4): + print("Home Assistant requires atleast Python 3.4") + sys.exit(1) + + def ensure_config_path(config_dir): """ Validates configuration directory. """ @@ -74,6 +83,8 @@ def get_arguments(): def main(): """ Starts Home Assistant. """ + validate_python() + args = get_arguments() config_dir = os.path.join(os.getcwd(), args.config) @@ -86,6 +97,7 @@ def main(): }, config_dir=config_dir) else: config_file = ensure_config_file(config_dir) + print('Config directory:', config_dir) hass = bootstrap.from_config_file(config_file) if args.open_ui: diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/device_tracker/aruba.py new file mode 100644 index 00000000000..8e8c6e78592 --- /dev/null +++ b/homeassistant/components/device_tracker/aruba.py @@ -0,0 +1,154 @@ +""" +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 config/configuration.yaml. You also need to enable Telnet in the +configuration pages. + +device_tracker: + platform: aruba + host: YOUR_ACCESS_POINT_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. +""" +import logging +from datetime import timedelta +import re +import threading +import telnetlib + +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.helpers import validate_config +from homeassistant.util import Throttle +from homeassistant.components.device_tracker import DOMAIN + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) + +_LOGGER = logging.getLogger(__name__) + +_DEVICES_REGEX = re.compile( + r'(?P([^\s]+))\s+' + + r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' + + r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))\s+' + + r'(?P([^\s]+))') + + +# pylint: disable=unused-argument +def get_scanner(hass, config): + """ Validates config and returns a Aruba scanner. """ + if not validate_config(config, + {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, + _LOGGER): + return None + + scanner = ArubaDeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +class ArubaDeviceScanner(object): + """ This class queries a Aruba Acces Point for connected devices. """ + def __init__(self, config): + self.host = config[CONF_HOST] + self.username = config[CONF_USERNAME] + self.password = config[CONF_PASSWORD] + + self.lock = threading.Lock() + + self.last_results = {} + + # Test the router is accessible + data = self.get_aruba_data() + self.success_init = data is not None + + def scan_devices(self): + """ Scans for new devices and return a list containing found device + ids. """ + + self._update_info() + return [client['mac'] for client in self.last_results] + + def get_device_name(self, device): + """ Returns the name of the given device or None if we don't know. """ + if not self.last_results: + return None + for client in self.last_results: + if client['mac'] == device: + return client['name'] + return None + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """ Ensures the information from the Aruba Access Point is up to date. + Returns boolean if scanning successful. """ + if not self.success_init: + return False + + with self.lock: + data = self.get_aruba_data() + if not data: + return False + + self.last_results = data.values() + return True + + def get_aruba_data(self): + """ Retrieve data from Aruba Access Point and return parsed + result. """ + try: + telnet = telnetlib.Telnet(self.host) + telnet.read_until(b'User: ') + telnet.write((self.username + '\r\n').encode('ascii')) + telnet.read_until(b'Password: ') + telnet.write((self.password + '\r\n').encode('ascii')) + telnet.read_until(b'#') + telnet.write(('show clients\r\n').encode('ascii')) + devices_result = telnet.read_until(b'#').split(b'\r\n') + telnet.write('exit\r\n'.encode('ascii')) + except EOFError: + _LOGGER.exception("Unexpected response from router") + return + except ConnectionRefusedError: + _LOGGER.exception("Connection refused by router," + + " is telnet enabled?") + return + + devices = {} + for device in devices_result: + match = _DEVICES_REGEX.search(device.decode('utf-8')) + if match: + devices[match.group('ip')] = { + 'ip': match.group('ip'), + 'mac': match.group('mac').upper(), + 'name': match.group('name') + } + return devices diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index f6ccfbe7a78..8c6b05726da 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 = "6558cad990c03dd96f9e8b1035495cb6" +VERSION = "35ecb5457a9ff0f4142c2605b53eb843" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 19d13a1f889..02d96975a0e 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -3828,11 +3828,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN } .install { display: block; - white-space: pre; line-height: 1.5em; margin-top: 8px; margin-bottom: 16px; - }