From 15538442795685b06be8c387751a2a87fea4def4 Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Tue, 22 Sep 2015 22:48:43 +0200 Subject: [PATCH 1/3] Added support for the newest tp-link firmware Currently this seemingly only applies to the Archer C9 --- .../components/device_tracker/tplink.py | 111 +++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 6b12000cf45..2ecfaea4eb2 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -54,10 +54,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 @@ -79,6 +82,9 @@ class TplinkDeviceScanner(object): self.username = username self.password = password + self.stok = '' + self.sysauth = '' + self.last_results = {} self.lock = threading.Lock() self.success_init = self._update_info() @@ -187,3 +193,104 @@ class Tplink2DeviceScanner(TplinkDeviceScanner): 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 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: + _LOGGER.error("Couldn't fetch auth tokens!") + return False + + 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 From 2f2bd7a616a0e47e4db1ad00655b1e9a5428f3b4 Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Mon, 12 Oct 2015 09:18:55 +0200 Subject: [PATCH 2/3] Fixed pylint and pep8 violations --- .../components/device_tracker/tplink.py | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 2ecfaea4eb2..60c337309d4 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -82,9 +82,6 @@ class TplinkDeviceScanner(object): self.username = username self.password = password - self.stok = '' - self.sysauth = '' - self.last_results = {} self.lock = threading.Lock() self.success_init = self._update_info() @@ -162,7 +159,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) @@ -172,7 +169,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, @@ -189,17 +186,23 @@ 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): + super(Tplink3DeviceScanner, self).__init__(config) + self.stok = '' + self.sysauth = '' + def scan_devices(self): """ Scans for new devices and return a list containing found device ids. @@ -224,30 +227,30 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): _LOGGER.info("Retrieving auth tokens...") - url = 'http://{}/cgi-bin/luci/;stok=/login?form=login'\ + 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}) + 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']) + regex_result = re.search('sysauth=(.*);', + response.headers['set-cookie']) self.sysauth = regex_result.group(1) _LOGGER.info(self.sysauth) return True - except: + except ValueError: _LOGGER.error("Couldn't fetch auth tokens!") return False - return False - @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): """ @@ -261,11 +264,14 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): _LOGGER.info("Loading wireless clients...") - url = 'http://{}/cgi-bin/luci/;stok={}/admin/wireless?form=statistics'\ + 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}) + response = requests.post(url, + params={'operation': 'load'}, + headers={'referer': referer}, + cookies={'sysauth': self.sysauth}) try: json_response = response.json() @@ -274,12 +280,14 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): result = response.json().get('data') else: if json_response.get('errorcode') == 'timeout': - _LOGGER.info("Token timed out. Relogging on next scan.") + _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.") + _LOGGER.error("An unknown error happened " + "while fetching data.") return False except ValueError: _LOGGER.error("Router didn't respond with JSON. " @@ -290,7 +298,7 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): self.last_results = { device['mac'].replace('-', ':'): device['mac'] for device in result - } + } return True return False From a6cb19b27dea2af515875de5d5bc3b7192200b92 Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Mon, 12 Oct 2015 22:42:45 +0200 Subject: [PATCH 3/3] Fixed an issue with the initiation of the new attributes --- homeassistant/components/device_tracker/tplink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 60c337309d4..d368637cd6b 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -199,9 +199,9 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): """ def __init__(self, config): - super(Tplink3DeviceScanner, self).__init__(config) self.stok = '' self.sysauth = '' + super(Tplink3DeviceScanner, self).__init__(config) def scan_devices(self): """