diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index ef7678d..c11ff4e 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -69,14 +69,11 @@ class MikrotikControllerData: "accounting": {} } - self.local_dhcp_networks = [] - self.listeners = [] self.lock = asyncio.Lock() self.api = MikrotikAPI(host, username, password, port, use_ssl) - self.raw_arp_entries = [] self.nat_removed = {} async_track_time_interval( @@ -209,7 +206,7 @@ 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) - await self.hass.async_add_executor_job(self.get_dhcp) + # await self.hass.async_add_executor_job(self.get_dhcp) await self.hass.async_add_executor_job(self.get_accounting) async_dispatcher_send(self.hass, self.signal_update) @@ -354,7 +351,6 @@ class MikrotikControllerData: def update_arp(self, mac2ip, bridge_used): """Get list of hosts in ARP for interface client data from Mikrotik""" data = self.api.path("/ip/arp") - self.raw_arp_entries = data if not data: return mac2ip, bridge_used @@ -856,7 +852,7 @@ class MikrotikControllerData: def _address_part_of_local_network(self, address): address = ip_address(address) - for network in self.local_dhcp_networks: + for network in self.data['dhcp-networks']: if address in network: return True return False @@ -874,80 +870,36 @@ class MikrotikControllerData: traffic_type, traffic_div = self._get_traffic_type_and_div() 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(): - self.data['accounting'][mac]["tx-rx-attr"] = traffic_type - if 'wan-tx' in vals: - self.data["accounting"][mac]['wan-tx'] = 0.0 - if 'wan-rx' in vals: - self.data["accounting"][mac]['wan-rx'] = 0.0 - if 'lan-tx' in vals: - self.data["accounting"][mac]['lan-tx'] = 0.0 - if 'lan-rx' in vals: - self.data["accounting"][mac]['lan-rx'] = 0.0 + # If any hosts were created set them as unavailable + for uid, vals in self.data["accounting"].items(): + self.data['accounting'][uid]["tx-rx-attr"] = traffic_type + self.data['accounting'][uid]["available"] = False + self.data['accounting'][uid]["local_accounting"] = False return - # Build missing hosts from already retrieved DHCP Server leases - for mac, vals in self.data["dhcp"].items(): - if mac not in self.data["accounting"]: - self.data["accounting"][mac] = { + # Build missing hosts from main hosts dict + for uid, vals in self.data["host"].items(): + if uid not in self.data["accounting"]: + self.data["accounting"][uid] = { 'address': vals['address'], 'mac-address': vals['mac-address'], 'host-name': vals['host-name'], - 'comment': vals['comment'] + 'tx-rx-attr': traffic_type, + 'available': False, + 'local_accounting': False } - # Build missing hosts from already retrieved ARP list - host_update_from_arp = False - 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'], - 'host-name': '', - 'comment': '' - } - host_update_from_arp = True - - # If some host was added from ARP table build new host-name for it from static DNS entry. Fallback to MAC - if host_update_from_arp: - dns_data = parse_api( - data={}, - source=self.api.path("/ip/dns/static"), - key="address", - vals=[ - {"name": "address"}, - {"name": "name"}, - ], - ) - - # Try to build hostname from DNS static entry - for mac, vals in self.data["accounting"].items(): - if not str(vals.get('host-name', '')).strip() or vals['host-name'] is 'unknown': - if vals['address'] in dns_data and str(dns_data[vals['address']].get('name', '')).strip(): - self.data["accounting"][mac]['host-name'] = dns_data[vals['address']]['name'] - - # Check if any host still have empty 'host-name'. Default it to MAC. - # Same check for 'comment' (pretty name) - for mac, vals in self.data["accounting"].items(): - if not str(vals.get('host-name', '')).strip() or vals['host-name'] is 'unknown': - self.data["accounting"][mac]['host-name'] = mac - if not str(vals.get('comment', '')).strip() or vals['host-name'] is 'comment': - self.data["accounting"][mac]['comment'] = mac - _LOGGER.debug(f"Working with {len(self.data['accounting'])} accounting devices") # Build temp accounting values dict with ip address as key - # Also set traffic type for each item tmp_accounting_values = {} - for mac, vals in self.data['accounting'].items(): + for uid, vals in self.data['accounting'].items(): tmp_accounting_values[vals['address']] = { "wan-tx": 0, "wan-rx": 0, "lan-tx": 0, "lan-rx": 0 } - self.data['accounting'][mac]["tx-rx-attr"] = traffic_type time_diff = self.api.take_accounting_snapshot() if time_diff: @@ -984,42 +936,35 @@ class MikrotikControllerData: # WAN RX if destination_ip in tmp_accounting_values: tmp_accounting_values[destination_ip]['wan-rx'] += bits_count + else: + # No data to calculate, publish accounting as unavailable + accounting_enabled = False - # 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 tmp_accounting_values: - mac = self._get_accounting_mac_by_ip(addr) - if not mac: - _LOGGER.debug(f"Address {addr} not found in accounting data, skipping update") - continue + # Calculate real throughput and transform it to appropriate unit + # Also handle availability of accounting and local_accounting from Mikrotik + for addr in tmp_accounting_values: + uid = self._get_accounting_mac_by_ip(addr) + if not uid: + _LOGGER.warning(f"Address {addr} not found in accounting data, skipping update") + continue - self.data['accounting'][mac]['wan-tx'] = round( + self.data['accounting'][uid]['tx-rx-attr'] = traffic_type + self.data['accounting'][uid]['available'] = accounting_enabled + self.data['accounting'][uid]['local_accounting'] = local_traffic_enabled + + if not accounting_enabled: + # No data present, do not calculate throughout + continue + + if tmp_accounting_values[addr]['wan-tx']: + self.data['accounting'][uid]['wan-tx'] = round( tmp_accounting_values[addr]['wan-tx'] / time_diff * traffic_div, 2) - self.data['accounting'][mac]['wan-rx'] = round( + if tmp_accounting_values[addr]['wan-rx']: + self.data['accounting'][uid]['wan-rx'] = round( tmp_accounting_values[addr]['wan-rx'] / time_diff * traffic_div, 2) - if local_traffic_enabled: - self.data['accounting'][mac]['lan-tx'] = round( - tmp_accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) - self.data['accounting'][mac]['lan-rx'] = round( - 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'][mac]: - self.data['accounting'][mac]['lan-tx'] = 0.0 - if 'lan-rx' in self.data['accounting'][mac]: - self.data['accounting'][mac]['lan-rx'] = 0.0 - else: - # No time diff, just initialize/return counters to 0 for all - for addr in tmp_accounting_values: - mac = self._get_accounting_mac_by_ip(addr) - if not mac: - _LOGGER.debug(f"Address {addr} not found in accounting data, skipping update") - continue - - self.data['accounting'][mac]['wan-tx'] = 0.0 - self.data['accounting'][mac]['wan-rx'] = 0.0 - - if local_traffic_enabled: - self.data['accounting'][mac]['lan-tx'] = 0.0 - self.data['accounting'][mac]['lan-rx'] = 0.0 + if local_traffic_enabled: + self.data['accounting'][uid]['lan-tx'] = round( + tmp_accounting_values[addr]['lan-tx'] / time_diff * traffic_div, 2) + self.data['accounting'][uid]['lan-rx'] = round( + tmp_accounting_values[addr]['lan-rx'] / time_diff * traffic_div, 2) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 1ddf128..70c00c2 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -378,6 +378,16 @@ class MikrotikAccountingSensor(MikrotikControllerSensor): """Return a unique_id for this entity.""" return f"{self._inst.lower()}-{self._sensor.lower()}-{self._data['mac-address'].lower()}" + @property + def available(self) -> bool: + """Return if controller and accounting feature in Mikrotik is available. + Additional check for lan-tx/rx sensors + """ + if self._sensor in ['lan-tx, lan-rx']: + return self._ctrl.connected() and self._data['available'] and self._data['local_accounting'] + else: + return self._ctrl.connected() and self._data['available'] + @property def device_info(self): """Return a accounting description for device registry."""