Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a1c1f13
rework device scanning, add tests
engrbm87 Oct 11, 2019
9f13bf8
update requirements and coverage
engrbm87 Oct 11, 2019
710b3b5
fix description comments
engrbm87 Oct 23, 2019
eebe079
update tests, fix disabled entity updates
engrbm87 Nov 14, 2019
bf8d767
rework device scanning, add tests
engrbm87 Oct 11, 2019
8e1f5e7
update requirements and coverage
engrbm87 Oct 11, 2019
e67a7b0
fix description comments
engrbm87 Oct 23, 2019
231c611
update tests, fix disabled entity updates
engrbm87 Nov 14, 2019
d89c362
Merge branch 'mikortik-integration' of https://github.com/engrbm87/ho…
engrbm87 Jan 3, 2020
a130051
update librouteros to 3.0.0
engrbm87 Jan 3, 2020
44c5348
fix sorting
engrbm87 Jan 3, 2020
394bfc6
fix sorting 2
engrbm87 Jan 3, 2020
acfac87
revert to 2.3.0 as 3.0.0 requires code update
engrbm87 Jan 6, 2020
404b500
fix requirements
engrbm87 Jan 6, 2020
3af5c98
apply fixes
engrbm87 Jan 9, 2020
4267e70
Merge branch 'dev' into mikortik-integration
engrbm87 Jan 9, 2020
ebeb1c6
fix tests
engrbm87 Jan 9, 2020
6c5c384
update hub.py and fix tests
engrbm87 Jan 10, 2020
c07f1e9
fix test_hub_setup_failed
engrbm87 Jan 10, 2020
adc4558
Merge branch 'dev' of https://github.com/home-assistant/home-assistan…
engrbm87 Jan 28, 2020
e9dcfeb
rebased on dev and update librouteros to 3.0.0
engrbm87 Jan 30, 2020
33b47a1
fixed test_config_flow
engrbm87 Jan 30, 2020
88944e3
Merge branch 'dev' of https://github.com/home-assistant/home-assistan…
engrbm87 Jan 30, 2020
a7d141b
fixed tests
engrbm87 Jan 30, 2020
7891909
fix test_config_flow
engrbm87 Jan 30, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,8 @@ omit =
homeassistant/components/metoffice/weather.py
homeassistant/components/microsoft/tts.py
homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/*
homeassistant/components/mikrotik/hub.py
homeassistant/components/mikrotik/device_tracker.py
homeassistant/components/mill/climate.py
homeassistant/components/mill/const.py
homeassistant/components/minio/*
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ homeassistant/components/met/* @danielhiversen
homeassistant/components/meteo_france/* @victorcerutti @oncleben31
homeassistant/components/meteoalarm/* @rolfberkenbosch
homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel
homeassistant/components/mikrotik/* @engrbm87
homeassistant/components/mill/* @danielhiversen
homeassistant/components/min_max/* @fabaff
homeassistant/components/minio/* @tkislan
Expand Down
37 changes: 37 additions & 0 deletions homeassistant/components/mikrotik/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"config": {
"title": "Mikrotik",
"step": {
"user": {
"title": "Set up Mikrotik Router",
"data": {
"name": "Name",
"host": "Host",
"username": "Username",
"password": "Password",
"port": "Port",
"verify_ssl": "Use ssl"
}
}
},
"error": {
"name_exists": "Name exists",
"cannot_connect": "Connection Unsuccessful",
"wrong_credentials": "Wrong Credentials"
},
"abort": {
"already_configured": "Mikrotik is already configured"
}
},
"options": {
"step": {
"device_tracker": {
"data": {
"arp_ping": "Enable ARP ping",
"force_dhcp": "Force scanning using DHCP",
"detection_time": "Consider home interval"
}
}
}
}
}
197 changes: 52 additions & 145 deletions homeassistant/components/mikrotik/__init__.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,43 @@
"""The mikrotik component."""
import logging
import ssl

from librouteros import connect
from librouteros.exceptions import LibRouterosError
from librouteros.login import plain as login_plain, token as login_token
"""The Mikrotik component."""
import voluptuous as vol

from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
CONF_HOST,
CONF_METHOD,
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import load_platform

from .const import (
ATTR_MANUFACTURER,
CONF_ARP_PING,
CONF_ENCODING,
CONF_LOGIN_METHOD,
CONF_TRACK_DEVICES,
DEFAULT_ENCODING,
CONF_DETECTION_TIME,
CONF_FORCE_DHCP,
DEFAULT_API_PORT,
DEFAULT_DETECTION_TIME,
DEFAULT_NAME,
DOMAIN,
HOSTS,
IDENTITY,
MIKROTIK_SERVICES,
MTK_LOGIN_PLAIN,
MTK_LOGIN_TOKEN,
NAME,
)

_LOGGER = logging.getLogger(__name__)

MTK_DEFAULT_API_PORT = "8728"
MTK_DEFAULT_API_SSL_PORT = "8729"
from .hub import MikrotikHub

MIKROTIK_SCHEMA = vol.All(
vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_METHOD): cv.string,
vol.Optional(CONF_LOGIN_METHOD): vol.Any(MTK_LOGIN_PLAIN, MTK_LOGIN_TOKEN),
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
vol.Optional(CONF_TRACK_DEVICES, default=True): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_API_PORT): cv.port,
vol.Optional(CONF_VERIFY_SSL, default=False): cv.boolean,
vol.Optional(CONF_ARP_PING, default=False): cv.boolean,
vol.Optional(CONF_FORCE_DHCP, default=False): cv.boolean,
vol.Optional(
CONF_DETECTION_TIME, default=DEFAULT_DETECTION_TIME
): cv.time_period,
}
)
)
Expand All @@ -61,124 +47,45 @@
)


def setup(hass, config):
"""Set up the Mikrotik component."""
hass.data[DOMAIN] = {HOSTS: {}}

for device in config[DOMAIN]:
host = device[CONF_HOST]
use_ssl = device.get(CONF_SSL)
user = device.get(CONF_USERNAME)
password = device.get(CONF_PASSWORD, "")
login = device.get(CONF_LOGIN_METHOD)
encoding = device.get(CONF_ENCODING)
track_devices = device.get(CONF_TRACK_DEVICES)

if CONF_PORT in device:
port = device.get(CONF_PORT)
else:
if use_ssl:
port = MTK_DEFAULT_API_SSL_PORT
else:
port = MTK_DEFAULT_API_PORT

if login == MTK_LOGIN_PLAIN:
login_method = login_plain
else:
login_method = login_token

try:
api = MikrotikClient(
host, use_ssl, port, user, password, login_method, encoding
async def async_setup(hass, config):
"""Import the Mikrotik component from config."""

if DOMAIN in config:
for entry in config[DOMAIN]:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=entry
)
)
api.connect_to_device()
hass.data[DOMAIN][HOSTS][host] = {"config": device, "api": api}
except LibRouterosError as api_error:
_LOGGER.error("Mikrotik %s error %s", host, api_error)
continue

if track_devices:
hass.data[DOMAIN][HOSTS][host][DEVICE_TRACKER] = True
load_platform(hass, DEVICE_TRACKER, DOMAIN, None, config)
return True


async def async_setup_entry(hass, config_entry):
"""Set up the Mikrotik component."""

if not hass.data[DOMAIN][HOSTS]:
hub = MikrotikHub(hass, config_entry)
if not await hub.async_setup():
return False

hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub
device_registry = await hass.helpers.device_registry.async_get_registry()
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(DOMAIN, hub.serial_num)},
manufacturer=ATTR_MANUFACTURER,
model=hub.model,
name=hub.hostname,
sw_version=hub.firmware,
)

return True


class MikrotikClient:
"""Handle all communication with the Mikrotik API."""

def __init__(self, host, use_ssl, port, user, password, login_method, encoding):
"""Initialize the Mikrotik Client."""
self._host = host
self._use_ssl = use_ssl
self._port = port
self._user = user
self._password = password
self._login_method = login_method
self._encoding = encoding
self._ssl_wrapper = None
self.hostname = None
self._client = None
self._connected = False

def connect_to_device(self):
"""Connect to Mikrotik device."""
self._connected = False
_LOGGER.debug("[%s] Connecting to Mikrotik device", self._host)

kwargs = {
"encoding": self._encoding,
"login_methods": self._login_method,
"port": self._port,
}
async def async_unload_entry(hass, config_entry):
"""Unload a config entry."""
await hass.config_entries.async_forward_entry_unload(config_entry, "device_tracker")

if self._use_ssl:
if self._ssl_wrapper is None:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
self._ssl_wrapper = ssl_context.wrap_socket
kwargs["ssl_wrapper"] = self._ssl_wrapper

try:
self._client = connect(self._host, self._user, self._password, **kwargs)
self._connected = True
except LibRouterosError as api_error:
_LOGGER.error("Mikrotik %s: %s", self._host, api_error)
self._client = None
return False

self.hostname = self.get_hostname()
_LOGGER.info("Mikrotik Connected to %s (%s)", self.hostname, self._host)
return self._connected

def get_hostname(self):
"""Return device host name."""
data = list(self.command(MIKROTIK_SERVICES[IDENTITY]))
return data[0][NAME] if data else None

def connected(self):
"""Return connected boolean."""
return self._connected

def command(self, cmd, params=None):
"""Retrieve data from Mikrotik API."""
if not self._connected or not self._client:
if not self.connect_to_device():
return None
try:
if params:
response = self._client(cmd=cmd, **params)
else:
response = self._client(cmd=cmd)
except LibRouterosError as api_error:
_LOGGER.error(
"Mikrotik %s failed to retrieve data. cmd=[%s] Error: %s",
self._host,
cmd,
api_error,
)
return None
return response if response else None
hass.data[DOMAIN].pop(config_entry.entry_id)

return True
Loading