From 2c9c79ea61cb16c5dbbef7482b57667dc2281734 Mon Sep 17 00:00:00 2001 From: Chris Mulder Date: Sun, 9 Aug 2015 18:10:34 +0200 Subject: [PATCH 1/2] Add support for TP-Link ArcherC9 to to device tracker component --- .../components/device_tracker/tplink2.py | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100755 homeassistant/components/device_tracker/tplink2.py diff --git a/homeassistant/components/device_tracker/tplink2.py b/homeassistant/components/device_tracker/tplink2.py new file mode 100755 index 00000000000..834b1f210ae --- /dev/null +++ b/homeassistant/components/device_tracker/tplink2.py @@ -0,0 +1,128 @@ +""" +homeassistant.components.device_tracker.tplink2 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a newer model TP-Link router for device +presence. + +Configuration: + +To use the TP-Link tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: tplink2 + 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. + +""" +import logging +import base64 +from datetime import timedelta +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 TP-Link scanner. """ + if not validate_config(config, + {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, + _LOGGER): + return None + + scanner = Tplink2DeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +class Tplink2DeviceScanner(object): + """ This class queries a wireless router running TP-Link firmware + for connected devices. + """ + + def __init__(self, config): + host = config[CONF_HOST] + username, password = config[CONF_USERNAME], config[CONF_PASSWORD] + + self.host = host + self.username = username + self.password = password + + self.last_results = {} + self.lock = threading.Lock() + self.success_init = self._update_info() + + 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. """ + + 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. """ + + with self.lock: + _LOGGER.info("Loading wireless clients...") + + url = 'http://{}/data/map_access_wireless_client_grid.json'.format(self.host) + referer = 'http://{}'.format(self.host) + + # Router uses Authorization cookie instead of header + # Let's create the cookie + username_password = '{}:{}'.format(self.username, self.password) + b64_encoded_username_password = base64.b64encode( + username_password.encode('ascii') + ).decode('ascii') + authorization = 'Basic {}'.format(b64_encoded_username_password) + cookie = 'Authorization={}'.format(authorization) + + response = requests.post(url, headers={'referer': referer, + 'cookie': cookie}) + + result = None + try: + result = response.json().get('data') + except ValueError: + _LOGGER.error("Router didn't respond with JSON. Check if credentials are correct.") + + if result: + self.last_results = {device['mac_addr'].replace('-', ':'): device['name'] + for device in result} + return True + + return False From 58fcf7934025ad9c2420840c75613ad070d935e3 Mon Sep 17 00:00:00 2001 From: Chris Mulder Date: Mon, 10 Aug 2015 20:03:43 +0200 Subject: [PATCH 2/2] Put new TP-Link device tracker class in same file as original and use the new one and have tailback to original one. --- .../components/device_tracker/tplink.py | 66 ++++++++- .../components/device_tracker/tplink2.py | 128 ------------------ 2 files changed, 65 insertions(+), 129 deletions(-) delete mode 100755 homeassistant/components/device_tracker/tplink2.py diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 24d170a5de7..8e556e47e8a 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -31,6 +31,7 @@ password The password for your given admin account. """ +import base64 import logging from datetime import timedelta import re @@ -55,7 +56,10 @@ def get_scanner(hass, config): _LOGGER): return None - 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 @@ -115,3 +119,63 @@ class TplinkDeviceScanner(object): return True return False + + +class Tplink2DeviceScanner(TplinkDeviceScanner): + """ This class queries a wireless router running newer version of TP-Link + firmware for connected devices. + """ + + 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. """ + + 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. """ + + with self.lock: + _LOGGER.info("Loading wireless clients...") + + url = 'http://{}/data/map_access_wireless_client_grid.json'\ + .format(self.host) + referer = 'http://{}'.format(self.host) + + # Router uses Authorization cookie instead of header + # Let's create the cookie + username_password = '{}:{}'.format(self.username, self.password) + b64_encoded_username_password = base64.b64encode( + username_password.encode('ascii') + ).decode('ascii') + cookie = 'Authorization=Basic {}'\ + .format(b64_encoded_username_password) + + response = requests.post(url, headers={'referer': referer, + 'cookie': cookie}) + + try: + result = response.json().get('data') + except ValueError: + _LOGGER.error("Router didn't respond with JSON. " + "Check if credentials are correct.") + return False + + if result: + self.last_results = { + device['mac_addr'].replace('-', ':'): device['name'] + for device in result + } + return True + + return False diff --git a/homeassistant/components/device_tracker/tplink2.py b/homeassistant/components/device_tracker/tplink2.py deleted file mode 100755 index 834b1f210ae..00000000000 --- a/homeassistant/components/device_tracker/tplink2.py +++ /dev/null @@ -1,128 +0,0 @@ -""" -homeassistant.components.device_tracker.tplink2 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Device tracker platform that supports scanning a newer model TP-Link router for device -presence. - -Configuration: - -To use the TP-Link tracker you will need to add something like the following -to your config/configuration.yaml - -device_tracker: - platform: tplink2 - 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. - -""" -import logging -import base64 -from datetime import timedelta -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 TP-Link scanner. """ - if not validate_config(config, - {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, - _LOGGER): - return None - - scanner = Tplink2DeviceScanner(config[DOMAIN]) - - return scanner if scanner.success_init else None - - -class Tplink2DeviceScanner(object): - """ This class queries a wireless router running TP-Link firmware - for connected devices. - """ - - def __init__(self, config): - host = config[CONF_HOST] - username, password = config[CONF_USERNAME], config[CONF_PASSWORD] - - self.host = host - self.username = username - self.password = password - - self.last_results = {} - self.lock = threading.Lock() - self.success_init = self._update_info() - - 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. """ - - 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. """ - - with self.lock: - _LOGGER.info("Loading wireless clients...") - - url = 'http://{}/data/map_access_wireless_client_grid.json'.format(self.host) - referer = 'http://{}'.format(self.host) - - # Router uses Authorization cookie instead of header - # Let's create the cookie - username_password = '{}:{}'.format(self.username, self.password) - b64_encoded_username_password = base64.b64encode( - username_password.encode('ascii') - ).decode('ascii') - authorization = 'Basic {}'.format(b64_encoded_username_password) - cookie = 'Authorization={}'.format(authorization) - - response = requests.post(url, headers={'referer': referer, - 'cookie': cookie}) - - result = None - try: - result = response.json().get('data') - except ValueError: - _LOGGER.error("Router didn't respond with JSON. Check if credentials are correct.") - - if result: - self.last_results = {device['mac_addr'].replace('-', ':'): device['name'] - for device in result} - return True - - return False