From 30c11db741c03b6983cf092221acdc1c7e4fad02 Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sat, 4 Apr 2020 19:42:05 +0200 Subject: [PATCH 01/12] Add ability to track accounting data from Mikrotik. --- .../mikrotik_router/.translations/en.json | 6 +- .../mikrotik_router/.translations/ru.json | 6 +- custom_components/mikrotik_router/__init__.py | 8 +- .../mikrotik_router/config_flow.py | 11 +- custom_components/mikrotik_router/const.py | 2 + .../mikrotik_router/mikrotik_controller.py | 200 ++++++++++++++++++ .../mikrotik_router/mikrotikapi.py | 93 +++++++- custom_components/mikrotik_router/sensor.py | 130 +++++++++++- .../mikrotik_router/strings.json | 6 +- 9 files changed, 449 insertions(+), 13 deletions(-) diff --git a/custom_components/mikrotik_router/.translations/en.json b/custom_components/mikrotik_router/.translations/en.json index ec26cf2..913af8a 100644 --- a/custom_components/mikrotik_router/.translations/en.json +++ b/custom_components/mikrotik_router/.translations/en.json @@ -12,7 +12,8 @@ "username": "Username", "password": "Password", "ssl": "Use SSL", - "unit_of_measurement": "Unit of measurement" + "unit_of_measurement": "Unit of measurement", + "track_accounting": "Track accounting" } } }, @@ -21,7 +22,8 @@ "cannot_connect": "Cannot connect to Mikrotik.", "ssl_handshake_failure": "SSL handshake failure", "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": { diff --git a/custom_components/mikrotik_router/.translations/ru.json b/custom_components/mikrotik_router/.translations/ru.json index 2cda4b9..b8216d0 100644 --- a/custom_components/mikrotik_router/.translations/ru.json +++ b/custom_components/mikrotik_router/.translations/ru.json @@ -12,7 +12,8 @@ "username": "Имя пользователя", "password": "Пароль", "ssl": "Использовать SSL", - "unit_of_measurement": "Единицы измерения" + "unit_of_measurement": "Единицы измерения", + "track_accounting": "Отслеживание учета" } } }, @@ -21,7 +22,8 @@ "cannot_connect": "Нет связи с Mikrotik.", "ssl_handshake_failure": "Ошибка SSL-соединения", "connection_timeout": "Таймаут подключения к Mikrotik.", - "wrong_login": "Неверные имя пользователя или пароль." + "wrong_login": "Неверные имя пользователя или пароль.", + "accounting_disabled": "Учетная запись отключена в Mikrotik, не может отслеживать." } }, "options": { diff --git a/custom_components/mikrotik_router/__init__.py b/custom_components/mikrotik_router/__init__.py index 61d051b..b9ae7f3 100644 --- a/custom_components/mikrotik_router/__init__.py +++ b/custom_components/mikrotik_router/__init__.py @@ -17,6 +17,7 @@ from .const import ( DOMAIN, DATA_CLIENT, DEFAULT_TRAFFIC_TYPE, + CONF_TRACK_ACCOUNTING, ) from .mikrotik_controller import MikrotikControllerData @@ -48,12 +49,17 @@ async def async_setup_entry(hass, config_entry): traffic_type = config_entry.data[CONF_UNIT_OF_MEASUREMENT] else: traffic_type = DEFAULT_TRAFFIC_TYPE + track_accounting = config_entry.data[CONF_TRACK_ACCOUNTING] mikrotik_controller = MikrotikControllerData( hass, config_entry, name, host, port, username, password, use_ssl, - traffic_type + traffic_type, track_accounting ) await mikrotik_controller.hwinfo_update() + + if track_accounting: + await mikrotik_controller.async_accounting_hosts_update() + await mikrotik_controller.async_update() if not mikrotik_controller.data: diff --git a/custom_components/mikrotik_router/config_flow.py b/custom_components/mikrotik_router/config_flow.py index 3e9b3be..fdc6285 100644 --- a/custom_components/mikrotik_router/config_flow.py +++ b/custom_components/mikrotik_router/config_flow.py @@ -27,6 +27,7 @@ from .const import ( DEFAULT_SCAN_INTERVAL, DEFAULT_TRAFFIC_TYPE, TRAFFIC_TYPES, + CONF_TRACK_ACCOUNTING, ) from .mikrotikapi import MikrotikAPI @@ -51,7 +52,7 @@ def configured_instances(hass): class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN): """MikrotikControllerConfigFlow class""" - VERSION = 1 + VERSION = 2 CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL def __init__(self): @@ -81,10 +82,13 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN): username=user_input["username"], password=user_input["password"], port=user_input["port"], - use_ssl=user_input["ssl"], + use_ssl=user_input["ssl"] ) if not api.connect(): 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 if not errors: @@ -99,6 +103,7 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN): port=user_input["port"], name=user_input["name"], use_ssl=user_input["ssl"], + track_accounting=user_input["track_accounting"], errors=errors, ) @@ -115,6 +120,7 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN): port=0, name="Mikrotik", use_ssl=False, + track_accounting=False, errors=None, ): """Show the configuration form to edit data.""" @@ -131,6 +137,7 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN): vol.Optional(CONF_PORT, default=port): int, vol.Optional(CONF_NAME, default=name): str, vol.Optional(CONF_SSL, default=use_ssl): bool, + vol.Optional(CONF_TRACK_ACCOUNTING, default=track_accounting): bool, } ), errors=errors, diff --git a/custom_components/mikrotik_router/const.py b/custom_components/mikrotik_router/const.py index 0472cca..c9d4a11 100644 --- a/custom_components/mikrotik_router/const.py +++ b/custom_components/mikrotik_router/const.py @@ -16,3 +16,5 @@ DEFAULT_LOGIN_METHOD = "plain" DEFAULT_TRAFFIC_TYPE = "Kbps" TRAFFIC_TYPES = ["bps", "Kbps", "Mbps", "B/s", "KB/s", "MB/s"] + +CONF_TRACK_ACCOUNTING = "track_accounting" diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index 42a5326..3377253 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -3,6 +3,7 @@ import asyncio import logging from datetime import timedelta +import ipaddress from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -41,12 +42,14 @@ class MikrotikControllerData: password, use_ssl, traffic_type, + track_accounting, ): """Initialize MikrotikController.""" self.name = name self.hass = hass self.config_entry = config_entry self.traffic_type = traffic_type + self.track_accounting = track_accounting self.data = { "routerboard": {}, @@ -57,8 +60,11 @@ class MikrotikControllerData: "fw-update": {}, "script": {}, "queue": {}, + "accounting": {} } + self.local_dhcp_networks = [] + self.listeners = [] self.lock = asyncio.Lock() @@ -70,6 +76,10 @@ class MikrotikControllerData: async_track_time_interval( 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): traffic_type = self.option_traffic_type @@ -96,6 +106,14 @@ class MikrotikControllerData: """Trigger update by timer""" 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 # --------------------------- @@ -162,6 +180,19 @@ class MikrotikControllerData: await self.hass.async_add_executor_job(self.get_system_resource) 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 # --------------------------- @@ -190,6 +221,8 @@ class MikrotikControllerData: await self.hass.async_add_executor_job(self.get_system_resource) await self.hass.async_add_executor_job(self.get_script) await self.hass.async_add_executor_job(self.get_queue) + if self.track_accounting: + await self.hass.async_add_executor_job(self.get_accounting) async_dispatcher_send(self.hass, self.signal_update) self.lock.release() @@ -629,3 +662,170 @@ class MikrotikControllerData: upload_burst_time, download_burst_time = self.data["queue"][uid]["burst-time"].split('/') self.data["queue"][uid]["upload-burst-time"] = upload_burst_time self.data["queue"][uid]["download-burst-time"] = download_burst_time + + def build_accounting_hosts(self): + # Build hosts from DHCP Server leases and ARP list + + self.data["accounting"] = parse_api( + data=self.data["accounting"], + source=self.api.path("/ip/dhcp-server/lease", return_list=True), + key="address", + vals=[ + {"name": "address"}, + {"name": "mac-address"}, + {"name": "host-name"}, + {"name": "comment"}, + {"name": "disabled", "default": True}, + ], + only=[ + {"key": "disabled", "value": False}, + ], + ensure_vals=[ + {"name": "address"}, + {"name": "mac-address"}, + ] + ) + + # Also retrieve static DNS entries + dns_data = parse_api( + data={}, + source=self.api.path("/ip/dns/static", return_list=True), + key="address", + vals=[ + {"name": "address"}, + {"name": "name"}, + ], + ) + + # Also retrieve all entries in ARP table. If some hosts are missing, build it here + arp_hosts = parse_api( + data={}, + source=self.api.path("/ip/arp", return_list=True), + key="address", + vals=[ + {"name": "address"}, + {"name": "mac-address"}, + {"name": "disabled", "default": True}, + {"name": "invalid", "default": True}, + ], + only=[ + {"key": "disabled", "value": False}, + {"key": "invalid", "value": False} + ], + ensure_vals=[ + {"name": "address"}, + {"name": "mac-address"}, + ] + ) + + for addr in arp_hosts: + if addr not in self.data["accounting"]: + self.data["accounting"][addr] = { + "address": arp_hosts[addr]['address'], + "mac-address": arp_hosts[addr]['address'] + } + + # Build name for host. First try getting DHCP lease comment, then entry in DNS and then device's host-name. + # If everything fails use hosts IP address as name + for addr in self.data["accounting"]: + if str(self.data["accounting"][addr].get('comment', '').strip()): + self.data["accounting"][addr]['name'] = self.data["accounting"][addr]['comment'] + elif addr in dns_data and str(dns_data[addr].get('name', '').strip()): + self.data["accounting"][addr]['name'] = dns_data[addr]['name'] + elif str(self.data["accounting"][addr].get('host-name', '').strip()): + self.data["accounting"][addr]['name'] = self.data["accounting"][addr]['host-name'] + else: + self.data["accounting"][addr]['name'] = self.data["accounting"][addr]['address'] + + _LOGGER.debug(f"Generated {len(self.data['accounting'])} accounting hosts") + + # Build local networks + dhcp_networks = parse_api( + data={}, + source=self.api.path("/ip/dhcp-server/network", return_list=True), + key="address", + vals=[ + {"name": "address"}, + ], + ensure_vals=[ + {"name": "address"}, + ] + ) + + self.local_dhcp_networks = [ipaddress.IPv4Network(network) for network in dhcp_networks] + + def _address_part_of_local_network(self, address): + address = ipaddress.ip_address(address) + for network in self.local_dhcp_networks: + if address in network: + return True + return False + + def get_accounting(self): + """Get Accounting data from Mikrotik""" + traffic_type, traffic_div = self._get_traffic_type_and_div() + + # Build temp accounting values dict with all known addresses + # Also set traffic type for each item + accounting_values = {} + for addr in self.data['accounting']: + accounting_values[addr] = { + "wan-tx": 0, + "wan-rx": 0, + "lan-tx": 0, + "lan-rx": 0 + } + self.data['accounting'][addr]["lan-wan-tx-rx-attr"] = traffic_type + + time_diff = self.api.take_accounting_snapshot() + if time_diff: + accounting_data = parse_api( + data={}, + source=self.api.path("/ip/accounting/snapshot", return_list=True), + key=".id", + vals=[ + {"name": ".id"}, + {"name": "src-address"}, + {"name": "dst-address"}, + {"name": "bytes", "default": 0}, + ], + ) + + for item in accounting_data.values(): + source_ip = str(item.get('src-address')).strip() + destination_ip = str(item.get('dst-address')).strip() + bits_count = int(str(item.get('bytes')).strip()) * 8 + + if self._address_part_of_local_network(source_ip) and self._address_part_of_local_network(destination_ip): + # LAN TX/RX + if source_ip in accounting_values: + accounting_values[source_ip]['lan-tx'] += bits_count + if destination_ip in accounting_values: + accounting_values[destination_ip]['lan-rx'] += bits_count + elif self._address_part_of_local_network(source_ip) and \ + not self._address_part_of_local_network(destination_ip): + # WAN TX + if source_ip in accounting_values: + accounting_values[source_ip]['wan-tx'] += bits_count + elif not self._address_part_of_local_network(source_ip) and \ + self._address_part_of_local_network(destination_ip): + # WAN RX + if destination_ip in accounting_values: + accounting_values[destination_ip]['wan-rx'] += bits_count + else: + _LOGGER.debug(f"Skipping packet from {source_ip} to {destination_ip}") + continue + + # Now that we have sum of all traffic in bytes for given period + # calculate real throughput and transform it to appropriate unit + for addr in accounting_values: + self.data['accounting'][addr]['lan-tx'] = round( + accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) + self.data['accounting'][addr]['lan-rx'] = round( + accounting_values[addr]['lan-rx'] / time_diff * traffic_div, 2) + + self.data['accounting'][addr]['wan-tx'] = round( + accounting_values[addr]['wan-tx'] / time_diff * traffic_div, 2) + + self.data['accounting'][addr]['wan-rx'] = round( + accounting_values[addr]['wan-rx'] / time_diff * traffic_div, 2) diff --git a/custom_components/mikrotik_router/mikrotikapi.py b/custom_components/mikrotik_router/mikrotikapi.py index 883e865..b8497fb 100644 --- a/custom_components/mikrotik_router/mikrotikapi.py +++ b/custom_components/mikrotik_router/mikrotikapi.py @@ -60,6 +60,7 @@ class MikrotikAPI: self._connection_retry_sec = 58 self.error = None self.connection_error_reported = False + self.accounting_last_run = None # Default ports if not self._port: @@ -396,7 +397,6 @@ class MikrotikAPI: # --------------------------- def get_traffic(self, interfaces) -> Optional(list): """Get traffic stats""" - traffic = None if not self._connected or not self._connection: if self._connection_epoch > time.time() - self._connection_retry_sec: return None @@ -482,3 +482,94 @@ class MikrotikAPI: self.lock.release() return traffic if traffic else None + + @staticmethod + def _current_milliseconds(): + from time import time + return int(round(time() * 1000)) + + def is_accounting_enabled(self): + accounting = self.path("/ip/accounting", return_list=True) + if accounting is None: + return False + + for item in accounting: + if 'enabled' not in item: + continue + if item['enabled']: + return True + return False + + # --------------------------- + # take_accounting_snapshot + # Returns float -> seconds period between last run and current run + # --------------------------- + def take_accounting_snapshot(self) -> float: + """Get accounting data""" + if not self._connected or not self._connection: + if self._connection_epoch > time.time() - self._connection_retry_sec: + return 0 + + if not self.connect(): + return 0 + + accounting = self.path("/ip/accounting") + + self.lock.acquire() + try: + # Prepare command + take = accounting('snapshot/take') + # Run command on Mikrotik + tuple(take) + except librouteros_custom.exceptions.ConnectionClosed: + if not self.connection_error_reported: + _LOGGER.error("Mikrotik %s connection closed", self._host) + self.connection_error_reported = True + + self.disconnect() + self.lock.release() + return 0 + except ( + librouteros_custom.exceptions.TrapError, + librouteros_custom.exceptions.MultiTrapError, + librouteros_custom.exceptions.ProtocolError, + librouteros_custom.exceptions.FatalError, + ssl.SSLError, + BrokenPipeError, + OSError, + ValueError, + ) as api_error: + if not self.connection_error_reported: + _LOGGER.error( + "Mikrotik %s error while take_accounting_snapshot %s -> %s - %s", self._host, + type(api_error), api_error.args + ) + self.connection_error_reported = True + + self.disconnect() + self.lock.release() + return 0 + except Exception as e: + if not self.connection_error_reported: + _LOGGER.error( + "% -> %s error on %s host while take_accounting_snapshot", + type(e), e.args, self._host, + ) + self.connection_error_reported = True + + self.disconnect() + self.lock.release() + return 0 + + self.lock.release() + + # First request will be discarded because we cannot know when the last data was retrieved + # prevents spikes in data + if not self.accounting_last_run: + self.accounting_last_run = self._current_milliseconds() + return 0 + + # Calculate time difference in seconds and return + time_diff = self._current_milliseconds() - self.accounting_last_run + self.accounting_last_run = self._current_milliseconds() + return time_diff / 1000 diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 5a883a2..9746f14 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -12,6 +12,19 @@ from .const import (DOMAIN, DATA_CLIENT, ATTRIBUTION) _LOGGER = logging.getLogger(__name__) + +# --------------------------- +# format_attribute +# --------------------------- +def format_attribute(attr): + res = attr.replace("-", " ") + res = res.capitalize() + res = res.replace(" ip ", " IP ") + res = res.replace(" mac ", " MAC ") + res = res.replace(" mtu", " MTU") + return res + + ATTR_ICON = "icon" ATTR_LABEL = "label" ATTR_UNIT = "unit" @@ -66,8 +79,49 @@ SENSOR_TYPES = { ATTR_PATH: "interface", ATTR_ATTR: "rx-bits-per-second", }, + "accounting_lan_tx": { + ATTR_DEVICE_CLASS: None, + ATTR_ICON: "mdi:download-network", + ATTR_LABEL: "LAN TX", + ATTR_UNIT: "ps", + ATTR_UNIT_ATTR: "lan-wan-tx-rx-attr", + ATTR_PATH: "accounting", + ATTR_ATTR: "lan-tx", + }, + "accounting_lan_rx": { + ATTR_DEVICE_CLASS: None, + ATTR_ICON: "mdi:upload-network", + ATTR_LABEL: "LAN RX", + ATTR_UNIT: "ps", + ATTR_UNIT_ATTR: "lan-wan-tx-rx-attr", + ATTR_PATH: "accounting", + ATTR_ATTR: "lan-rx", + }, + "accounting_wan_tx": { + ATTR_DEVICE_CLASS: None, + ATTR_ICON: "mdi:download-network", + ATTR_LABEL: "WAN TX", + ATTR_UNIT: "ps", + ATTR_UNIT_ATTR: "lan-wan-tx-rx-attr", + ATTR_PATH: "accounting", + ATTR_ATTR: "wan-tx", + }, + "accounting_wan_rx": { + ATTR_DEVICE_CLASS: None, + ATTR_ICON: "mdi:upload-network", + ATTR_LABEL: "WAN RX", + ATTR_UNIT: "ps", + ATTR_UNIT_ATTR: "lan-wan-tx-rx-attr", + ATTR_PATH: "accounting", + ATTR_ATTR: "wan-rx", + }, } +DEVICE_ATTRIBUTES_ACCOUNTING = [ + "address", + "mac-address", +] + # --------------------------- # async_setup_entry @@ -101,7 +155,7 @@ def update_items(inst, mikrotik_controller, async_add_entities, sensors): new_sensors = [] for sensor in SENSOR_TYPES: - if "traffic_" not in sensor: + if "system_" in sensor: item_id = f"{inst}-{sensor}" if item_id in sensors: if sensors[item_id].enabled: @@ -116,8 +170,7 @@ def update_items(inst, mikrotik_controller, async_add_entities, sensors): if "traffic_" in sensor: 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 = f"{inst}-{sensor}-{mikrotik_controller.data['interface'][uid]['default-name']}" if item_id in sensors: if sensors[item_id].enabled: @@ -132,6 +185,23 @@ def update_items(inst, mikrotik_controller, async_add_entities, sensors): ) new_sensors.append(sensors[item_id]) + if "accounting_" in sensor: + for uid in mikrotik_controller.data["accounting"]: + + item_id = f"{inst}-{sensor}-{mikrotik_controller.data['accounting'][uid]['name']}" + if item_id in sensors: + if sensors[item_id].enabled: + sensors[item_id].async_schedule_update_ha_state() + continue + + sensors[item_id] = MikrotikAccountingSensor( + mikrotik_controller=mikrotik_controller, + inst=inst, + sensor=sensor, + uid=uid, + ) + new_sensors.append(sensors[item_id]) + if new_sensors: async_add_entities(new_sensors, True) @@ -276,3 +346,57 @@ class MikrotikControllerTrafficSensor(MikrotikControllerSensor): self._data["default-name"], self._sensor, ) + + +# --------------------------- +# MikrotikAccountingSensor +# --------------------------- +class MikrotikAccountingSensor(MikrotikControllerSensor): + """Define an Mikrotik Accounting sensor.""" + + def __init__(self, mikrotik_controller, inst, sensor, uid): + """Initialize.""" + super().__init__(mikrotik_controller, inst, sensor) + self._uid = uid + self._data = mikrotik_controller.data[SENSOR_TYPES[sensor][ATTR_PATH]][uid] + + @property + def name(self): + """Return the name.""" + return f"{self._inst} {self._data['name']} {self._type[ATTR_LABEL]}" + + @property + def unique_id(self): + """Return a unique_id for this entity.""" + return f"{self._inst.lower()}-{self._sensor.lower()}-{self._data['address'].lower()}" + + @property + def device_info(self): + """Return a port description for device registry.""" + info = { + "connections": { + (CONNECTION_NETWORK_MAC, self._data["mac-address"])}, + "manufacturer": self._ctrl.data["resource"]["platform"], + "model": self._ctrl.data["resource"]["board-name"], + "name": self._data["name"], + } + return info + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attributes = self._attrs + for variable in DEVICE_ATTRIBUTES_ACCOUNTING: + if variable in self._data: + attributes[format_attribute(variable)] = self._data[variable] + + return attributes + + async def async_added_to_hass(self): + """Port entity created.""" + _LOGGER.debug( + "New sensor %s (%s %s)", + self._inst, + self._data["name"], + self._sensor, + ) diff --git a/custom_components/mikrotik_router/strings.json b/custom_components/mikrotik_router/strings.json index ec26cf2..913af8a 100644 --- a/custom_components/mikrotik_router/strings.json +++ b/custom_components/mikrotik_router/strings.json @@ -12,7 +12,8 @@ "username": "Username", "password": "Password", "ssl": "Use SSL", - "unit_of_measurement": "Unit of measurement" + "unit_of_measurement": "Unit of measurement", + "track_accounting": "Track accounting" } } }, @@ -21,7 +22,8 @@ "cannot_connect": "Cannot connect to Mikrotik.", "ssl_handshake_failure": "SSL handshake failure", "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": { From f9a458bbfbf28995f01088a73cbcdc85a2aade3e Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 15:03:17 +0200 Subject: [PATCH 02/12] Add ability to automatically determine if the LAN accounting sensor should be created depending on account-local-traffic in Mikrotik API --- README.md | 1 + .../mikrotik_router/mikrotik_controller.py | 29 ++++++++++--------- .../mikrotik_router/mikrotikapi.py | 14 ++++++++- custom_components/mikrotik_router/sensor.py | 26 +++++++++-------- 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index ab7f942..a2ccf8c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Features: * System sensors (CPU, Memory, HDD) * Firmware update binary sensor * Switches to run scripts + * RX/TX traffic sensors per hosts from Mikrotik Accounting # Integration preview ![Tracker and sensors](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/device_tracker.png) diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index 3377253..23fffb8 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -76,10 +76,12 @@ class MikrotikControllerData: async_track_time_interval( self.hass, self.force_fwupdate_check, timedelta(hours=1) ) + self.account_local_traffic = False if self.track_accounting: async_track_time_interval( self.hass, self.force_accounting_hosts_update, timedelta(minutes=15) ) + self.account_local_traffic = self.api.is_accounting_local_traffic_enabled() def _get_traffic_type_and_div(self): traffic_type = self.option_traffic_type @@ -769,13 +771,14 @@ class MikrotikControllerData: # Also set traffic type for each item accounting_values = {} for addr in self.data['accounting']: - accounting_values[addr] = { - "wan-tx": 0, - "wan-rx": 0, - "lan-tx": 0, - "lan-rx": 0 - } - self.data['accounting'][addr]["lan-wan-tx-rx-attr"] = traffic_type + accounting_values[addr] = {} + accounting_values[addr]["wan-tx"] = 0 + accounting_values[addr]["wan-rx"] = 0 + if self.account_local_traffic: + accounting_values[addr]["lan-tx"] = 0 + accounting_values[addr]["lan-rx"] = 0 + + self.data['accounting'][addr]["tx-rx-attr"] = traffic_type time_diff = self.api.take_accounting_snapshot() if time_diff: @@ -819,13 +822,13 @@ class MikrotikControllerData: # Now that we have sum of all traffic in bytes for given period # calculate real throughput and transform it to appropriate unit for addr in accounting_values: - self.data['accounting'][addr]['lan-tx'] = round( - accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) - self.data['accounting'][addr]['lan-rx'] = round( - accounting_values[addr]['lan-rx'] / time_diff * traffic_div, 2) - self.data['accounting'][addr]['wan-tx'] = round( accounting_values[addr]['wan-tx'] / time_diff * traffic_div, 2) - self.data['accounting'][addr]['wan-rx'] = round( accounting_values[addr]['wan-rx'] / time_diff * traffic_div, 2) + + if self.account_local_traffic: + self.data['accounting'][addr]['lan-tx'] = round( + accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) + self.data['accounting'][addr]['lan-rx'] = round( + accounting_values[addr]['lan-rx'] / time_diff * traffic_div, 2) diff --git a/custom_components/mikrotik_router/mikrotikapi.py b/custom_components/mikrotik_router/mikrotikapi.py index b8497fb..dd69141 100644 --- a/custom_components/mikrotik_router/mikrotikapi.py +++ b/custom_components/mikrotik_router/mikrotikapi.py @@ -488,7 +488,7 @@ class MikrotikAPI: from time import time return int(round(time() * 1000)) - def is_accounting_enabled(self): + def is_accounting_enabled(self) -> bool: accounting = self.path("/ip/accounting", return_list=True) if accounting is None: return False @@ -500,6 +500,18 @@ class MikrotikAPI: return True return False + def is_accounting_local_traffic_enabled(self) -> bool: + accounting = self.path("/ip/accounting", return_list=True) + if accounting is None: + return False + + for item in accounting: + if 'account-local-traffic' not in item: + continue + if item['account-local-traffic']: + return True + return False + # --------------------------- # take_accounting_snapshot # Returns float -> seconds period between last run and current run diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 9746f14..3ee1376 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -84,7 +84,7 @@ SENSOR_TYPES = { ATTR_ICON: "mdi:download-network", ATTR_LABEL: "LAN TX", ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "lan-wan-tx-rx-attr", + ATTR_UNIT_ATTR: "tx-rx-attr", ATTR_PATH: "accounting", ATTR_ATTR: "lan-tx", }, @@ -93,7 +93,7 @@ SENSOR_TYPES = { ATTR_ICON: "mdi:upload-network", ATTR_LABEL: "LAN RX", ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "lan-wan-tx-rx-attr", + ATTR_UNIT_ATTR: "tx-rx-attr", ATTR_PATH: "accounting", ATTR_ATTR: "lan-rx", }, @@ -102,7 +102,7 @@ SENSOR_TYPES = { ATTR_ICON: "mdi:download-network", ATTR_LABEL: "WAN TX", ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "lan-wan-tx-rx-attr", + ATTR_UNIT_ATTR: "tx-rx-attr", ATTR_PATH: "accounting", ATTR_ATTR: "wan-tx", }, @@ -111,7 +111,7 @@ SENSOR_TYPES = { ATTR_ICON: "mdi:upload-network", ATTR_LABEL: "WAN RX", ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "lan-wan-tx-rx-attr", + ATTR_UNIT_ATTR: "tx-rx-attr", ATTR_PATH: "accounting", ATTR_ATTR: "wan-rx", }, @@ -194,14 +194,16 @@ def update_items(inst, mikrotik_controller, async_add_entities, sensors): sensors[item_id].async_schedule_update_ha_state() continue - sensors[item_id] = MikrotikAccountingSensor( - mikrotik_controller=mikrotik_controller, - inst=inst, - sensor=sensor, - uid=uid, - ) - new_sensors.append(sensors[item_id]) - + if SENSOR_TYPES[sensor][ATTR_ATTR] in mikrotik_controller.data['accounting'][uid].keys(): + sensors[item_id] = MikrotikAccountingSensor( + mikrotik_controller=mikrotik_controller, + inst=inst, + sensor=sensor, + uid=uid, + ) + new_sensors.append(sensors[item_id]) + else: + _LOGGER.info(f"WONT CREATE {SENSOR_TYPES[sensor][ATTR_ATTR]} for {item_id}") if new_sensors: async_add_entities(new_sensors, True) From a1eee6f69848195bdbb725d4f330c9284f03c61d Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 16:00:30 +0200 Subject: [PATCH 03/12] Initialize wan/lan accounting data on startup --- .../mikrotik_router/mikrotik_controller.py | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index 23fffb8..d2a6ce7 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -688,16 +688,7 @@ class MikrotikControllerData: ] ) - # Also retrieve static DNS entries - dns_data = parse_api( - data={}, - source=self.api.path("/ip/dns/static", return_list=True), - key="address", - vals=[ - {"name": "address"}, - {"name": "name"}, - ], - ) + # Also retrieve all entries in ARP table. If some hosts are missing, build it here arp_hosts = parse_api( @@ -729,6 +720,17 @@ class MikrotikControllerData: # Build name for host. First try getting DHCP lease comment, then entry in DNS and then device's host-name. # If everything fails use hosts IP address as name + + dns_data = parse_api( + data={}, + source=self.api.path("/ip/dns/static", return_list=True), + key="address", + vals=[ + {"name": "address"}, + {"name": "name"}, + ], + ) + for addr in self.data["accounting"]: if str(self.data["accounting"][addr].get('comment', '').strip()): self.data["accounting"][addr]['name'] = self.data["accounting"][addr]['comment'] @@ -739,6 +741,13 @@ class MikrotikControllerData: else: self.data["accounting"][addr]['name'] = self.data["accounting"][addr]['address'] + # Initialize data + self.data["accounting"][addr]["wan-tx"] = 0 + self.data["accounting"][addr]["wan-rx"] = 0 + if self.account_local_traffic: + self.data["accounting"][addr]["lan-tx"] = 0 + self.data["accounting"][addr]["lan-rx"] = 0 + _LOGGER.debug(f"Generated {len(self.data['accounting'])} accounting hosts") # Build local networks From 1c0b3018ed59b50cae3f7177e8409a05c79c8563 Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 16:04:17 +0200 Subject: [PATCH 04/12] Fix --- .../mikrotik_router/mikrotik_controller.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index d2a6ce7..0a72ebf 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -780,13 +780,12 @@ class MikrotikControllerData: # Also set traffic type for each item accounting_values = {} for addr in self.data['accounting']: - accounting_values[addr] = {} - accounting_values[addr]["wan-tx"] = 0 - accounting_values[addr]["wan-rx"] = 0 - if self.account_local_traffic: - accounting_values[addr]["lan-tx"] = 0 - accounting_values[addr]["lan-rx"] = 0 - + accounting_values[addr] = { + "wan-tx": 0, + "wan-rx": 0, + "lan-tx": 0, + "lan-rx": 0 + } self.data['accounting'][addr]["tx-rx-attr"] = traffic_type time_diff = self.api.take_accounting_snapshot() From d5ddbc7772fdeae95a262d66e0db0001ee1de46b Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 18:08:34 +0200 Subject: [PATCH 05/12] Allow for dynamic check of local_traffic enabled in Mikrotik. --- .../mikrotik_router/mikrotik_controller.py | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index 0a72ebf..7b78774 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -76,12 +76,10 @@ class MikrotikControllerData: async_track_time_interval( self.hass, self.force_fwupdate_check, timedelta(hours=1) ) - self.account_local_traffic = False if self.track_accounting: async_track_time_interval( - self.hass, self.force_accounting_hosts_update, timedelta(minutes=15) + self.hass, self.force_accounting_hosts_update, timedelta(minutes=5) ) - self.account_local_traffic = self.api.is_accounting_local_traffic_enabled() def _get_traffic_type_and_div(self): traffic_type = self.option_traffic_type @@ -667,7 +665,6 @@ class MikrotikControllerData: def build_accounting_hosts(self): # Build hosts from DHCP Server leases and ARP list - self.data["accounting"] = parse_api( data=self.data["accounting"], source=self.api.path("/ip/dhcp-server/lease", return_list=True), @@ -688,8 +685,6 @@ class MikrotikControllerData: ] ) - - # Also retrieve all entries in ARP table. If some hosts are missing, build it here arp_hosts = parse_api( data={}, @@ -718,9 +713,8 @@ class MikrotikControllerData: "mac-address": arp_hosts[addr]['address'] } - # Build name for host. First try getting DHCP lease comment, then entry in DNS and then device's host-name. - # If everything fails use hosts IP address as name - + # Build name for host. First try getting DHCP lease comment, then entry in DNS (only static entries) + # and then device's host-name. If everything fails use hosts IP address as name dns_data = parse_api( data={}, source=self.api.path("/ip/dns/static", return_list=True), @@ -744,13 +738,13 @@ class MikrotikControllerData: # Initialize data self.data["accounting"][addr]["wan-tx"] = 0 self.data["accounting"][addr]["wan-rx"] = 0 - if self.account_local_traffic: + if self.api.is_accounting_local_traffic_enabled(): self.data["accounting"][addr]["lan-tx"] = 0 self.data["accounting"][addr]["lan-rx"] = 0 - _LOGGER.debug(f"Generated {len(self.data['accounting'])} accounting hosts") + _LOGGER.debug(f"Generated {len(self.data['accounting'])} accounting devices") - # Build local networks + # Build list of local networks dhcp_networks = parse_api( data={}, source=self.api.path("/ip/dhcp-server/network", return_list=True), @@ -835,8 +829,9 @@ class MikrotikControllerData: self.data['accounting'][addr]['wan-rx'] = round( accounting_values[addr]['wan-rx'] / time_diff * traffic_div, 2) - if self.account_local_traffic: + if 'lan-tx' in self.data['accounting'][addr]: self.data['accounting'][addr]['lan-tx'] = round( accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) + if 'lan-rx' in self.data['accounting'][addr]: self.data['accounting'][addr]['lan-rx'] = round( accounting_values[addr]['lan-rx'] / time_diff * traffic_div, 2) From 887e645553ecf436fa6073ef89e667f9cb864aac Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 18:29:51 +0200 Subject: [PATCH 06/12] Fix dynamic build of local tx/rx sensors. --- .../mikrotik_router/mikrotik_controller.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index 7b78774..d58ce66 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -78,7 +78,7 @@ class MikrotikControllerData: ) if self.track_accounting: async_track_time_interval( - self.hass, self.force_accounting_hosts_update, timedelta(minutes=5) + self.hass, self.force_accounting_hosts_update, timedelta(minutes=15) ) def _get_traffic_type_and_div(self): @@ -735,13 +735,6 @@ class MikrotikControllerData: else: self.data["accounting"][addr]['name'] = self.data["accounting"][addr]['address'] - # Initialize data - self.data["accounting"][addr]["wan-tx"] = 0 - self.data["accounting"][addr]["wan-rx"] = 0 - if self.api.is_accounting_local_traffic_enabled(): - self.data["accounting"][addr]["lan-tx"] = 0 - self.data["accounting"][addr]["lan-rx"] = 0 - _LOGGER.debug(f"Generated {len(self.data['accounting'])} accounting devices") # Build list of local networks @@ -829,9 +822,17 @@ class MikrotikControllerData: self.data['accounting'][addr]['wan-rx'] = round( accounting_values[addr]['wan-rx'] / time_diff * traffic_div, 2) - if 'lan-tx' in self.data['accounting'][addr]: + if self.api.is_accounting_local_traffic_enabled(): self.data['accounting'][addr]['lan-tx'] = round( accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) - if 'lan-rx' in self.data['accounting'][addr]: self.data['accounting'][addr]['lan-rx'] = round( accounting_values[addr]['lan-rx'] / time_diff * traffic_div, 2) + else: + # No time diff, just initialize/return counters to 0 for all + for addr in accounting_values: + self.data['accounting'][addr]['wan-tx'] = 0.0 + self.data['accounting'][addr]['wan-rx'] = 0.0 + + if self.api.is_accounting_local_traffic_enabled(): + self.data['accounting'][addr]['lan-tx'] = 0.0 + self.data['accounting'][addr]['lan-rx'] = 0.0 From 03e70e521cc961711b91625cd2d8b7c0bbb143fc Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 18:32:48 +0200 Subject: [PATCH 07/12] Accounting initialize fix --- .../mikrotik_router/mikrotik_controller.py | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index d58ce66..ca63ce7 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -765,9 +765,9 @@ class MikrotikControllerData: # Build temp accounting values dict with all known addresses # Also set traffic type for each item - accounting_values = {} + tmp_accounting_values = {} for addr in self.data['accounting']: - accounting_values[addr] = { + tmp_accounting_values[addr] = { "wan-tx": 0, "wan-rx": 0, "lan-tx": 0, @@ -796,40 +796,46 @@ class MikrotikControllerData: if self._address_part_of_local_network(source_ip) and self._address_part_of_local_network(destination_ip): # LAN TX/RX - if source_ip in accounting_values: - accounting_values[source_ip]['lan-tx'] += bits_count - if destination_ip in accounting_values: - accounting_values[destination_ip]['lan-rx'] += bits_count + if source_ip in tmp_accounting_values: + tmp_accounting_values[source_ip]['lan-tx'] += bits_count + if destination_ip in tmp_accounting_values: + tmp_accounting_values[destination_ip]['lan-rx'] += bits_count elif self._address_part_of_local_network(source_ip) and \ not self._address_part_of_local_network(destination_ip): # WAN TX - if source_ip in accounting_values: - accounting_values[source_ip]['wan-tx'] += bits_count + if source_ip in tmp_accounting_values: + tmp_accounting_values[source_ip]['wan-tx'] += bits_count elif not self._address_part_of_local_network(source_ip) and \ self._address_part_of_local_network(destination_ip): # WAN RX - if destination_ip in accounting_values: - accounting_values[destination_ip]['wan-rx'] += bits_count + if destination_ip in tmp_accounting_values: + tmp_accounting_values[destination_ip]['wan-rx'] += bits_count else: _LOGGER.debug(f"Skipping packet from {source_ip} to {destination_ip}") continue # Now that we have sum of all traffic in bytes for given period # calculate real throughput and transform it to appropriate unit - for addr in accounting_values: + for addr in tmp_accounting_values: self.data['accounting'][addr]['wan-tx'] = round( - accounting_values[addr]['wan-tx'] / time_diff * traffic_div, 2) + tmp_accounting_values[addr]['wan-tx'] / time_diff * traffic_div, 2) self.data['accounting'][addr]['wan-rx'] = round( - 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(): self.data['accounting'][addr]['lan-tx'] = round( - accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) + tmp_accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) self.data['accounting'][addr]['lan-rx'] = round( - accounting_values[addr]['lan-rx'] / time_diff * traffic_div, 2) + tmp_accounting_values[addr]['lan-rx'] / time_diff * traffic_div, 2) + else: + # If local traffic was enabled earlier and then disabled return counters for LAN traffic to 0 + if 'lan-tx' in self.data['accounting'][addr]: + self.data['accounting'][addr]['lan-tx'] = 0.0 + if 'lan-rx' in self.data['accounting'][addr]: + self.data['accounting'][addr]['lan-rx'] = 0.0 else: # No time diff, just initialize/return counters to 0 for all - for addr in accounting_values: + for addr in tmp_accounting_values: self.data['accounting'][addr]['wan-tx'] = 0.0 self.data['accounting'][addr]['wan-rx'] = 0.0 From ca39617aaf034fbaae882ddd083bb2da90ac659c Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 23:15:53 +0200 Subject: [PATCH 08/12] Update docs and comments --- README.md | 13 ++++++++++++- .../mikrotik_router/mikrotikapi.py | 2 +- docs/assets/images/ui/accounting_sensor.jpg | Bin 0 -> 15030 bytes 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 docs/assets/images/ui/accounting_sensor.jpg diff --git a/README.md b/README.md index a2ccf8c..af9edfc 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Features: * System sensors (CPU, Memory, HDD) * Firmware update binary sensor * Switches to run scripts - * RX/TX traffic sensors per hosts from Mikrotik Accounting + * RX/TX WAN/LAN traffic sensors per hosts from Mikrotik Accounting feature # Integration preview ![Tracker and sensors](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/device_tracker.png) @@ -32,6 +32,8 @@ Features: ![NAT switch](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/nat.png) ![Queue switch](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/queue_switch.png) +![Accounting sensor](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/accounting_sensor.png) + # Setup integration Setup this integration for your Mikrotik device in Home Assistant via `Configuration -> Integrations -> Add -> Mikrotik Router`. You can add this integration several times for different devices. @@ -50,3 +52,12 @@ You can add this integration several times for different devices. ## List of detected devices ![Integration options](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/integration_devices.png) + +## Accounting +For per-IP throughput tracking Mikrotik's accounting feature is used. + +[Mikrotik support page](https://wiki.mikrotik.com/wiki/Manual:IP/Accounting) + +Before setting up integration in HA, go in Winbox IP-Accounting and setup the feature. Make sure that threshold is set to resonable value to store all connections between user defined scan interval. Max value is 8192 so for piece of mind i recommend setting that value. Web Access is not needed, integration is using API access. + +Integration will scan DHCP Lease table and ARP table to generate all known hosts and create two sensors for WAN traffic (mikrotik-XXX-wan-rx and mikrotik-XXX-wan-tx). If the parameter *account-local-traffic* is set in Mikrotik's accounting configuration it will also create two sensors for LAN traffic (mikrotik-XXX-lan-rx and mikrotik-XXX-lan-tx). \ No newline at end of file diff --git a/custom_components/mikrotik_router/mikrotikapi.py b/custom_components/mikrotik_router/mikrotikapi.py index dd69141..bcf1c83 100644 --- a/custom_components/mikrotik_router/mikrotikapi.py +++ b/custom_components/mikrotik_router/mikrotikapi.py @@ -514,7 +514,7 @@ class MikrotikAPI: # --------------------------- # take_accounting_snapshot - # Returns float -> seconds period between last run and current run + # Returns float -> period in seconds between last and current run # --------------------------- def take_accounting_snapshot(self) -> float: """Get accounting data""" diff --git a/docs/assets/images/ui/accounting_sensor.jpg b/docs/assets/images/ui/accounting_sensor.jpg new file mode 100644 index 0000000000000000000000000000000000000000..10583ed1646ce7f3cb101663e922aaba1193668e GIT binary patch literal 15030 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 literal 0 HcmV?d00001 From 30bf7be151908363e610c3514258aca2b3905418 Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 23:17:36 +0200 Subject: [PATCH 09/12] Update docs --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index af9edfc..8ce71c5 100644 --- a/README.md +++ b/README.md @@ -60,4 +60,9 @@ For per-IP throughput tracking Mikrotik's accounting feature is used. Before setting up integration in HA, go in Winbox IP-Accounting and setup the feature. Make sure that threshold is set to resonable value to store all connections between user defined scan interval. Max value is 8192 so for piece of mind i recommend setting that value. Web Access is not needed, integration is using API access. -Integration will scan DHCP Lease table and ARP table to generate all known hosts and create two sensors for WAN traffic (mikrotik-XXX-wan-rx and mikrotik-XXX-wan-tx). If the parameter *account-local-traffic* is set in Mikrotik's accounting configuration it will also create two sensors for LAN traffic (mikrotik-XXX-lan-rx and mikrotik-XXX-lan-tx). \ No newline at end of file +Integration will scan DHCP Lease table and ARP table to generate all known hosts and create two sensors for WAN traffic (mikrotik-XXX-wan-rx and mikrotik-XXX-wan-tx). If the parameter *account-local-traffic* is set in Mikrotik's accounting configuration it will also create two sensors for LAN traffic (mikrotik-XXX-lan-rx and mikrotik-XXX-lan-tx). +Device's name will be determined by first available string this order: +1. DHCP lease comment +2. DNS static entry +3. DHCP hostname +4. Device's IP address From e8c3b3750d3c588743ce0b6e3ca936fb4fd08d6e Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 23:18:04 +0200 Subject: [PATCH 10/12] Update docs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8ce71c5..593e635 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ For per-IP throughput tracking Mikrotik's accounting feature is used. Before setting up integration in HA, go in Winbox IP-Accounting and setup the feature. Make sure that threshold is set to resonable value to store all connections between user defined scan interval. Max value is 8192 so for piece of mind i recommend setting that value. Web Access is not needed, integration is using API access. Integration will scan DHCP Lease table and ARP table to generate all known hosts and create two sensors for WAN traffic (mikrotik-XXX-wan-rx and mikrotik-XXX-wan-tx). If the parameter *account-local-traffic* is set in Mikrotik's accounting configuration it will also create two sensors for LAN traffic (mikrotik-XXX-lan-rx and mikrotik-XXX-lan-tx). + Device's name will be determined by first available string this order: 1. DHCP lease comment 2. DNS static entry From 8767557e8413fa21c4c1d1e99f9e78df9cb5ee02 Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 23:21:23 +0200 Subject: [PATCH 11/12] Update docs --- README.md | 1 + docs/assets/images/ui/setup_integration.PNG | Bin 0 -> 21576 bytes docs/assets/images/ui/setup_integration.png | Bin 17785 -> 0 bytes 3 files changed, 1 insertion(+) create mode 100644 docs/assets/images/ui/setup_integration.PNG delete mode 100644 docs/assets/images/ui/setup_integration.png diff --git a/README.md b/README.md index 593e635..321448f 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ You can add this integration several times for different devices. * "Port" - Leave at 0 for defaults * "Name of the integration" - Friendy name for this router * "Unit of measurement" - Traffic sensor measurement (bps, Kbps, Mbps, B/s, KB/s, MB/s) +* "Track accounting" - Determines if integration will track per-host throughput. Accounting must be enabled in Mikrotik first # Configuration ![Integration options](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/integration_options.png) diff --git a/docs/assets/images/ui/setup_integration.PNG b/docs/assets/images/ui/setup_integration.PNG new file mode 100644 index 0000000000000000000000000000000000000000..42958f953ba350c13ad9d7b5ad9ed4354c8fd3ce GIT binary patch literal 21576 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 UtMY0R@bpgxPgg&ebxsLQ0LPhTLjV8( literal 0 HcmV?d00001 diff --git a/docs/assets/images/ui/setup_integration.png b/docs/assets/images/ui/setup_integration.png deleted file mode 100644 index 2eada0de0d01f7a1d5a10220e5626a7f95582244..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17785 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 From 2c577116f9d6d8d6a69d1d82e46e42b6cd56b66e Mon Sep 17 00:00:00 2001 From: Ivan Pavlina Date: Sun, 5 Apr 2020 23:23:07 +0200 Subject: [PATCH 12/12] Removed unnecessary logging --- custom_components/mikrotik_router/mikrotik_controller.py | 3 --- custom_components/mikrotik_router/sensor.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index ca63ce7..63fad91 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -810,9 +810,6 @@ class MikrotikControllerData: # WAN RX if destination_ip in tmp_accounting_values: tmp_accounting_values[destination_ip]['wan-rx'] += bits_count - else: - _LOGGER.debug(f"Skipping packet from {source_ip} to {destination_ip}") - continue # Now that we have sum of all traffic in bytes for given period # calculate real throughput and transform it to appropriate unit diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 3ee1376..5880425 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -202,8 +202,7 @@ def update_items(inst, mikrotik_controller, async_add_entities, sensors): uid=uid, ) new_sensors.append(sensors[item_id]) - else: - _LOGGER.info(f"WONT CREATE {SENSOR_TYPES[sensor][ATTR_ATTR]} for {item_id}") + if new_sensors: async_add_entities(new_sensors, True)