From e4ebae55d50daee6e901abdb602eac0bb28a7d80 Mon Sep 17 00:00:00 2001 From: LvivEchoes <pavlo.tsiupka@gmail.com> Date: Sun, 30 Apr 2017 06:39:11 +0300 Subject: [PATCH] Feature/add mikrotik device tracker (#7366) * Add Mikroik device tracker platform * Update coveragerc with mikrotik.py * Update coveragerc with mikrotik.py * Fix lint errors --- .coveragerc | 1 + .../components/device_tracker/mikrotik.py | 131 ++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 135 insertions(+) create mode 100644 homeassistant/components/device_tracker/mikrotik.py diff --git a/.coveragerc b/.coveragerc index 365a3a23fc1..90caadc23b5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -205,6 +205,7 @@ omit = homeassistant/components/device_tracker/icloud.py homeassistant/components/device_tracker/linksys_ap.py homeassistant/components/device_tracker/luci.py + homeassistant/components/device_tracker/mikrotik.py homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/nmap_tracker.py homeassistant/components/device_tracker/ping.py diff --git a/homeassistant/components/device_tracker/mikrotik.py b/homeassistant/components/device_tracker/mikrotik.py new file mode 100644 index 00000000000..5b28a9eb664 --- /dev/null +++ b/homeassistant/components/device_tracker/mikrotik.py @@ -0,0 +1,131 @@ +""" +Support for Mikrotik routers as device tracker. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.mikrotik/ +""" +import logging +import threading +from datetime import timedelta + +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, + CONF_PORT) +from homeassistant.util import Throttle + +REQUIREMENTS = ['librouteros==1.0.2'] + +# Return cached results if last scan was less then this time ago. +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) + +MTK_DEFAULT_API_PORT = '8728' + +_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, + vol.Optional(CONF_PORT, default=MTK_DEFAULT_API_PORT): cv.port +}) + + +def get_scanner(hass, config): + """Validate the configuration and return MTikScanner.""" + scanner = MikrotikScanner(config[DOMAIN]) + return scanner if scanner.success_init else None + + +class MikrotikScanner(DeviceScanner): + """This class queries a Mikrotik router.""" + + def __init__(self, config): + """Initialize the scanner.""" + self.last_results = {} + + self.host = config[CONF_HOST] + self.port = config[CONF_PORT] + self.username = config[CONF_USERNAME] + self.password = config[CONF_PASSWORD] + + self.lock = threading.Lock() + + self.connected = False + self.success_init = False + self.client = None + + self.success_init = self.connect_to_device() + + if self.success_init: + _LOGGER.info('Start polling Mikrotik router...') + self._update_info() + else: + _LOGGER.error('Connection to Mikrotik failed.') + + def connect_to_device(self): + """Connect to Mikrotik method.""" + # pylint: disable=import-error + import librouteros + try: + self.client = librouteros.connect( + self.host, + self.username, + self.password, + port=int(self.port) + ) + + routerboard_info = self.client(cmd='/system/routerboard/getall') + + if routerboard_info: + _LOGGER.info('Connected to Mikrotik %s with ip %s.', + routerboard_info[0].get('model', 'Router'), + self.host) + self.connected = True + + except (librouteros.exceptions.TrapError, + librouteros.exceptions.ConnectionError) as api_error: + _LOGGER.error('Connection error: %s', api_error) + + return self.connected + + def scan_devices(self): + """Scan for new devices and return a list with found device MACs.""" + self._update_info() + return [device for device in self.last_results] + + def get_device_name(self, mac): + """Return the name of the given device or None if we don't know.""" + with self.lock: + return self.last_results.get(mac) + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """Retrieve latest information from the Mikrotik box.""" + with self.lock: + _LOGGER.info('Loading wireless device from Mikrotik...') + + wireless_clients = self.client( + cmd='/interface/wireless/registration-table/getall' + ) + device_names = self.client(cmd='/ip/dhcp-server/lease/getall') + + if device_names is None or wireless_clients is None: + return False + + mac_names = {device.get('mac-address'): device.get('host-name') + for device in device_names + if device.get('mac-address')} + + self.last_results = { + device.get('mac-address'): + mac_names.get(device.get('mac-address')) + for device in wireless_clients + } + + return True diff --git a/requirements_all.txt b/requirements_all.txt index 4cde496c7cd..8b5ad9bc775 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -363,6 +363,9 @@ knxip==0.3.3 # homeassistant.components.device_tracker.owntracks libnacl==1.5.0 +# homeassistant.components.device_tracker.mikrotik +librouteros==1.0.2 + # homeassistant.components.media_player.soundtouch libsoundtouch==0.3.0 -- GitLab