Removed manual accounting enable/disable checkbox, now it dinamically creates entites depending on state in Mikrotik. Reverted config flow to version 1.

Reused ARP call in accounting hosts building.
This commit is contained in:
Ivan Pavlina 2020-04-07 14:50:26 +02:00
parent 39c31cb84a
commit 069897c32c
8 changed files with 50 additions and 70 deletions

View file

@ -13,7 +13,6 @@
"password": "Password", "password": "Password",
"ssl": "Use SSL", "ssl": "Use SSL",
"unit_of_measurement": "Unit of measurement", "unit_of_measurement": "Unit of measurement",
"track_accounting": "Track accounting"
} }
} }
}, },
@ -23,7 +22,6 @@
"ssl_handshake_failure": "SSL handshake failure.", "ssl_handshake_failure": "SSL handshake failure.",
"connection_timeout": "Mikrotik connection timeout.", "connection_timeout": "Mikrotik connection timeout.",
"wrong_login": "Invalid username or password.", "wrong_login": "Invalid username or password.",
"accounting_disabled": "Accounting disabled in Mikrotik, cannot track."
} }
}, },
"options": { "options": {

View file

@ -13,7 +13,6 @@
"password": "Пароль", "password": "Пароль",
"ssl": "Использовать SSL", "ssl": "Использовать SSL",
"unit_of_measurement": "Единицы измерения", "unit_of_measurement": "Единицы измерения",
"track_accounting": "Отслеживание учета"
} }
} }
}, },
@ -23,7 +22,6 @@
"ssl_handshake_failure": "Ошибка SSL-соединения", "ssl_handshake_failure": "Ошибка SSL-соединения",
"connection_timeout": "Таймаут подключения к Mikrotik.", "connection_timeout": "Таймаут подключения к Mikrotik.",
"wrong_login": "Неверные имя пользователя или пароль.", "wrong_login": "Неверные имя пользователя или пароль.",
"accounting_disabled": "Учетная запись отключена в Mikrotik, не может отслеживать."
} }
}, },
"options": { "options": {

View file

@ -17,7 +17,6 @@ from .const import (
DOMAIN, DOMAIN,
DATA_CLIENT, DATA_CLIENT,
DEFAULT_TRAFFIC_TYPE, DEFAULT_TRAFFIC_TYPE,
CONF_TRACK_ACCOUNTING,
) )
from .mikrotik_controller import MikrotikControllerData from .mikrotik_controller import MikrotikControllerData
@ -49,11 +48,10 @@ async def async_setup_entry(hass, config_entry):
traffic_type = config_entry.data[CONF_UNIT_OF_MEASUREMENT] traffic_type = config_entry.data[CONF_UNIT_OF_MEASUREMENT]
else: else:
traffic_type = DEFAULT_TRAFFIC_TYPE traffic_type = DEFAULT_TRAFFIC_TYPE
track_accounting = config_entry.data[CONF_TRACK_ACCOUNTING]
mikrotik_controller = MikrotikControllerData( mikrotik_controller = MikrotikControllerData(
hass, config_entry, name, host, port, username, password, use_ssl, hass, config_entry, name, host, port, username, password, use_ssl,
traffic_type, track_accounting traffic_type
) )
await mikrotik_controller.hwinfo_update() await mikrotik_controller.hwinfo_update()

View file

@ -27,7 +27,6 @@ from .const import (
DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
DEFAULT_TRAFFIC_TYPE, DEFAULT_TRAFFIC_TYPE,
TRAFFIC_TYPES, TRAFFIC_TYPES,
CONF_TRACK_ACCOUNTING,
) )
from .mikrotikapi import MikrotikAPI from .mikrotikapi import MikrotikAPI
@ -52,7 +51,7 @@ def configured_instances(hass):
class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN): class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
"""MikrotikControllerConfigFlow class""" """MikrotikControllerConfigFlow class"""
VERSION = 2 VERSION = 1
CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL
def __init__(self): def __init__(self):
@ -86,9 +85,6 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
) )
if not api.connect(): if not api.connect():
errors[CONF_HOST] = api.error errors[CONF_HOST] = api.error
else:
if user_input[CONF_TRACK_ACCOUNTING] and not api.is_accounting_enabled():
errors[CONF_HOST] = "accounting_disabled"
# Save instance # Save instance
if not errors: if not errors:
@ -103,7 +99,6 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
port=user_input["port"], port=user_input["port"],
name=user_input["name"], name=user_input["name"],
use_ssl=user_input["ssl"], use_ssl=user_input["ssl"],
track_accounting=user_input["track_accounting"],
errors=errors, errors=errors,
) )
@ -120,7 +115,6 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
port=0, port=0,
name="Mikrotik", name="Mikrotik",
use_ssl=False, use_ssl=False,
track_accounting=False,
errors=None, errors=None,
): ):
"""Show the configuration form to edit data.""" """Show the configuration form to edit data."""
@ -137,7 +131,6 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
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,
vol.Optional(CONF_TRACK_ACCOUNTING, default=track_accounting): bool,
} }
), ),
errors=errors, errors=errors,

View file

@ -16,5 +16,3 @@ DEFAULT_LOGIN_METHOD = "plain"
DEFAULT_TRAFFIC_TYPE = "Kbps" DEFAULT_TRAFFIC_TYPE = "Kbps"
TRAFFIC_TYPES = ["bps", "Kbps", "Mbps", "B/s", "KB/s", "MB/s"] TRAFFIC_TYPES = ["bps", "Kbps", "Mbps", "B/s", "KB/s", "MB/s"]
CONF_TRACK_ACCOUNTING = "track_accounting"

View file

@ -42,7 +42,6 @@ class MikrotikControllerData:
password, password,
use_ssl, use_ssl,
traffic_type, traffic_type,
track_accounting,
): ):
"""Initialize MikrotikController.""" """Initialize MikrotikController."""
self.name = name self.name = name
@ -50,7 +49,6 @@ class MikrotikControllerData:
self.host = host self.host = host
self.config_entry = config_entry self.config_entry = config_entry
self.traffic_type = traffic_type self.traffic_type = traffic_type
self.track_accounting = track_accounting
self.data = { self.data = {
"routerboard": {}, "routerboard": {},
@ -73,6 +71,7 @@ class MikrotikControllerData:
self.api = MikrotikAPI(host, username, password, port, use_ssl) self.api = MikrotikAPI(host, username, password, port, use_ssl)
self.raw_arp_entries = []
self.nat_removed = {} self.nat_removed = {}
async_track_time_interval( async_track_time_interval(
@ -81,10 +80,6 @@ class MikrotikControllerData:
async_track_time_interval( async_track_time_interval(
self.hass, self.force_fwupdate_check, timedelta(hours=1) self.hass, self.force_fwupdate_check, timedelta(hours=1)
) )
# if self.track_accounting:
# async_track_time_interval(
# self.hass, self.force_accounting_hosts_update, timedelta(minutes=15)
# )
def _get_traffic_type_and_div(self): def _get_traffic_type_and_div(self):
traffic_type = self.option_traffic_type traffic_type = self.option_traffic_type
@ -111,14 +106,6 @@ class MikrotikControllerData:
"""Trigger update by timer""" """Trigger update by timer"""
await self.async_update() await self.async_update()
# ---------------------------
# force_accounting_hosts_update
# ---------------------------
# @callback
# async def force_accounting_hosts_update(self, _now=None):
# """Trigger update by timer"""
# await self.async_accounting_hosts_update()
# --------------------------- # ---------------------------
# force_fwupdate_check # force_fwupdate_check
# --------------------------- # ---------------------------
@ -185,19 +172,6 @@ class MikrotikControllerData:
await self.hass.async_add_executor_job(self.get_system_resource) await self.hass.async_add_executor_job(self.get_system_resource)
self.lock.release() self.lock.release()
# ---------------------------
# async_accounting_hosts_update
# ---------------------------
# async def async_accounting_hosts_update(self):
# """Update Mikrotik accounting hosts"""
# try:
# await asyncio.wait_for(self.lock.acquire(), timeout=10)
# except:
# return
#
# await self.hass.async_add_executor_job(self.build_accounting_hosts)
# self.lock.release()
# --------------------------- # ---------------------------
# async_fwupdate_check # async_fwupdate_check
# --------------------------- # ---------------------------
@ -227,8 +201,7 @@ class MikrotikControllerData:
await self.hass.async_add_executor_job(self.get_script) await self.hass.async_add_executor_job(self.get_script)
await self.hass.async_add_executor_job(self.get_queue) await self.hass.async_add_executor_job(self.get_queue)
await self.hass.async_add_executor_job(self.get_dhcp) await self.hass.async_add_executor_job(self.get_dhcp)
if self.track_accounting: await self.hass.async_add_executor_job(self.get_accounting)
await self.hass.async_add_executor_job(self.get_accounting)
async_dispatcher_send(self.hass, self.signal_update) async_dispatcher_send(self.hass, self.signal_update)
self.lock.release() self.lock.release()
@ -371,6 +344,7 @@ class MikrotikControllerData:
def update_arp(self, mac2ip, bridge_used): def update_arp(self, mac2ip, bridge_used):
"""Get list of hosts in ARP for interface client data from Mikrotik""" """Get list of hosts in ARP for interface client data from Mikrotik"""
data = self.api.path("/ip/arp") data = self.api.path("/ip/arp")
self.raw_arp_entries = data
if not data: if not data:
return mac2ip, bridge_used return mac2ip, bridge_used
@ -763,10 +737,38 @@ class MikrotikControllerData:
def get_accounting(self): def get_accounting(self):
"""Get Accounting data from Mikrotik""" """Get Accounting data from Mikrotik"""
# Check if accounting and account-local-traffic is enabled
accounting_enabled, local_traffic_enabled = self.api.is_accounting_and_local_traffic_enabled()
if not accounting_enabled:
# If any hosts were created return counters to 0 so sensors wont get stuck on last value
for mac, vals in self.data["accounting"].items():
if 'wan-tx' in vals:
self.data["accounting"]['wan-tx'] = 0.0
if 'wan-rx' in vals:
self.data["accounting"]['wan-rx'] = 0.0
if 'lan-tx' in vals:
self.data["accounting"]['lan-tx'] = 0.0
if 'lan-rx' in vals:
self.data["accounting"]['lan-rx'] = 0.0
return
# Build missing hosts from already retrieved DHCP Server leases # Build missing hosts from already retrieved DHCP Server leases
for mac, vals in self.data["dhcp"].items(): for mac, vals in self.data["dhcp"].items():
if mac not in self.data["accounting"]: if mac not in self.data["accounting"]:
self.data["accounting"][mac] = vals self.data["accounting"][mac] = {
'address': vals['address'],
'mac-address': vals['mac-address'],
'host-name': vals['host-name'],
}
# Build missing hosts from already retrieved ARP list
for entry in self.raw_arp_entries:
if entry['mac-address'] not in self.data["accounting"]:
self.data["accounting"][entry['mac-address']] = {
'address': entry['address'],
'mac-address': entry['mac-address'],
}
# Build name for host # Build name for host
dns_data = parse_api( dns_data = parse_api(
@ -855,7 +857,7 @@ class MikrotikControllerData:
self.data['accounting'][mac]['wan-rx'] = round( self.data['accounting'][mac]['wan-rx'] = round(
tmp_accounting_values[addr]['wan-rx'] / time_diff * traffic_div, 2) tmp_accounting_values[addr]['wan-rx'] / time_diff * traffic_div, 2)
if self.api.is_accounting_local_traffic_enabled(): if local_traffic_enabled:
self.data['accounting'][mac]['lan-tx'] = round( self.data['accounting'][mac]['lan-tx'] = round(
tmp_accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) tmp_accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2)
self.data['accounting'][mac]['lan-rx'] = round( self.data['accounting'][mac]['lan-rx'] = round(
@ -877,6 +879,6 @@ class MikrotikControllerData:
self.data['accounting'][mac]['wan-tx'] = 0.0 self.data['accounting'][mac]['wan-tx'] = 0.0
self.data['accounting'][mac]['wan-rx'] = 0.0 self.data['accounting'][mac]['wan-rx'] = 0.0
if self.api.is_accounting_local_traffic_enabled(): if local_traffic_enabled:
self.data['accounting'][mac]['lan-tx'] = 0.0 self.data['accounting'][mac]['lan-tx'] = 0.0
self.data['accounting'][mac]['lan-rx'] = 0.0 self.data['accounting'][mac]['lan-rx'] = 0.0

View file

@ -471,36 +471,31 @@ class MikrotikAPI:
from time import time from time import time
return int(round(time() * 1000)) return int(round(time() * 1000))
def is_accounting_enabled(self) -> bool: def is_accounting_and_local_traffic_enabled(self) -> (bool, bool):
# Returns:
# 1st bool: Is accounting enabled
# 2nd bool: Is account-local-traffic enabled
if not self.connection_check(): if not self.connection_check():
return False return False, False
response = self.path("/ip/accounting") response = self.path("/ip/accounting")
if response is None: if response is None:
return False return False, False
for item in response: for item in response:
if 'enabled' not in item: if 'enabled' not in item:
continue continue
if item['enabled']: if not item['enabled']:
return True return False, False
return False for item in response:
def is_accounting_local_traffic_enabled(self) -> bool:
if not self.connection_check():
return False
accounting = self.path("/ip/accounting")
if accounting is None:
return False
for item in accounting:
if 'account-local-traffic' not in item: if 'account-local-traffic' not in item:
continue continue
if item['account-local-traffic']: if not item['account-local-traffic']:
return True return True, False
return False
return True, True
# --------------------------- # ---------------------------
# take_accounting_snapshot # take_accounting_snapshot

View file

@ -13,7 +13,6 @@
"password": "Password", "password": "Password",
"ssl": "Use SSL", "ssl": "Use SSL",
"unit_of_measurement": "Unit of measurement", "unit_of_measurement": "Unit of measurement",
"track_accounting": "Track accounting"
} }
} }
}, },
@ -23,7 +22,6 @@
"ssl_handshake_failure": "SSL handshake failure", "ssl_handshake_failure": "SSL handshake failure",
"connection_timeout": "Mikrotik connection timeout.", "connection_timeout": "Mikrotik connection timeout.",
"wrong_login": "Invalid user name or password.", "wrong_login": "Invalid user name or password.",
"accounting_disabled": "Accounting disabled in Mikrotik, cannot track."
} }
}, },
"options": { "options": {