diff --git a/.gitignore b/.gitignore index f92aa1b..6651715 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +*.pyc +*.egg* +.cache +.coverage +.idea +.pytest_cache +.tox .vscode/ .DS_Store/ Test/ @@ -5,4 +12,3 @@ build/ dist/ pyubee.egg-info/ __pycache__ - diff --git a/CHANGELOG.md b/CHANGELOG.md index 522353e..f773155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # PyUbee CHANGELOG This file is used to list changes made in each version of the PyUbee. +## 0.4 (March 26 2019) +* Detect Ubee model automatically ([@mzdrale](https://github.com/mzdrale) - [#5](https://github.com/mzdrale/pyubee/pull/5)) + ## 0.3 (March 12 2019) * Add pyubee command line interface ([@StevenLooman](https://github.com/StevenLooman) - [#3](https://github.com/mzdrale/pyubee/pull/3)) * Add support for EVW3200-Wifi model ([@StevenLooman](https://github.com/StevenLooman) - [#3](https://github.com/mzdrale/pyubee/pull/3)) diff --git a/README.md b/README.md index f5f54b9..4b2ee9c 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ from pyubee import Ubee ubee = Ubee( host='192.168.1.1', username='admin', - password='somepassword', - model='EVW32C-0N' + password='somepassword' ) if not ubee.session_active(): @@ -34,7 +33,7 @@ ubee.logout() CLI --- -A simple command line interface is available to query the router. The cli takes `host`, `username`, and `password` as mandatory arguments. The optional argument model can be used to specify the model of your routers (defaults to `EVW32C-0N`.) +A simple command line interface is available to query the router. The cli takes `host`, `username`, and `password` as mandatory arguments. The optional argument model can be used to specify the model of your routers. If model is not specified, this tool will try to detect it automatically. ``` $ pyubee --help usage: pyubee [-h] [--model MODEL] host username password @@ -50,9 +49,9 @@ optional arguments: -h, --help show this help message and exit --model MODEL Model, supported models: EVW32C-0N, EVW3200-Wifi -$ pyubee 192.168.1.1 admin somepassword --model EVW3200-Wifi -AA:BB:CC:DD:EE:FF 192.168.001.010 -FF:EE:DD:CC:BB:AA 192.168.001.011 +$ pyubee 192.168.1.1 admin somepassword +AA:BB:CC:DD:EE:FF 192.168.1.10 +FF:EE:DD:CC:BB:AA 192.168.1.11 ``` Notice @@ -96,3 +95,4 @@ This library was written for and tested with: * Ubee EVW32C-0N * Ubee EVW3200-Wifi +* Ambit EVW320B diff --git a/pyubee/__init__.py b/pyubee/__init__.py index 5f82ede..1e6a651 100644 --- a/pyubee/__init__.py +++ b/pyubee/__init__.py @@ -2,7 +2,6 @@ import logging import re -import sys import requests from requests.exceptions import RequestException @@ -10,6 +9,8 @@ _LOGGER = logging.getLogger(__name__) +MODEL_REGEX = re.compile(r'(.*)') + MODELS = { 'EVW32C-0N': { 'url_session_active': '/UbeeSysInfo.asp', @@ -20,7 +21,7 @@ 'regex_login': re.compile(r'Residential Gateway Login'), 'regex_wifi_devices': re.compile( r'' - r'([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:' # mac address + r'([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:' # mac address r'[0-9a-fA-F]{2}:[0-9a-fA-F]{2})' # mac address, cont'd r'\d+' # age r'.+' # rssi @@ -37,7 +38,7 @@ r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ip address ), }, - 'EVW3200-Wifi': { + 'EVW320B': { 'url_session_active': '/BasicStatus.asp', 'url_login': '/goform/loginMR3', 'url_logout': '/logout.asp', @@ -46,7 +47,7 @@ 'regex_login': re.compile(r'Residential Gateway Login'), 'regex_wifi_devices': re.compile( r'' - r'([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:' # mac address + r'([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:' # mac address r'[0-9a-fA-F]{2}:[0-9a-fA-F]{2})' # mac address, cont'd r'\d+' # age r'.+' # rssi @@ -70,27 +71,58 @@ class Ubee: """Represents a session to a Ubee Router.""" - def __init__(self, host=None, username=None, password=None, model='EVW32C-0N'): + def __init__(self, host=None, username=None, password=None, model='detect'): """Initialize a Ubee session.""" + self.host = host + self.username = username + self.password = password + + if model == 'detect': + model = self.detect_model() if model not in MODELS: raise LookupError('Unknown model') - self.host = host - self.username = username - self.password = password self.model = model self._model_info = MODELS[model] @property def _base_url(self): + """Form base url.""" return 'http://{}'.format(self.host) + def _get(self, url): + """Do a HTTP GET.""" + # pylint: disable=no-self-use + return requests.get(url, timeout=4) + + def _post(self, url, data): + """Do a HTTP POST.""" + # pylint: disable=no-self-use + return requests.post(url, data=data, timeout=4) + + def detect_model(self): + """Autodetect Ubee model.""" + url = self._base_url + "/RootDevice.xml" + try: + response = self._get(url) + except RequestException as ex: + _LOGGER.error("Connection to the router failed: %s", ex) + return "Unknown" + + data = response.text + entries = MODEL_REGEX.findall(data) + + if entries: + return entries[1] + + return "Unknown" + def session_active(self): """Check if session is active.""" url = self._base_url + self._model_info['url_session_active'] try: - response = requests.get(url, timeout=4) + response = self._get(url) except RequestException as ex: _LOGGER.error("Connection to the router failed: %s", ex) return False @@ -110,7 +142,7 @@ def login(self): 'loginPassword': self.password } try: - response = requests.post(url, data=payload, timeout=4) + response = self._post(url, payload) except RequestException as ex: _LOGGER.error("Connection to the router failed: %s", ex) return False @@ -127,10 +159,10 @@ def login(self): return False def logout(self): - """Logout from Admin interface""" + """Logout from Admin interface.""" url = self._base_url + self._model_info['url_logout'] try: - response = requests.get(url, timeout=4) + response = self._get(url) except RequestException as ex: _LOGGER.error("Connection to the router failed: %s", ex) return False @@ -141,7 +173,7 @@ def logout(self): return False def get_connected_devices(self): - """Get list of connected devices""" + """Get list of connected devices.""" lan_devices = self.get_connected_devices_lan() _LOGGER.debug('LAN devices: %s', lan_devices) wifi_devices = self.get_connected_devices_wifi() @@ -150,17 +182,11 @@ def get_connected_devices(self): devices.update(wifi_devices) return devices - def _format_mac_address(self, address): - """Format a given address to a default format.""" - # remove all ':' and '-' - bare = address.upper().replace(':', '').replace('-', '') - return ':'.join(bare[i:i + 2] for i in range(0, 12, 2)) - def get_connected_devices_lan(self): """Get list of connected devices via ethernet.""" url = self._base_url + self._model_info['url_connected_devices_lan'] try: - response = requests.get(url, timeout=4) + response = self._get(url) except RequestException as ex: _LOGGER.error("Connection to the router failed: %s", ex) return [] @@ -176,7 +202,7 @@ def get_connected_devices_wifi(self): """Get list of connected devices via wifi.""" url = self._base_url + self._model_info['url_connected_devices_wifi'] try: - response = requests.get(url, timeout=4) + response = self._get(url) except RequestException as ex: _LOGGER.error("Connection to the router failed: %s", ex) return [] @@ -187,3 +213,10 @@ def get_connected_devices_wifi(self): self._format_mac_address(address): hostname for address, hostname in entries } + + def _format_mac_address(self, address): + """Format a given address to a default format.""" + # pylint: disable=no-self-use + # remove all ':' and '-' + bare = address.upper().replace(':', '').replace('-', '') + return ':'.join(bare[i:i + 2] for i in range(0, 12, 2)) diff --git a/pyubee/__main__.py b/pyubee/__main__.py index 3061a4f..cbe52a0 100644 --- a/pyubee/__main__.py +++ b/pyubee/__main__.py @@ -12,7 +12,7 @@ def main(): parser.add_argument('host', help='Host') parser.add_argument('username', help='Username') parser.add_argument('password', help='Password') - parser.add_argument('--model', default="EVW32C-0N", + parser.add_argument('-m', '--model', default="detect", help='Model, supported models: ' + ', '.join(SUPPORTED_MODELS)) args = parser.parse_args() @@ -27,8 +27,13 @@ def main(): sys.exit(1) devices = ubee.get_connected_devices() - for device in devices: - print("%s\t%s" % (device, devices[device])) + + if devices: + print("Connected devices:") + for device in devices: + print("%s\t%s" % (device, devices[device])) + else: + print("No connected devices found") if __name__ == '__main__': diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..73137bf --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[metadata] +license_file = LICENSE + +[flake8] +max-line-length = 119 diff --git a/setup.py b/setup.py index 56249d0..b7c8c6f 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="pyubee", - version="0.3", + version="0.4.dev2", author="Miroslav Zdrale", author_email="mzdrale@gmail.com", description="Simple library for getting stats from Ubee routers.", diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..a076f60 --- /dev/null +++ b/tox.ini @@ -0,0 +1,22 @@ +[tox] +envlist = py35, py36, py37, flake8, pylint, pydocstyle + +[testenv:flake8] +basepython = python3 +ignore_errors = True +deps = flake8 +commands = flake8 pyubee + +[testenv:pylint] +basepython = python3 +ignore_errors = True +deps = + pylint + requests +commands = pylint pyubee + +[testenv:pydocstyle] +basepython = python3 +ignore_errors = True +deps = pydocstyle +commands = pydocstyle pyubee