From 2dcde12d38f65b8e4117ea531526034555662e42 Mon Sep 17 00:00:00 2001 From: Ari Lotter Date: Mon, 13 Nov 2017 17:10:39 -0500 Subject: [PATCH] Support presence detection using Hitron Coda router (#9682) * Support presence detection using Hitron Coda router * at least 2 spaces before inline comment * Update hitron_coda.py * rewrote authentication code, it actually works now * make line slightly shorter to comply with hound * Removed hardcoded IP address * Fix string formatting, add timeout, and use generator * Update hitron_coda.py * Update hitron_coda.py * Update hitron_coda.py * typo * update .coveragerc * Update stale URL --- .coveragerc | 1 + .../components/device_tracker/hitron_coda.py | 138 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 homeassistant/components/device_tracker/hitron_coda.py diff --git a/.coveragerc b/.coveragerc index 390e57e2e31..4ff7fa24102 100644 --- a/.coveragerc +++ b/.coveragerc @@ -309,6 +309,7 @@ omit = homeassistant/components/device_tracker/cisco_ios.py homeassistant/components/device_tracker/fritz.py homeassistant/components/device_tracker/gpslogger.py + homeassistant/components/device_tracker/hitron_coda.py homeassistant/components/device_tracker/huawei_router.py homeassistant/components/device_tracker/icloud.py homeassistant/components/device_tracker/keenetic_ndms2.py diff --git a/homeassistant/components/device_tracker/hitron_coda.py b/homeassistant/components/device_tracker/hitron_coda.py new file mode 100644 index 00000000000..17dc34d1040 --- /dev/null +++ b/homeassistant/components/device_tracker/hitron_coda.py @@ -0,0 +1,138 @@ +""" +Support for the Hitron CODA-4582U, provided by Rogers. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.hitron_coda/ +""" +import logging +from collections import namedtuple + +import requests +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) +from homeassistant.const import ( + CONF_HOST, CONF_PASSWORD, CONF_USERNAME +) + +_LOGGER = logging.getLogger(__name__) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string +}) + + +def get_scanner(_hass, config): + """Validate the configuration and return a Nmap scanner.""" + scanner = HitronCODADeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +Device = namedtuple('Device', ['mac', 'name']) + + +class HitronCODADeviceScanner(DeviceScanner): + """This class scans for devices using the CODA's web interface.""" + + def __init__(self, config): + """Initialize the scanner.""" + self.last_results = [] + host = config[CONF_HOST] + self._url = 'http://{}/data/getConnectInfo.asp'.format(host) + self._loginurl = 'http://{}/goform/login'.format(host) + + self._username = config.get(CONF_USERNAME) + self._password = config.get(CONF_PASSWORD) + + self._userid = None + + self.success_init = self._update_info() + _LOGGER.info("Scanner initialized") + + def scan_devices(self): + """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): + """Return the name of the device with the given MAC address.""" + name = next(( + device.name for device in self.last_results + if device.mac == mac), None) + return name + + def _login(self): + """Log in to the router. This is required for subsequent api calls.""" + _LOGGER.info("Logging in to CODA...") + + try: + data = [ + ('user', self._username), + ('pws', self._password), + ] + res = requests.post(self._loginurl, data=data, timeout=10) + except requests.exceptions.Timeout: + _LOGGER.error( + "Connection to the router timed out at URL %s", self._url) + return False + if res.status_code != 200: + _LOGGER.error( + "Connection failed with http code %s", res.status_code) + return False + try: + self._userid = res.cookies['userid'] + return True + except KeyError: + _LOGGER.error("Failed to log in to router") + return False + + def _update_info(self): + """Get ARP from router.""" + _LOGGER.info("Fetching...") + + if self._userid is None: + if not self._login(): + _LOGGER.error("Could not obtain a user ID from the router") + return False + last_results = [] + + # doing a request + try: + res = requests.get(self._url, timeout=10, cookies={ + 'userid': self._userid + }) + except requests.exceptions.Timeout: + _LOGGER.error( + "Connection to the router timed out at URL %s", self._url) + return False + if res.status_code != 200: + _LOGGER.error( + "Connection failed with http code %s", res.status_code) + return False + try: + result = res.json() + except ValueError: + # If json decoder could not parse the response + _LOGGER.error("Failed to parse response from router") + return False + + # parsing response + for info in result: + mac = info['macAddr'] + name = info['hostName'] + # No address = no item :) + if mac is None: + continue + + last_results.append(Device(mac.upper(), name)) + + self.last_results = last_results + + _LOGGER.info("Request successful") + return True