converted leading tabs to spaces

This commit is contained in:
tomaae 2019-12-02 18:13:55 +01:00
parent ae54d8cbaa
commit b7083263fb
9 changed files with 749 additions and 749 deletions

View file

@ -1,38 +1,38 @@
{ {
"config": { "config": {
"title": "Mikrotik Router", "title": "Mikrotik Router",
"step": { "step": {
"user": { "user": {
"title": "Mikrotik Router", "title": "Mikrotik Router",
"description": "Set up Mikrotik Router integration.", "description": "Set up Mikrotik Router integration.",
"data": { "data": {
"name": "Name of the integration", "name": "Name of the integration",
"host": "Host", "host": "Host",
"port": "Port", "port": "Port",
"username": "Username", "username": "Username",
"password": "Password", "password": "Password",
"ssl": "Use SSL" "ssl": "Use SSL"
} }
} }
}, },
"error": { "error": {
"name_exists": "Name already exists.", "name_exists": "Name already exists.",
"cannot_connect": "Cannot connect to Mikrotik.", "cannot_connect": "Cannot connect to Mikrotik.",
"wrong_login": "Invalid user name or password.", "wrong_login": "Invalid user name or password.",
"routeros_api_missing": "Python module routeros_api not installed." "routeros_api_missing": "Python module routeros_api not installed."
} }
}, },
"options": { "options": {
"step": { "step": {
"init": { "init": {
"data": {} "data": {}
}, },
"device_tracker": { "device_tracker": {
"data": { "data": {
"scan_interval": "Scan interval", "scan_interval": "Scan interval",
"track_arp": "Show client MAC and IP on interfaces" "track_arp": "Show client MAC and IP on interfaces"
} }
} }
} }
} }
} }

View file

@ -3,19 +3,19 @@
import logging import logging
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_NAME,
CONF_HOST, CONF_HOST,
CONF_PORT, CONF_PORT,
CONF_USERNAME, CONF_USERNAME,
CONF_PASSWORD, CONF_PASSWORD,
CONF_SSL, CONF_SSL,
) )
from .mikrotik_controller import MikrotikControllerData from .mikrotik_controller import MikrotikControllerData
from .const import ( from .const import (
DOMAIN, DOMAIN,
DATA_CLIENT, DATA_CLIENT,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -25,61 +25,61 @@ _LOGGER = logging.getLogger(__name__)
# async_setup # async_setup
# --------------------------- # ---------------------------
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up configured Mikrotik Controller.""" """Set up configured Mikrotik Controller."""
hass.data[DOMAIN] = {} hass.data[DOMAIN] = {}
hass.data[DOMAIN][DATA_CLIENT] = {} hass.data[DOMAIN][DATA_CLIENT] = {}
return True return True
# --------------------------- # ---------------------------
# async_setup_entry # async_setup_entry
# --------------------------- # ---------------------------
async def async_setup_entry(hass, config_entry): async def async_setup_entry(hass, config_entry):
"""Set up Mikrotik Router as config entry.""" """Set up Mikrotik Router as config entry."""
name = config_entry.data[CONF_NAME] name = config_entry.data[CONF_NAME]
host = config_entry.data[CONF_HOST] host = config_entry.data[CONF_HOST]
port = config_entry.data[CONF_PORT] port = config_entry.data[CONF_PORT]
username = config_entry.data[CONF_USERNAME] username = config_entry.data[CONF_USERNAME]
password = config_entry.data[CONF_PASSWORD] password = config_entry.data[CONF_PASSWORD]
use_ssl = config_entry.data[CONF_SSL] use_ssl = config_entry.data[CONF_SSL]
mikrotik_controller = MikrotikControllerData(hass, config_entry, name, host, port, username, password, use_ssl) mikrotik_controller = MikrotikControllerData(hass, config_entry, name, host, port, username, password, use_ssl)
await mikrotik_controller.hwinfo_update() await mikrotik_controller.hwinfo_update()
await mikrotik_controller.async_update() await mikrotik_controller.async_update()
if not mikrotik_controller.data: if not mikrotik_controller.data:
raise ConfigEntryNotReady() raise ConfigEntryNotReady()
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = mikrotik_controller hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = mikrotik_controller
# hass.async_create_task( # hass.async_create_task(
# hass.config_entries.async_forward_entry_setup(config_entry, "sensor") # hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
# ) # )
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, "device_tracker") hass.config_entries.async_forward_entry_setup(config_entry, "device_tracker")
) )
device_registry = await hass.helpers.device_registry.async_get_registry() device_registry = await hass.helpers.device_registry.async_get_registry()
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id, config_entry_id=config_entry.entry_id,
manufacturer=mikrotik_controller.data['resource']['platform'], manufacturer=mikrotik_controller.data['resource']['platform'],
model=mikrotik_controller.data['routerboard']['model'], model=mikrotik_controller.data['routerboard']['model'],
name=mikrotik_controller.data['routerboard']['model'], name=mikrotik_controller.data['routerboard']['model'],
sw_version=mikrotik_controller.data['resource']['version'], sw_version=mikrotik_controller.data['resource']['version'],
) )
return True return True
# --------------------------- # ---------------------------
# async_unload_entry # async_unload_entry
# --------------------------- # ---------------------------
async def async_unload_entry(hass, config_entry): async def async_unload_entry(hass, config_entry):
"""Unload a config entry.""" """Unload a config entry."""
mikrotik_controller = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] mikrotik_controller = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
await hass.config_entries.async_forward_entry_unload(config_entry, "device_tracker") await hass.config_entries.async_forward_entry_unload(config_entry, "device_tracker")
await mikrotik_controller.async_reset() await mikrotik_controller.async_reset()
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
return True return True

View file

@ -5,20 +5,20 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_NAME,
CONF_HOST, CONF_HOST,
CONF_PORT, CONF_PORT,
CONF_USERNAME, CONF_USERNAME,
CONF_PASSWORD, CONF_PASSWORD,
CONF_SSL, CONF_SSL,
) )
from .const import ( from .const import (
DOMAIN, DOMAIN,
CONF_TRACK_ARP, CONF_TRACK_ARP,
DEFAULT_TRACK_ARP, DEFAULT_TRACK_ARP,
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
) )
from .mikrotikapi import MikrotikAPI from .mikrotikapi import MikrotikAPI
@ -30,110 +30,110 @@ _LOGGER = logging.getLogger(__name__)
# --------------------------- # ---------------------------
@callback @callback
def configured_instances(hass): def configured_instances(hass):
"""Return a set of configured instances.""" """Return a set of configured instances."""
return set( return set(
entry.data[CONF_NAME] for entry in hass.config_entries.async_entries(DOMAIN) entry.data[CONF_NAME] for entry in hass.config_entries.async_entries(DOMAIN)
) )
# --------------------------- # ---------------------------
# MikrotikControllerConfigFlow # MikrotikControllerConfigFlow
# --------------------------- # ---------------------------
class MikrotikControllerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class MikrotikControllerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self): def __init__(self):
"""Initialize.""" """Initialize."""
return return
@staticmethod @staticmethod
@callback @callback
def async_get_options_flow(config_entry): def async_get_options_flow(config_entry):
"""Get the options flow for this handler.""" """Get the options flow for this handler."""
return MikrotikControllerOptionsFlowHandler(config_entry) return MikrotikControllerOptionsFlowHandler(config_entry)
async def async_step_import(self, user_input=None): async def async_step_import(self, user_input=None):
"""Occurs when a previously entry setup fails and is re-initiated.""" """Occurs when a previously entry setup fails and is re-initiated."""
return await self.async_step_user(user_input) return await self.async_step_user(user_input)
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user.""" """Handle a flow initialized by the user."""
errors = {} errors = {}
if user_input is not None: if user_input is not None:
# Check if instance with this name already exists # Check if instance with this name already exists
if user_input[CONF_NAME] in configured_instances(self.hass): if user_input[CONF_NAME] in configured_instances(self.hass):
errors["base"] = "name_exists" errors["base"] = "name_exists"
# Test connection # Test connection
api = MikrotikAPI(host=user_input["host"], username=user_input["username"], password=user_input["password"], port=user_input["port"], use_ssl=user_input["ssl"]) api = MikrotikAPI(host=user_input["host"], username=user_input["username"], password=user_input["password"], port=user_input["port"], use_ssl=user_input["ssl"])
if not api.connect(): if not api.connect():
errors[CONF_HOST] = api.error errors[CONF_HOST] = api.error
# Save instance # Save instance
if not errors: if not errors:
return self.async_create_entry( return self.async_create_entry(
title=user_input[CONF_NAME], title=user_input[CONF_NAME],
data=user_input data=user_input
) )
return self._show_config_form(host=user_input["host"], username=user_input["username"], password=user_input["password"], port=user_input["port"], name=user_input["name"], use_ssl=user_input["ssl"], errors=errors) return self._show_config_form(host=user_input["host"], username=user_input["username"], password=user_input["password"], port=user_input["port"], name=user_input["name"], use_ssl=user_input["ssl"], errors=errors)
return self._show_config_form(errors=errors) return self._show_config_form(errors=errors)
# --------------------------- # ---------------------------
# _show_config_form # _show_config_form
# --------------------------- # ---------------------------
def _show_config_form(self, host='10.0.0.1', username='admin', password='admin', port=0, name='Mikrotik', use_ssl=False, errors=None): def _show_config_form(self, host='10.0.0.1', username='admin', password='admin', port=0, name='Mikrotik', use_ssl=False, errors=None):
"""Show the configuration form to edit data.""" """Show the configuration form to edit data."""
return self.async_show_form( return self.async_show_form(
step_id='user', step_id='user',
data_schema=vol.Schema({ data_schema=vol.Schema({
vol.Required(CONF_HOST, default=host): str, vol.Required(CONF_HOST, default=host): str,
vol.Required(CONF_USERNAME, default=username): str, vol.Required(CONF_USERNAME, default=username): str,
vol.Required(CONF_PASSWORD, default=password): str, vol.Required(CONF_PASSWORD, default=password): str,
vol.Optional(CONF_PORT, default=port): int, vol.Optional(CONF_PORT, default=port): int,
vol.Optional(CONF_NAME, default=name): str, vol.Optional(CONF_NAME, default=name): str,
vol.Optional(CONF_SSL, default=use_ssl): bool, vol.Optional(CONF_SSL, default=use_ssl): bool,
}), }),
errors=errors, errors=errors,
) )
# --------------------------- # ---------------------------
# MikrotikControllerOptionsFlowHandler # MikrotikControllerOptionsFlowHandler
# --------------------------- # ---------------------------
class MikrotikControllerOptionsFlowHandler(config_entries.OptionsFlow): class MikrotikControllerOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle options.""" """Handle options."""
def __init__(self, config_entry): def __init__(self, config_entry):
"""Initialize options flow.""" """Initialize options flow."""
self.config_entry = config_entry self.config_entry = config_entry
self.options = dict(config_entry.options) self.options = dict(config_entry.options)
async def async_step_init(self, user_input=None): async def async_step_init(self, user_input=None):
"""Manage the options.""" """Manage the options."""
return await self.async_step_device_tracker(user_input) return await self.async_step_device_tracker(user_input)
async def async_step_device_tracker(self, user_input=None): async def async_step_device_tracker(self, user_input=None):
"""Manage the device tracker options.""" """Manage the device tracker options."""
if user_input is not None: if user_input is not None:
self.options.update(user_input) self.options.update(user_input)
return self.async_create_entry(title="", data=self.options) return self.async_create_entry(title="", data=self.options)
return self.async_show_form( return self.async_show_form(
step_id="device_tracker", step_id="device_tracker",
data_schema=vol.Schema( data_schema=vol.Schema(
{ {
vol.Optional( vol.Optional(
CONF_TRACK_ARP, CONF_TRACK_ARP,
default=self.config_entry.options.get( default=self.config_entry.options.get(
CONF_TRACK_ARP, DEFAULT_TRACK_ARP CONF_TRACK_ARP, DEFAULT_TRACK_ARP
), ),
): bool, ): bool,
vol.Optional( vol.Optional(
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
default=self.config_entry.options.get( default=self.config_entry.options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
), ),
): int, ): int,
} }
), ),
) )

View file

@ -7,31 +7,31 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.device_tracker.config_entry import ScannerEntity from homeassistant.components.device_tracker.config_entry import ScannerEntity
from homeassistant.components.device_tracker.const import SOURCE_TYPE_ROUTER from homeassistant.components.device_tracker.const import SOURCE_TYPE_ROUTER
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_NAME,
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
) )
from .const import ( from .const import (
DOMAIN, DOMAIN,
DATA_CLIENT, DATA_CLIENT,
ATTRIBUTION, ATTRIBUTION,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEVICE_ATTRIBUTES = [ DEVICE_ATTRIBUTES = [
"running", "running",
"enabled", "enabled",
"comment", "comment",
"client-ip-address", "client-ip-address",
"client-mac-address", "client-mac-address",
"port-mac-address", "port-mac-address",
"last-link-down-time", "last-link-down-time",
"last-link-up-time", "last-link-up-time",
"link-downs", "link-downs",
"actual-mtu", "actual-mtu",
"type", "type",
"name", "name",
"default-name", "default-name",
] ]
@ -39,22 +39,22 @@ DEVICE_ATTRIBUTES = [
# async_setup_entry # async_setup_entry
# --------------------------- # ---------------------------
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up device tracker for Mikrotik Router component.""" """Set up device tracker for Mikrotik Router component."""
name = config_entry.data[CONF_NAME] name = config_entry.data[CONF_NAME]
mikrotik_controller = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] mikrotik_controller = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
tracked = {} tracked = {}
@callback @callback
def update_controller(): def update_controller():
"""Update the values of the controller.""" """Update the values of the controller."""
update_items(name, mikrotik_controller, async_add_entities, tracked) update_items(name, mikrotik_controller, async_add_entities, tracked)
mikrotik_controller.listeners.append( mikrotik_controller.listeners.append(
async_dispatcher_connect(hass, mikrotik_controller.signal_update, update_controller) async_dispatcher_connect(hass, mikrotik_controller.signal_update, update_controller)
) )
update_controller() update_controller()
return return
# --------------------------- # ---------------------------
@ -62,111 +62,111 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
# --------------------------- # ---------------------------
@callback @callback
def update_items(name, mikrotik_controller, async_add_entities, tracked): def update_items(name, mikrotik_controller, async_add_entities, tracked):
"""Update tracked device state from the controller.""" """Update tracked device state from the controller."""
new_tracked = [] new_tracked = []
for uid in mikrotik_controller.data['interface']: for uid in mikrotik_controller.data['interface']:
if mikrotik_controller.data['interface'][uid]['type'] == "ether": if mikrotik_controller.data['interface'][uid]['type'] == "ether":
item_id = name + "-" + mikrotik_controller.data['interface'][uid]['default-name'] item_id = name + "-" + mikrotik_controller.data['interface'][uid]['default-name']
if item_id in tracked: if item_id in tracked:
if tracked[item_id].enabled: if tracked[item_id].enabled:
tracked[item_id].async_schedule_update_ha_state() tracked[item_id].async_schedule_update_ha_state()
continue continue
tracked[item_id] = MikrotikControllerPortDeviceTracker(name, uid, mikrotik_controller) tracked[item_id] = MikrotikControllerPortDeviceTracker(name, uid, mikrotik_controller)
new_tracked.append(tracked[item_id]) new_tracked.append(tracked[item_id])
if new_tracked: if new_tracked:
async_add_entities(new_tracked) async_add_entities(new_tracked)
return return
# --------------------------- # ---------------------------
# MikrotikControllerPortDeviceTracker # MikrotikControllerPortDeviceTracker
# --------------------------- # ---------------------------
class MikrotikControllerPortDeviceTracker(ScannerEntity): class MikrotikControllerPortDeviceTracker(ScannerEntity):
"""Representation of a network port.""" """Representation of a network port."""
def __init__(self, name, uid, mikrotik_controller): def __init__(self, name, uid, mikrotik_controller):
"""Set up tracked port.""" """Set up tracked port."""
self._name = name self._name = name
self._uid = uid self._uid = uid
self.mikrotik_controller = mikrotik_controller self.mikrotik_controller = mikrotik_controller
self._attrs = { self._attrs = {
ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_ATTRIBUTION: ATTRIBUTION,
} }
@property @property
def entity_registry_enabled_default(self): def entity_registry_enabled_default(self):
"""Return if the entity should be enabled when first added to the entity registry.""" """Return if the entity should be enabled when first added to the entity registry."""
return True return True
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Port entity created.""" """Port entity created."""
_LOGGER.debug("New port tracker %s (%s)", self._name, self.mikrotik_controller.data['interface'][self._uid]['port-mac-address']) _LOGGER.debug("New port tracker %s (%s)", self._name, self.mikrotik_controller.data['interface'][self._uid]['port-mac-address'])
return return
async def async_update(self): async def async_update(self):
"""Synchronize state with controller.""" """Synchronize state with controller."""
# await self.mikrotik_controller.async_update() # await self.mikrotik_controller.async_update()
return return
@property @property
def is_connected(self): def is_connected(self):
"""Return true if the port is connected to the network.""" """Return true if the port is connected to the network."""
return self.mikrotik_controller.data['interface'][self._uid]['running'] return self.mikrotik_controller.data['interface'][self._uid]['running']
@property @property
def source_type(self): def source_type(self):
"""Return the source type of the port.""" """Return the source type of the port."""
return SOURCE_TYPE_ROUTER return SOURCE_TYPE_ROUTER
@property @property
def name(self) -> str: def name(self) -> str:
"""Return the name of the port.""" """Return the name of the port."""
return self.mikrotik_controller.data['interface'][self._uid]['default-name'] return self.mikrotik_controller.data['interface'][self._uid]['default-name']
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
"""Return a unique identifier for this port.""" """Return a unique identifier for this port."""
return f"{self._name.lower()}-{self.mikrotik_controller.data['interface'][self._uid]['port-mac-address']}" return f"{self._name.lower()}-{self.mikrotik_controller.data['interface'][self._uid]['port-mac-address']}"
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return if controller is available.""" """Return if controller is available."""
return self.mikrotik_controller.connected() return self.mikrotik_controller.connected()
@property @property
def icon(self): def icon(self):
"""Return the icon.""" """Return the icon."""
if not self.mikrotik_controller.data['interface'][self._uid]['enabled']: if not self.mikrotik_controller.data['interface'][self._uid]['enabled']:
return 'mdi:lan-disconnect' return 'mdi:lan-disconnect'
if self.mikrotik_controller.data['interface'][self._uid]['running']: if self.mikrotik_controller.data['interface'][self._uid]['running']:
return 'mdi:lan-connect' return 'mdi:lan-connect'
else: else:
return 'mdi:lan-pending' return 'mdi:lan-pending'
@property @property
def device_info(self): def device_info(self):
"""Return a port description for device registry.""" """Return a port description for device registry."""
info = { info = {
"connections": {(CONNECTION_NETWORK_MAC, self.mikrotik_controller.data['interface'][self._uid]['port-mac-address'])}, "connections": {(CONNECTION_NETWORK_MAC, self.mikrotik_controller.data['interface'][self._uid]['port-mac-address'])},
"manufacturer": self.mikrotik_controller.data['resource']['platform'], "manufacturer": self.mikrotik_controller.data['resource']['platform'],
"model": "Port", "model": "Port",
"name": self.mikrotik_controller.data['interface'][self._uid]['default-name'], "name": self.mikrotik_controller.data['interface'][self._uid]['default-name'],
} }
return info return info
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the port state attributes.""" """Return the port state attributes."""
attributes = self._attrs attributes = self._attrs
for variable in DEVICE_ATTRIBUTES: for variable in DEVICE_ATTRIBUTES:
if variable in self.mikrotik_controller.data['interface'][self._uid]: if variable in self.mikrotik_controller.data['interface'][self._uid]:
attributes[variable] = self.mikrotik_controller.data['interface'][self._uid][variable] attributes[variable] = self.mikrotik_controller.data['interface'][self._uid][variable]
return attributes return attributes

View file

@ -1,13 +1,13 @@
{ {
"domain": "mikrotik_router", "domain": "mikrotik_router",
"name": "Mikrotik Router", "name": "Mikrotik Router",
"config_flow": true, "config_flow": true,
"documentation": "https://github.com/tomaae/homeassistant-mikrotik_router", "documentation": "https://github.com/tomaae/homeassistant-mikrotik_router",
"dependencies": [], "dependencies": [],
"requirements": [ "requirements": [
"librouteros==3.0.0" "librouteros==3.0.0"
], ],
"codeowners": [ "codeowners": [
"@tomaae" "@tomaae"
] ]
} }

View file

@ -7,11 +7,11 @@ from homeassistant.helpers.event import async_track_time_interval
# from homeassistant.util import Throttle # from homeassistant.util import Throttle
from .const import ( from .const import (
DOMAIN, DOMAIN,
CONF_TRACK_ARP, CONF_TRACK_ARP,
DEFAULT_TRACK_ARP, DEFAULT_TRACK_ARP,
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
) )
from .mikrotikapi import MikrotikAPI from .mikrotikapi import MikrotikAPI
@ -24,257 +24,257 @@ _LOGGER = logging.getLogger(__name__)
# MikrotikControllerData # MikrotikControllerData
# --------------------------- # ---------------------------
class MikrotikControllerData(): class MikrotikControllerData():
def __init__(self, hass, config_entry, name, host, port, username, password, use_ssl): def __init__(self, hass, config_entry, name, host, port, username, password, use_ssl):
"""Initialize.""" """Initialize."""
self.name = name self.name = name
self.hass = hass self.hass = hass
self.config_entry = config_entry self.config_entry = config_entry
self.data = {} self.data = {}
self.data['routerboard'] = {} self.data['routerboard'] = {}
self.data['resource'] = {} self.data['resource'] = {}
self.data['interface'] = {} self.data['interface'] = {}
self.data['arp'] = {} self.data['arp'] = {}
self.listeners = [] self.listeners = []
self.api = MikrotikAPI(host, username, password, port, use_ssl) self.api = MikrotikAPI(host, username, password, port, use_ssl)
if not self.api.connect(): if not self.api.connect():
self.api = None self.api = None
async_track_time_interval(self.hass, self.force_update, self.option_scan_interval) async_track_time_interval(self.hass, self.force_update, self.option_scan_interval)
return return
async def force_update(self, now=None): async def force_update(self, now=None):
"""Periodic update.""" """Periodic update."""
await self.async_update() await self.async_update()
return return
# --------------------------- # ---------------------------
# option_track_arp # option_track_arp
# --------------------------- # ---------------------------
@property @property
def option_track_arp(self): def option_track_arp(self):
"""Config entry option to not track ARP.""" """Config entry option to not track ARP."""
return self.config_entry.options.get(CONF_TRACK_ARP, DEFAULT_TRACK_ARP) return self.config_entry.options.get(CONF_TRACK_ARP, DEFAULT_TRACK_ARP)
# --------------------------- # ---------------------------
# option_scan_interval # option_scan_interval
# --------------------------- # ---------------------------
@property @property
def option_scan_interval(self): def option_scan_interval(self):
"""Config entry option scan interval.""" """Config entry option scan interval."""
scan_interval = self.config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) scan_interval = self.config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
return timedelta(seconds=scan_interval) return timedelta(seconds=scan_interval)
# --------------------------- # ---------------------------
# signal_update # signal_update
# --------------------------- # ---------------------------
@property @property
def signal_update(self): def signal_update(self):
"""Event specific per UniFi entry to signal new data.""" """Event specific per UniFi entry to signal new data."""
return f"{DOMAIN}-update-{self.name}" return f"{DOMAIN}-update-{self.name}"
# --------------------------- # ---------------------------
# connected # connected
# --------------------------- # ---------------------------
def connected(self): def connected(self):
"""Return connected boolean.""" """Return connected boolean."""
return self.api.connected() return self.api.connected()
# --------------------------- # ---------------------------
# hwinfo_update # hwinfo_update
# --------------------------- # ---------------------------
async def hwinfo_update(self): async def hwinfo_update(self):
"""Update Mikrotik hardware info.""" """Update Mikrotik hardware info."""
self.get_system_routerboard() self.get_system_routerboard()
self.get_system_resource() self.get_system_resource()
return return
# --------------------------- # ---------------------------
# async_update # async_update
# --------------------------- # ---------------------------
# @Throttle(DEFAULT_SCAN_INTERVAL) # @Throttle(DEFAULT_SCAN_INTERVAL)
async def async_update(self): async def async_update(self):
"""Update Mikrotik Controller data.""" """Update Mikrotik Controller data."""
self.get_interfaces() self.get_interfaces()
self.get_arp() self.get_arp()
async_dispatcher_send(self.hass, self.signal_update) async_dispatcher_send(self.hass, self.signal_update)
return return
# --------------------------- # ---------------------------
# async_reset # async_reset
# --------------------------- # ---------------------------
async def async_reset(self): async def async_reset(self):
"""Reset this controller to default state.""" """Reset this controller to default state."""
for unsub_dispatcher in self.listeners: for unsub_dispatcher in self.listeners:
unsub_dispatcher() unsub_dispatcher()
self.listeners = [] self.listeners = []
return True return True
# --------------------------- # ---------------------------
# get_interfaces # get_interfaces
# --------------------------- # ---------------------------
def get_interfaces(self): def get_interfaces(self):
ifaces = self.api.path("/interface") ifaces = self.api.path("/interface")
for iface in ifaces: for iface in ifaces:
if 'default-name' not in iface: if 'default-name' not in iface:
continue continue
uid = iface['default-name'] uid = iface['default-name']
if uid not in self.data['interface']: if uid not in self.data['interface']:
self.data['interface'][uid] = {} self.data['interface'][uid] = {}
self.data['interface'][uid]['default-name'] = iface['default-name'] self.data['interface'][uid]['default-name'] = iface['default-name']
self.data['interface'][uid]['name'] = iface['name'] if 'name' in iface else iface['default-name'] self.data['interface'][uid]['name'] = iface['name'] if 'name' in iface else iface['default-name']
self.data['interface'][uid]['type'] = iface['type'] if 'type' in iface else "unknown" self.data['interface'][uid]['type'] = iface['type'] if 'type' in iface else "unknown"
self.data['interface'][uid]['running'] = True if iface['running'] else False self.data['interface'][uid]['running'] = True if iface['running'] else False
self.data['interface'][uid]['enabled'] = True if not iface['disabled'] else False self.data['interface'][uid]['enabled'] = True if not iface['disabled'] else False
self.data['interface'][uid]['port-mac-address'] = iface['mac-address'] if 'mac-address' in iface else "" self.data['interface'][uid]['port-mac-address'] = iface['mac-address'] if 'mac-address' in iface else ""
self.data['interface'][uid]['comment'] = iface['comment'] if 'comment' in iface else "" self.data['interface'][uid]['comment'] = iface['comment'] if 'comment' in iface else ""
self.data['interface'][uid]['last-link-down-time'] = iface['last-link-down-time'] if 'last-link-down-time' in iface else "" self.data['interface'][uid]['last-link-down-time'] = iface['last-link-down-time'] if 'last-link-down-time' in iface else ""
self.data['interface'][uid]['last-link-up-time'] = iface['last-link-up-time'] if 'last-link-up-time' in iface else "" self.data['interface'][uid]['last-link-up-time'] = iface['last-link-up-time'] if 'last-link-up-time' in iface else ""
self.data['interface'][uid]['link-downs'] = iface['link-downs'] if 'link-downs' in iface else "" self.data['interface'][uid]['link-downs'] = iface['link-downs'] if 'link-downs' in iface else ""
self.data['interface'][uid]['rx-byte'] = iface['rx-byte'] if 'rx-byte' in iface else "" self.data['interface'][uid]['rx-byte'] = iface['rx-byte'] if 'rx-byte' in iface else ""
self.data['interface'][uid]['tx-byte'] = iface['tx-byte'] if 'tx-byte' in iface else "" self.data['interface'][uid]['tx-byte'] = iface['tx-byte'] if 'tx-byte' in iface else ""
self.data['interface'][uid]['tx-queue-drop'] = iface['tx-queue-drop'] if 'tx-queue-drop' in iface else "" self.data['interface'][uid]['tx-queue-drop'] = iface['tx-queue-drop'] if 'tx-queue-drop' in iface else ""
self.data['interface'][uid]['actual-mtu'] = iface['actual-mtu'] if 'actual-mtu' in iface else "" self.data['interface'][uid]['actual-mtu'] = iface['actual-mtu'] if 'actual-mtu' in iface else ""
if 'client-ip-address' not in self.data['interface'][uid]: if 'client-ip-address' not in self.data['interface'][uid]:
self.data['interface'][uid]['client-ip-address'] = "" self.data['interface'][uid]['client-ip-address'] = ""
if 'client-mac-address' not in self.data['interface'][uid]: if 'client-mac-address' not in self.data['interface'][uid]:
self.data['interface'][uid]['client-mac-address'] = "" self.data['interface'][uid]['client-mac-address'] = ""
return return
# --------------------------- # ---------------------------
# get_arp # get_arp
# --------------------------- # ---------------------------
def get_arp(self): def get_arp(self):
self.data['arp'] = {} self.data['arp'] = {}
if not self.option_track_arp: if not self.option_track_arp:
for uid in self.data['interface']: for uid in self.data['interface']:
self.data['interface'][uid]['client-ip-address'] = "disabled" self.data['interface'][uid]['client-ip-address'] = "disabled"
self.data['interface'][uid]['client-mac-address'] = "disabled" self.data['interface'][uid]['client-mac-address'] = "disabled"
return False return False
mac2ip = {} mac2ip = {}
bridge_used = False bridge_used = False
data = self.api.path("/ip/arp") data = self.api.path("/ip/arp")
for entry in data: for entry in data:
# Ignore invalid entries # Ignore invalid entries
if entry['invalid']: if entry['invalid']:
continue continue
# Do not add ARP detected on bridge # Do not add ARP detected on bridge
if entry['interface'] == "bridge": if entry['interface'] == "bridge":
bridge_used = True bridge_used = True
# Build address table on bridge # Build address table on bridge
if 'mac-address' in entry and 'address' in entry: if 'mac-address' in entry and 'address' in entry:
mac2ip[entry['mac-address']] = entry['address'] mac2ip[entry['mac-address']] = entry['address']
continue continue
# Get iface default-name from custom name # Get iface default-name from custom name
uid = None uid = None
for ifacename in self.data['interface']: for ifacename in self.data['interface']:
if self.data['interface'][ifacename]['name'] == entry['interface']: if self.data['interface'][ifacename]['name'] == entry['interface']:
uid = self.data['interface'][ifacename]['default-name'] uid = self.data['interface'][ifacename]['default-name']
break break
if not uid: if not uid:
continue continue
# Create uid arp dict # Create uid arp dict
if uid not in self.data['arp']: if uid not in self.data['arp']:
self.data['arp'][uid] = {} self.data['arp'][uid] = {}
# Add data # Add data
self.data['arp'][uid]['interface'] = uid self.data['arp'][uid]['interface'] = uid
if 'mac-address' in self.data['arp'][uid]: if 'mac-address' in self.data['arp'][uid]:
self.data['arp'][uid]['mac-address'] = "multiple" self.data['arp'][uid]['mac-address'] = "multiple"
else: else:
self.data['arp'][uid]['mac-address'] = entry['mac-address'] if 'mac-address' in entry else "" self.data['arp'][uid]['mac-address'] = entry['mac-address'] if 'mac-address' in entry else ""
if 'address' in self.data['arp'][uid]: if 'address' in self.data['arp'][uid]:
self.data['arp'][uid]['address'] = "multiple" self.data['arp'][uid]['address'] = "multiple"
else: else:
self.data['arp'][uid]['address'] = entry['address'] if 'address' in entry else "" self.data['arp'][uid]['address'] = entry['address'] if 'address' in entry else ""
if bridge_used: if bridge_used:
self.update_bridge_hosts(mac2ip) self.update_bridge_hosts(mac2ip)
# Map ARP to ifaces # Map ARP to ifaces
for uid in self.data['interface']: for uid in self.data['interface']:
self.data['interface'][uid]['client-ip-address'] = self.data['arp'][uid]['address'] if uid in self.data['arp'] and 'address' in self.data['arp'][uid] else "" self.data['interface'][uid]['client-ip-address'] = self.data['arp'][uid]['address'] if uid in self.data['arp'] and 'address' in self.data['arp'][uid] else ""
self.data['interface'][uid]['client-mac-address'] = self.data['arp'][uid]['mac-address'] if uid in self.data['arp'] and 'mac-address' in self.data['arp'][uid] else "" self.data['interface'][uid]['client-mac-address'] = self.data['arp'][uid]['mac-address'] if uid in self.data['arp'] and 'mac-address' in self.data['arp'][uid] else ""
return True return True
# --------------------------- # ---------------------------
# update_bridge_hosts # update_bridge_hosts
# --------------------------- # ---------------------------
def update_bridge_hosts(self, mac2ip): def update_bridge_hosts(self, mac2ip):
data = self.api.path("/interface/bridge/host") data = self.api.path("/interface/bridge/host")
for entry in data: for entry in data:
# Ignore port MAC # Ignore port MAC
if entry['local']: if entry['local']:
continue continue
# Get iface default-name from custom name # Get iface default-name from custom name
uid = None uid = None
for ifacename in self.data['interface']: for ifacename in self.data['interface']:
if self.data['interface'][ifacename]['name'] == entry['interface']: if self.data['interface'][ifacename]['name'] == entry['interface']:
uid = self.data['interface'][ifacename]['default-name'] uid = self.data['interface'][ifacename]['default-name']
break break
if not uid: if not uid:
continue continue
# Create uid arp dict # Create uid arp dict
if uid not in self.data['arp']: if uid not in self.data['arp']:
self.data['arp'][uid] = {} self.data['arp'][uid] = {}
# Add data # Add data
self.data['arp'][uid]['interface'] = uid self.data['arp'][uid]['interface'] = uid
if 'mac-address' in self.data['arp'][uid]: if 'mac-address' in self.data['arp'][uid]:
self.data['arp'][uid]['mac-address'] = "multiple" self.data['arp'][uid]['mac-address'] = "multiple"
self.data['arp'][uid]['address'] = "multiple" self.data['arp'][uid]['address'] = "multiple"
else: else:
self.data['arp'][uid]['mac-address'] = entry['mac-address'] if 'mac-address' in entry else "" self.data['arp'][uid]['mac-address'] = entry['mac-address'] if 'mac-address' in entry else ""
self.data['arp'][uid]['address'] = "" self.data['arp'][uid]['address'] = ""
if self.data['arp'][uid]['address'] == "" and self.data['arp'][uid]['mac-address'] in mac2ip: if self.data['arp'][uid]['address'] == "" and self.data['arp'][uid]['mac-address'] in mac2ip:
self.data['arp'][uid]['address'] = mac2ip[self.data['arp'][uid]['mac-address']] self.data['arp'][uid]['address'] = mac2ip[self.data['arp'][uid]['mac-address']]
return return
# --------------------------- # ---------------------------
# get_system_routerboard # get_system_routerboard
# --------------------------- # ---------------------------
def get_system_routerboard(self): def get_system_routerboard(self):
data = self.api.path("/system/routerboard") data = self.api.path("/system/routerboard")
for entry in data: for entry in data:
self.data['routerboard']['routerboard'] = True if entry['routerboard'] else False self.data['routerboard']['routerboard'] = True if entry['routerboard'] else False
self.data['routerboard']['model'] = entry['model'] if 'model' in entry else "unknown" self.data['routerboard']['model'] = entry['model'] if 'model' in entry else "unknown"
self.data['routerboard']['serial-number'] = entry['serial-number'] if 'serial-number' in entry else "unknown" self.data['routerboard']['serial-number'] = entry['serial-number'] if 'serial-number' in entry else "unknown"
self.data['routerboard']['firmware'] = entry['current-firmware'] if 'current-firmware' in entry else "unknown" self.data['routerboard']['firmware'] = entry['current-firmware'] if 'current-firmware' in entry else "unknown"
return return
# --------------------------- # ---------------------------
# get_system_resource # get_system_resource
# --------------------------- # ---------------------------
def get_system_resource(self): def get_system_resource(self):
data = self.api.path("/system/resource") data = self.api.path("/system/resource")
for entry in data: for entry in data:
self.data['resource']['platform'] = entry['platform'] if 'platform' in entry else "unknown" self.data['resource']['platform'] = entry['platform'] if 'platform' in entry else "unknown"
self.data['resource']['board-name'] = entry['board-name'] if 'board-name' in entry else "unknown" self.data['resource']['board-name'] = entry['board-name'] if 'board-name' in entry else "unknown"
self.data['resource']['version'] = entry['version'] if 'version' in entry else "unknown" self.data['resource']['version'] = entry['version'] if 'version' in entry else "unknown"
return return

View file

@ -10,109 +10,109 @@ _LOGGER = logging.getLogger(__name__)
# MikrotikAPI # MikrotikAPI
# --------------------------- # ---------------------------
class MikrotikAPI: class MikrotikAPI:
"""Handle all communication with the Mikrotik API.""" """Handle all communication with the Mikrotik API."""
def __init__(self, host, username, password, port=0, use_ssl=True, login_method="plain", encoding="utf-8"): def __init__(self, host, username, password, port=0, use_ssl=True, login_method="plain", encoding="utf-8"):
"""Initialize the Mikrotik Client.""" """Initialize the Mikrotik Client."""
self._host = host self._host = host
self._use_ssl = use_ssl self._use_ssl = use_ssl
self._port = port self._port = port
self._username = username self._username = username
self._password = password self._password = password
self._login_method = login_method self._login_method = login_method
self._encoding = encoding self._encoding = encoding
self._ssl_wrapper = None self._ssl_wrapper = None
self._connection = None self._connection = None
self._connected = False self._connected = False
self.error = "" self.error = ""
# Default ports # Default ports
if not self._port: if not self._port:
self._port = 8729 if self._use_ssl else 8728 self._port = 8729 if self._use_ssl else 8728
# --------------------------- # ---------------------------
# connect # connect
# --------------------------- # ---------------------------
def connect(self): def connect(self):
"""Connect to Mikrotik device.""" """Connect to Mikrotik device."""
self.error = "" self.error = ""
self._connected = False self._connected = False
kwargs = { kwargs = {
"encoding": self._encoding, "encoding": self._encoding,
"login_methods": self._login_method, "login_methods": self._login_method,
"port": self._port, "port": self._port,
} }
if self._use_ssl: if self._use_ssl:
if self._ssl_wrapper is None: if self._ssl_wrapper is None:
ssl_context = ssl.create_default_context() ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE ssl_context.verify_mode = ssl.CERT_NONE
self._ssl_wrapper = ssl_context.wrap_socket self._ssl_wrapper = ssl_context.wrap_socket
kwargs["ssl_wrapper"] = self._ssl_wrapper kwargs["ssl_wrapper"] = self._ssl_wrapper
try: try:
self._connection = librouteros.connect(self._host, self._username, self._password, **kwargs) self._connection = librouteros.connect(self._host, self._username, self._password, **kwargs)
except ( except (
librouteros.exceptions.TrapError, librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError, librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ConnectionClosed, librouteros.exceptions.ConnectionClosed,
librouteros.exceptions.ProtocolError, librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError librouteros.exceptions.FatalError
) as api_error: ) as api_error:
_LOGGER.error("Mikrotik %s: %s", self._host, api_error) _LOGGER.error("Mikrotik %s: %s", self._host, api_error)
self.error_to_strings("%s" % api_error) self.error_to_strings("%s" % api_error)
self._connection = None self._connection = None
return False return False
else: else:
_LOGGER.info("Mikrotik Connected to %s", self._host) _LOGGER.info("Mikrotik Connected to %s", self._host)
self._connected = True self._connected = True
return self._connected return self._connected
# --------------------------- # ---------------------------
# error_to_strings # error_to_strings
# --------------------------- # ---------------------------
def error_to_strings(self, error): def error_to_strings(self, error):
self.error = "cannot_connect" self.error = "cannot_connect"
if error == "invalid user name or password (6)": if error == "invalid user name or password (6)":
self.error = "wrong_login" self.error = "wrong_login"
return return
# --------------------------- # ---------------------------
# connected # connected
# --------------------------- # ---------------------------
def connected(self): def connected(self):
"""Return connected boolean.""" """Return connected boolean."""
return self._connected return self._connected
# --------------------------- # ---------------------------
# path # path
# --------------------------- # ---------------------------
def path(self, path): def path(self, path):
"""Retrieve data from Mikrotik API.""" """Retrieve data from Mikrotik API."""
if not self._connected or not self._connection: if not self._connected or not self._connection:
if not self.connect(): if not self.connect():
return None return None
try: try:
response = self._connection.path(path) response = self._connection.path(path)
tuple(response) tuple(response)
except librouteros.exceptions.ConnectionClosed: except librouteros.exceptions.ConnectionClosed:
_LOGGER.error("Mikrotik %s connection closed", self._host) _LOGGER.error("Mikrotik %s connection closed", self._host)
self._connected = False self._connected = False
self._connection = None self._connection = None
return None return None
except ( except (
librouteros.exceptions.TrapError, librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError, librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ProtocolError, librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError librouteros.exceptions.FatalError
) as api_error: ) as api_error:
_LOGGER.error("Mikrotik %s connection error %s", self._host, api_error) _LOGGER.error("Mikrotik %s connection error %s", self._host, api_error)
return None return None
return response if response else None return response if response else None

View file

@ -1,38 +1,38 @@
{ {
"config": { "config": {
"title": "Mikrotik Router", "title": "Mikrotik Router",
"step": { "step": {
"user": { "user": {
"title": "Mikrotik Router", "title": "Mikrotik Router",
"description": "Set up Mikrotik Router integration.", "description": "Set up Mikrotik Router integration.",
"data": { "data": {
"name": "Name of the integration", "name": "Name of the integration",
"host": "Host", "host": "Host",
"port": "Port", "port": "Port",
"username": "Username", "username": "Username",
"password": "Password", "password": "Password",
"ssl": "Use SSL" "ssl": "Use SSL"
} }
} }
}, },
"error": { "error": {
"name_exists": "Name already exists.", "name_exists": "Name already exists.",
"cannot_connect": "Cannot connect to Mikrotik.", "cannot_connect": "Cannot connect to Mikrotik.",
"wrong_login": "Invalid user name or password.", "wrong_login": "Invalid user name or password.",
"routeros_api_missing": "Python module routeros_api not installed." "routeros_api_missing": "Python module routeros_api not installed."
} }
}, },
"options": { "options": {
"step": { "step": {
"init": { "init": {
"data": {} "data": {}
}, },
"device_tracker": { "device_tracker": {
"data": { "data": {
"scan_interval": "Scan interval (requires HA restart)", "scan_interval": "Scan interval (requires HA restart)",
"track_arp": "Show client MAC and IP on interfaces" "track_arp": "Show client MAC and IP on interfaces"
} }
} }
} }
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "Mikrotik Router", "name": "Mikrotik Router",
"homeassistant": "0.102.0", "homeassistant": "0.102.0",
"iot_class": "local_poll", "iot_class": "local_poll",
"domains": ["device_tracker"], "domains": ["device_tracker"],
"render_readme": true "render_readme": true
} }