From 47f219b50de335b62a92e695e72484754ac009ba Mon Sep 17 00:00:00 2001 From: Dario Iacampo Date: Sun, 8 Jul 2018 08:30:50 +0200 Subject: [PATCH 1/4] Support latest tplink Archer D9 Firmware version / Device Scanner --- .../components/device_tracker/tplink.py | 74 ++++++++++++++++++- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 5266b9c6f574b3..8b515441f53779 100644 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -35,9 +35,9 @@ def get_scanner(hass, config): """Validate the configuration and return a TP-Link scanner.""" - for cls in [Tplink5DeviceScanner, Tplink4DeviceScanner, - Tplink3DeviceScanner, Tplink2DeviceScanner, - TplinkDeviceScanner]: + for cls in [Tplink6DeviceScanner, Tplink5DeviceScanner, + Tplink4DeviceScanner, Tplink3DeviceScanner, + Tplink2DeviceScanner, TplinkDeviceScanner]: scanner = cls(config[DOMAIN]) if scanner.success_init: return scanner @@ -412,3 +412,71 @@ def _update_info(self): return True return False + + +class Tplink6DeviceScanner(DeviceScanner): + """ + Works for Archer D9 Firmware version. + + 0.9.1 0.1 v0041.0 Build 160224 Rel.59129n + """ + + def __init__(self, config): + """Initialize the scanner.""" + host = config[CONF_HOST] + password = config[CONF_PASSWORD] + + self.host = host + self.password = password + + self.parse_macs = re.compile( + 'MACAddress=([0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:' + + '[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2})') + + self.parse_names = re.compile('hostName=(.*)') + + self.last_results = {} + self.success_init = self._update_info() + + def scan_devices(self): + """Scan for new devices and return a list with found device IDs.""" + self._update_info() + return self.last_results.keys() + + def get_device_name(self, device): + """Get the name of the device.""" + return self.last_results.get(device) + + def _update_info(self): + """Ensure the information from the TP-Link router is up to date. + + Return boolean if scanning successful. + """ + _LOGGER.info("Loading wireless clients...") + + b64_encoded_password = base64.b64encode( + self.password.encode('ascii')).decode('ascii') + cookie = 'Authorization=Basic {}' \ + .format(b64_encoded_password) + + payload = "[LAN_HOST_ENTRY#0,0,0,0,0,0#0,0,0,0,0,0]0,0\r\n" + page = requests.post( + f'http://{self.host}/cgi?5', + data=payload, + headers={ + "Referer": f"http://{self.host}/", + "Cookie": cookie, + "Content-Type": "text/plain" + }, + timeout=10) + + mac_addresses = self.parse_macs.findall(page.text) + host_names = self.parse_names.findall(page.text) + + result = dict(zip(mac_addresses, host_names)) + + if result: + self.last_results = result + return True + + return False From 1dc8989c630f018d8d98050d375b682fd58b7028 Mon Sep 17 00:00:00 2001 From: Dario Iacampo Date: Sun, 15 Jul 2018 18:12:46 +0200 Subject: [PATCH 2/4] tplink integration on pypi package --- .../components/device_tracker/tplink.py | 137 ++++++++---------- requirements_all.txt | 3 + 2 files changed, 64 insertions(+), 76 deletions(-) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 8b515441f53779..6176d2b6dc0ebf 100644 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -22,6 +22,8 @@ CONF_HOST, CONF_PASSWORD, CONF_USERNAME, HTTP_HEADER_X_REQUESTED_WITH) import homeassistant.helpers.config_validation as cv +REQUIREMENTS = ['tplink==0.2.1'] + _LOGGER = logging.getLogger(__name__) HTTP_HEADER_NO_CACHE = 'no-cache' @@ -34,10 +36,22 @@ def get_scanner(hass, config): - """Validate the configuration and return a TP-Link scanner.""" - for cls in [Tplink6DeviceScanner, Tplink5DeviceScanner, - Tplink4DeviceScanner, Tplink3DeviceScanner, - Tplink2DeviceScanner, TplinkDeviceScanner]: + """ + Validate the configuration and return a TP-Link scanner. + + The default way of integrating devices is to use a pypi + + package, The TplinkDeviceScanner has been refactored + + to depend on a pypi package, the other implementations + + should be gradually migrated in the pypi package + + """ + for cls in [ + TplinkDeviceScanner, Tplink5DeviceScanner, Tplink4DeviceScanner, + Tplink3DeviceScanner, Tplink2DeviceScanner, Tplink1DeviceScanner + ]: scanner = cls(config[DOMAIN]) if scanner.success_init: return scanner @@ -46,6 +60,45 @@ def get_scanner(hass, config): class TplinkDeviceScanner(DeviceScanner): + """Queries the router for connected devices.""" + + def __init__(self, config): + """Initialize the scanner.""" + self.host = config[CONF_HOST] + self.password = config[CONF_PASSWORD] + self.username = config[CONF_USERNAME] + + self.last_results = {} + self.success_init = self._update_info() + + def scan_devices(self): + """Scan for new devices and return a list with found device IDs.""" + self._update_info() + return self.last_results.keys() + + def get_device_name(self, device): + """Get the name of the device.""" + return self.last_results.get(device) + + def _update_info(self): + """Ensure the information from the TP-Link router is up to date. + + Return boolean if scanning successful. + """ + from tplink.tplink import TpLinkClient + _LOGGER.info("Loading wireless clients...") + client = TpLinkClient( + self.password, host=self.host, username=self.username) + result = client.get_connected_devices() + + if result: + self.last_results = result + return True + + return False + + +class Tplink1DeviceScanner(DeviceScanner): """This class queries a wireless router running TP-Link firmware.""" def __init__(self, config): @@ -94,7 +147,7 @@ def _update_info(self): return False -class Tplink2DeviceScanner(TplinkDeviceScanner): +class Tplink2DeviceScanner(Tplink1DeviceScanner): """This class queries a router with newer version of TP-Link firmware.""" def scan_devices(self): @@ -147,7 +200,7 @@ def _update_info(self): return False -class Tplink3DeviceScanner(TplinkDeviceScanner): +class Tplink3DeviceScanner(Tplink1DeviceScanner): """This class queries the Archer C9 router with version 150811 or high.""" def __init__(self, config): @@ -256,7 +309,7 @@ def _log_out(self): self.sysauth = '' -class Tplink4DeviceScanner(TplinkDeviceScanner): +class Tplink4DeviceScanner(Tplink1DeviceScanner): """This class queries an Archer C7 router with TP-Link firmware 150427.""" def __init__(self, config): @@ -337,7 +390,7 @@ def _update_info(self): return True -class Tplink5DeviceScanner(TplinkDeviceScanner): +class Tplink5DeviceScanner(Tplink1DeviceScanner): """This class queries a TP-Link EAP-225 AP with newer TP-Link FW.""" def scan_devices(self): @@ -412,71 +465,3 @@ def _update_info(self): return True return False - - -class Tplink6DeviceScanner(DeviceScanner): - """ - Works for Archer D9 Firmware version. - - 0.9.1 0.1 v0041.0 Build 160224 Rel.59129n - """ - - def __init__(self, config): - """Initialize the scanner.""" - host = config[CONF_HOST] - password = config[CONF_PASSWORD] - - self.host = host - self.password = password - - self.parse_macs = re.compile( - 'MACAddress=([0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:' + - '[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2})') - - self.parse_names = re.compile('hostName=(.*)') - - self.last_results = {} - self.success_init = self._update_info() - - def scan_devices(self): - """Scan for new devices and return a list with found device IDs.""" - self._update_info() - return self.last_results.keys() - - def get_device_name(self, device): - """Get the name of the device.""" - return self.last_results.get(device) - - def _update_info(self): - """Ensure the information from the TP-Link router is up to date. - - Return boolean if scanning successful. - """ - _LOGGER.info("Loading wireless clients...") - - b64_encoded_password = base64.b64encode( - self.password.encode('ascii')).decode('ascii') - cookie = 'Authorization=Basic {}' \ - .format(b64_encoded_password) - - payload = "[LAN_HOST_ENTRY#0,0,0,0,0,0#0,0,0,0,0,0]0,0\r\n" - page = requests.post( - f'http://{self.host}/cgi?5', - data=payload, - headers={ - "Referer": f"http://{self.host}/", - "Cookie": cookie, - "Content-Type": "text/plain" - }, - timeout=10) - - mac_addresses = self.parse_macs.findall(page.text) - host_names = self.parse_names.findall(page.text) - - result = dict(zip(mac_addresses, host_names)) - - if result: - self.last_results = result - return True - - return False diff --git a/requirements_all.txt b/requirements_all.txt index b6c1bb7dc41cb6..b0fee1a3780dce 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1336,6 +1336,9 @@ toonlib==1.0.2 # homeassistant.components.alarm_control_panel.totalconnect total_connect_client==0.18 +# homeassistant.components.device_tracker.tplink +tplink==0.2.1 + # homeassistant.components.sensor.transmission # homeassistant.components.switch.transmission transmissionrpc==0.11 From dcff6b5ed191950230cd8c5efd4f16ebf8109f94 Mon Sep 17 00:00:00 2001 From: Dario Iacampo Date: Mon, 16 Jul 2018 16:59:17 +0200 Subject: [PATCH 3/4] initialize the client only once --- homeassistant/components/device_tracker/tplink.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 6176d2b6dc0ebf..30ca7ce80ebc47 100644 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -64,9 +64,12 @@ class TplinkDeviceScanner(DeviceScanner): def __init__(self, config): """Initialize the scanner.""" + from tplink.tplink import TpLinkClient self.host = config[CONF_HOST] self.password = config[CONF_PASSWORD] self.username = config[CONF_USERNAME] + self.tplink_client = TpLinkClient( + self.password, host=self.host, username=self.username) self.last_results = {} self.success_init = self._update_info() @@ -85,11 +88,8 @@ def _update_info(self): Return boolean if scanning successful. """ - from tplink.tplink import TpLinkClient _LOGGER.info("Loading wireless clients...") - client = TpLinkClient( - self.password, host=self.host, username=self.username) - result = client.get_connected_devices() + result = self.tplink_client.get_connected_devices() if result: self.last_results = result From bc922def6cf9d0434b6c091723e981f68fad8cd3 Mon Sep 17 00:00:00 2001 From: Dario Iacampo Date: Mon, 16 Jul 2018 18:06:08 +0200 Subject: [PATCH 4/4] remove unnecessary instance attributes --- homeassistant/components/device_tracker/tplink.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 30ca7ce80ebc47..346f381db34b1c 100644 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -65,11 +65,12 @@ class TplinkDeviceScanner(DeviceScanner): def __init__(self, config): """Initialize the scanner.""" from tplink.tplink import TpLinkClient - self.host = config[CONF_HOST] - self.password = config[CONF_PASSWORD] - self.username = config[CONF_USERNAME] + host = config[CONF_HOST] + password = config[CONF_PASSWORD] + username = config[CONF_USERNAME] + self.tplink_client = TpLinkClient( - self.password, host=self.host, username=self.username) + password, host=host, username=username) self.last_results = {} self.success_init = self._update_info()