From f6bcfcb52814674c86a0a44350348f8985389574 Mon Sep 17 00:00:00 2001 From: tomaae <23486452+tomaae@users.noreply.github.com> Date: Sun, 8 Dec 2019 11:20:02 +0100 Subject: [PATCH] added sensors for rx/tx traffic per interface --- .../mikrotik_router/mikrotik_controller.py | 29 ++++++++ .../mikrotik_router/mikrotikapi.py | 33 +++++++++ custom_components/mikrotik_router/sensor.py | 69 +++++++++++++++++++ 3 files changed, 131 insertions(+) diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index 190de86..73b66ae 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -62,6 +62,7 @@ class MikrotikControllerData(): self.data = {'routerboard': {}, 'resource': {}, 'interface': {}, + 'interface_map': {}, 'arp': {}, 'nat': {}, 'fw-update': {}, @@ -193,6 +194,7 @@ class MikrotikControllerData(): # --------------------------- def get_interface(self): """Get all interfaces data from Mikrotik""" + interface_list = "" data = self.api.path("/interface") for entry in data: if 'default-name' not in entry: @@ -214,6 +216,13 @@ class MikrotikControllerData(): self.data['interface'][uid]['link-downs'] = from_entry(entry, 'link-downs') self.data['interface'][uid]['tx-queue-drop'] = from_entry(entry, 'tx-queue-drop') self.data['interface'][uid]['actual-mtu'] = from_entry(entry, 'actual-mtu') + + self.data['interface_map'][self.data['interface'][uid]['name']] = self.data['interface'][uid]['default-name'] + + if interface_list: + interface_list += "," + + interface_list += self.data['interface'][uid]['name'] if 'client-ip-address' not in self.data['interface'][uid]: self.data['interface'][uid]['client-ip-address'] = "" @@ -221,6 +230,26 @@ class MikrotikControllerData(): if 'client-mac-address' not in self.data['interface'][uid]: self.data['interface'][uid]['client-mac-address'] = "" + if 'rx-bits-per-second' not in self.data['interface'][uid]: + self.data['interface'][uid]['rx-bits-per-second'] = 0 + + if 'tx-bits-per-second' not in self.data['interface'][uid]: + self.data['interface'][uid]['tx-bits-per-second'] = 0 + + self.get_interface_traffic(interface_list) + + return + + # --------------------------- + # get_interface_traffic + # --------------------------- + def get_interface_traffic(self, interface_list): + data = self.api.get_traffic(interface_list) + for entry in data: + uid = self.data['interface_map'][from_entry(entry, 'name')] + self.data['interface'][uid]['rx-bits-per-second'] = from_entry(entry, 'rx-bits-per-second', default=0) + self.data['interface'][uid]['tx-bits-per-second'] = from_entry(entry, 'tx-bits-per-second', default=0) + return # --------------------------- diff --git a/custom_components/mikrotik_router/mikrotikapi.py b/custom_components/mikrotik_router/mikrotikapi.py index 0be5a0e..4d8ef67 100644 --- a/custom_components/mikrotik_router/mikrotikapi.py +++ b/custom_components/mikrotik_router/mikrotikapi.py @@ -231,3 +231,36 @@ class MikrotikAPI: raise ApiEntryNotFound(error) return True + + # --------------------------- + # get_traffic + # --------------------------- + def get_traffic(self, interfaces): + """Get traffic stats""" + traffic = None + if not self._connected or not self._connection: + if not self.connect(): + return None + + response = self.path('/interface') + if response is None: + return None + + args = {'interface': interfaces, 'once': True} + try: + traffic = response('monitor-traffic', **args) + except librouteros.exceptions.ConnectionClosed: + _LOGGER.error("Mikrotik %s connection closed", self._host) + self._connected = False + self._connection = None + return None + except ( + librouteros.exceptions.TrapError, + librouteros.exceptions.MultiTrapError, + librouteros.exceptions.ProtocolError, + librouteros.exceptions.FatalError + ) as api_error: + _LOGGER.error("Mikrotik %s connection error %s", self._host, api_error) + return None + + return traffic diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index fd69dcb..d5ebdfb 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -2,6 +2,7 @@ import logging from homeassistant.core import callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from homeassistant.const import ( @@ -53,6 +54,22 @@ SENSOR_TYPES = { ATTR_PATH: "resource", ATTR_ATTR: "hdd-usage", }, + 'traffic_tx': { + ATTR_DEVICE_CLASS: None, + ATTR_ICON: "mdi:upload-network-outline", + ATTR_LABEL: 'TX bps', + ATTR_UNIT: "bps", + ATTR_PATH: "interface", + ATTR_ATTR: "tx-bits-per-second", + }, + 'traffic_rx': { + ATTR_DEVICE_CLASS: None, + ATTR_ICON: "mdi:download-network-outline", + ATTR_LABEL: 'RX bps', + ATTR_UNIT: "bps", + ATTR_PATH: "interface", + ATTR_ATTR: "rx-bits-per-second", + }, } @@ -87,6 +104,9 @@ def update_items(inst, mikrotik_controller, async_add_entities, sensors): new_sensors = [] for sensor in SENSOR_TYPES: + if "traffic_" in sensor: + continue + item_id = "{}-{}".format(inst, sensor) if item_id in sensors: if sensors[item_id].enabled: @@ -96,6 +116,21 @@ def update_items(inst, mikrotik_controller, async_add_entities, sensors): sensors[item_id] = MikrotikControllerSensor(mikrotik_controller=mikrotik_controller, inst=inst, sensor=sensor) new_sensors.append(sensors[item_id]) + for sensor in SENSOR_TYPES: + if "traffic_" not in sensor: + continue + + for uid in mikrotik_controller.data['interface']: + if mikrotik_controller.data['interface'][uid]['type'] == "ether": + item_id = "{}-{}-{}".format(inst, sensor, mikrotik_controller.data['interface'][uid]['default-name']) + if item_id in sensors: + if sensors[item_id].enabled: + sensors[item_id].async_schedule_update_ha_state() + continue + + sensors[item_id] = MikrotikControllerTrafficSensor(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) @@ -187,3 +222,37 @@ class MikrotikControllerSensor(Entity): """Port entity created.""" _LOGGER.debug("New sensor %s (%s)", self._inst, self._sensor) return + + +# --------------------------- +# MikrotikControllerTrafficSensor +# --------------------------- +class MikrotikControllerTrafficSensor(MikrotikControllerSensor): + """Define an Mikrotik Controller 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 "{} {} {}".format(self._inst, self._data['name'], self._type[ATTR_LABEL]) + + @property + def unique_id(self): + """Return a unique_id for this entity.""" + return "{}-{}-{}".format(self._inst.lower(), self._sensor.lower(), self._data['name'].lower()) + + @property + def device_info(self): + """Return a port description for device registry.""" + info = { + "connections": {(CONNECTION_NETWORK_MAC, self._data['port-mac-address'])}, + "manufacturer": self._ctrl.data['resource']['platform'], + "model": self._ctrl.data['resource']['board-name'], + "name": self._data['default-name'], + } + return info