From 123044fceba533a9f1e4000fc70bb44cd5529e57 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Tue, 1 Feb 2022 10:11:42 +0100 Subject: [PATCH 01/36] cleanup integration init --- custom_components/mikrotik_router/__init__.py | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/custom_components/mikrotik_router/__init__.py b/custom_components/mikrotik_router/__init__.py index 7054683..a6c91b5 100644 --- a/custom_components/mikrotik_router/__init__.py +++ b/custom_components/mikrotik_router/__init__.py @@ -1,16 +1,10 @@ """Mikrotik Router integration.""" -import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.const import ( - CONF_NAME, - CONF_HOST, -) - from .const import ( PLATFORMS, DOMAIN, @@ -19,8 +13,6 @@ from .const import ( ) from .mikrotik_controller import MikrotikControllerData -_LOGGER = logging.getLogger(__name__) - SCRIPT_SCHEMA = vol.Schema( {vol.Required("router"): cv.string, vol.Required("script"): cv.string} ) @@ -69,24 +61,6 @@ async def async_setup_entry(hass, config_entry) -> bool: DOMAIN, RUN_SCRIPT_COMMAND, controller.run_script, schema=SCRIPT_SCHEMA ) - device_registry = await hass.helpers.device_registry.async_get_registry() - device_registry.async_get_or_create( - config_entry_id=config_entry.entry_id, - connections={(DOMAIN, f"{controller.data['routerboard']['serial-number']}")}, - manufacturer=controller.data["resource"]["platform"], - model=controller.data["routerboard"]["model"], - name=f"{config_entry.data[CONF_NAME]} {controller.data['routerboard']['model']}", - sw_version=controller.data["resource"]["version"], - configuration_url=f"http://{config_entry.data[CONF_HOST]}", - identifiers={ - DOMAIN, - "serial-number", - f"{controller.data['routerboard']['serial-number']}", - "sensor", - f"{config_entry.data[CONF_NAME]} {controller.data['routerboard']['model']}", - }, - ) - return True From 74b46ed5622d08abb7efad8881088ac1fac97d78 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Tue, 1 Feb 2022 10:12:38 +0100 Subject: [PATCH 02/36] redo system sensors --- custom_components/mikrotik_router/sensor.py | 530 ++++-------------- .../mikrotik_router/sensor_types.py | 329 +++++++++++ 2 files changed, 450 insertions(+), 409 deletions(-) create mode 100644 custom_components/mikrotik_router/sensor_types.py diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 0a57caf..66a2537 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -1,19 +1,17 @@ -"""Support for the Mikrotik Router sensor service.""" +"""Implementation of Mikrotik Router sensor entities.""" import logging + from typing import Any, Dict, Optional from homeassistant.const import ( CONF_NAME, CONF_HOST, ATTR_ATTRIBUTION, - ATTR_DEVICE_CLASS, - TEMP_CELSIUS, - ELECTRIC_POTENTIAL_VOLT, ) -from homeassistant.helpers.entity import EntityCategory -from homeassistant.components.sensor import SensorDeviceClass +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.components.sensor import SensorEntity from .const import ( CONF_SENSOR_PORT_TRAFFIC, @@ -21,11 +19,15 @@ from .const import ( ) from homeassistant.core import callback -from homeassistant.components.sensor import SensorEntity from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DOMAIN, DATA_CLIENT, ATTRIBUTION +from .sensor_types import ( + MikrotikSensorEntityDescription, + SENSOR_TYPES, + DEVICE_ATTRIBUTES_CLIENT_TRAFFIC, +) _LOGGER = logging.getLogger(__name__) @@ -46,191 +48,6 @@ def format_attribute(attr): return res -ATTR_ICON = "icon" -ATTR_LABEL = "label" -ATTR_UNIT = "unit" -ATTR_UNIT_ATTR = "unit_attr" -ATTR_GROUP = "group" -ATTR_PATH = "data_path" -ATTR_ATTR = "data_attr" -ATTR_CTGR = "entity_category" - -SENSOR_TYPES = { - "system_temperature": { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ICON: "mdi:thermometer", - ATTR_LABEL: "Temperature", - ATTR_UNIT: TEMP_CELSIUS, - ATTR_GROUP: "System", - ATTR_PATH: "health", - ATTR_ATTR: "temperature", - ATTR_CTGR: None, - }, - "system_voltage": { - ATTR_DEVICE_CLASS: SensorDeviceClass.VOLTAGE, - ATTR_ICON: "mdi:lightning-bolt", - ATTR_LABEL: "Voltage", - ATTR_UNIT: ELECTRIC_POTENTIAL_VOLT, - ATTR_GROUP: "System", - ATTR_PATH: "health", - ATTR_ATTR: "voltage", - ATTR_CTGR: EntityCategory.DIAGNOSTIC, - }, - "system_cpu-temperature": { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ICON: "mdi:thermometer", - ATTR_LABEL: "CPU temperature", - ATTR_UNIT: TEMP_CELSIUS, - ATTR_GROUP: "System", - ATTR_PATH: "health", - ATTR_ATTR: "cpu-temperature", - ATTR_CTGR: None, - }, - "system_board-temperature1": { - ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE, - ATTR_ICON: "mdi:thermometer", - ATTR_LABEL: "Board temperature", - ATTR_UNIT: TEMP_CELSIUS, - ATTR_GROUP: "System", - ATTR_PATH: "health", - ATTR_ATTR: "board-temperature1", - ATTR_CTGR: None, - }, - "system_power-consumption": { - ATTR_DEVICE_CLASS: SensorDeviceClass.POWER, - ATTR_ICON: "mdi:transmission-tower", - ATTR_LABEL: "Power consumption", - ATTR_UNIT: "W", - ATTR_GROUP: "System", - ATTR_PATH: "health", - ATTR_ATTR: "power-consumption", - ATTR_CTGR: None, - }, - "system_fan1-speed": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:fan", - ATTR_LABEL: "Fan1 speed", - ATTR_UNIT: "RPM", - ATTR_GROUP: "System", - ATTR_PATH: "health", - ATTR_ATTR: "fan1-speed", - ATTR_CTGR: None, - }, - "system_fan2-speed": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:fan", - ATTR_LABEL: "Fan2 speed", - ATTR_UNIT: "RPM", - ATTR_GROUP: "System", - ATTR_PATH: "health", - ATTR_ATTR: "fan2-speed", - ATTR_CTGR: None, - }, - "system_uptime": { - ATTR_DEVICE_CLASS: SensorDeviceClass.TIMESTAMP, - ATTR_ICON: None, - ATTR_LABEL: "Uptime", - ATTR_UNIT: None, - ATTR_GROUP: "System", - ATTR_PATH: "resource", - ATTR_ATTR: "uptime", - ATTR_CTGR: EntityCategory.DIAGNOSTIC, - }, - "system_cpu-load": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:speedometer", - ATTR_LABEL: "CPU load", - ATTR_UNIT: "%", - ATTR_GROUP: "System", - ATTR_PATH: "resource", - ATTR_ATTR: "cpu-load", - ATTR_CTGR: None, - }, - "system_memory-usage": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:memory", - ATTR_LABEL: "Memory usage", - ATTR_UNIT: "%", - ATTR_GROUP: "System", - ATTR_PATH: "resource", - ATTR_ATTR: "memory-usage", - ATTR_CTGR: None, - }, - "system_hdd-usage": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:harddisk", - ATTR_LABEL: "HDD usage", - ATTR_UNIT: "%", - ATTR_GROUP: "System", - ATTR_PATH: "resource", - ATTR_ATTR: "hdd-usage", - ATTR_CTGR: None, - }, - "traffic_tx": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:upload-network-outline", - ATTR_LABEL: "TX", - ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "tx-bits-per-second-attr", - ATTR_PATH: "interface", - ATTR_ATTR: "tx-bits-per-second", - ATTR_CTGR: None, - }, - "traffic_rx": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:download-network-outline", - ATTR_LABEL: "RX", - ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "rx-bits-per-second-attr", - ATTR_PATH: "interface", - ATTR_ATTR: "rx-bits-per-second", - ATTR_CTGR: None, - }, - "client_traffic_lan_tx": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:upload-network", - ATTR_LABEL: "LAN TX", - ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "tx-rx-attr", - ATTR_PATH: "client_traffic", - ATTR_ATTR: "lan-tx", - ATTR_CTGR: None, - }, - "client_traffic_lan_rx": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:download-network", - ATTR_LABEL: "LAN RX", - ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "tx-rx-attr", - ATTR_PATH: "client_traffic", - ATTR_ATTR: "lan-rx", - ATTR_CTGR: None, - }, - "client_traffic_wan_tx": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:upload-network", - ATTR_LABEL: "WAN TX", - ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "tx-rx-attr", - ATTR_PATH: "client_traffic", - ATTR_ATTR: "wan-tx", - ATTR_CTGR: None, - }, - "client_traffic_wan_rx": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:download-network", - ATTR_LABEL: "WAN RX", - ATTR_UNIT: "ps", - ATTR_UNIT_ATTR: "tx-rx-attr", - ATTR_PATH: "client_traffic", - ATTR_ATTR: "wan-rx", - ATTR_CTGR: None, - }, -} - -DEVICE_ATTRIBUTES_CLIENT_TRAFFIC = ["address", "mac-address", "host-name"] - - # --------------------------- # async_setup_entry # --------------------------- @@ -264,53 +81,37 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se """Update sensor state from the controller.""" new_sensors = [] - for sid, sid_uid, sid_name, sid_val, sid_ref, sid_attr, sid_func in zip( + for sensor, sid_func in zip( # Data point name ["environment"], - # Data point unique id - ["name"], - # Entry Name - ["name"], - # Entry Value - ["value"], - # Entry Unique id - ["name"], - # Attr - [ - None, - ], # Switch function [ - MikrotikControllerEnvironmentSensor, + MikrotikControllerSensor, ], ): - for uid in mikrotik_controller.data[sid]: - item_id = f"{inst}-{sid}-{mikrotik_controller.data[sid][uid][sid_uid]}" + for uid in mikrotik_controller.data[sensor]: + item_id = f"{inst}-{sensor}-{mikrotik_controller.data[sensor][uid][SENSOR_TYPES[sensor].data_uid]}" _LOGGER.debug("Updating sensor %s", item_id) if item_id in sensors: if sensors[item_id].enabled: sensors[item_id].async_schedule_update_ha_state() continue - # Create new entity - sid_data = { - "sid": sid, - "sid_uid": sid_uid, - "sid_name": sid_name, - "sid_ref": sid_ref, - "sid_attr": sid_attr, - "sid_val": sid_val, - } - sensors[item_id] = sid_func(inst, uid, mikrotik_controller, sid_data) + sensors[item_id] = sid_func( + inst=inst, + uid=uid, + mikrotik_controller=mikrotik_controller, + entity_description=SENSOR_TYPES[sensor], + ) new_sensors.append(sensors[item_id]) for sensor in SENSOR_TYPES: if sensor.startswith("system_"): if ( - SENSOR_TYPES[sensor][ATTR_ATTR] - not in mikrotik_controller.data[SENSOR_TYPES[sensor][ATTR_PATH]] - or mikrotik_controller.data[SENSOR_TYPES[sensor][ATTR_PATH]][ - SENSOR_TYPES[sensor][ATTR_ATTR] + SENSOR_TYPES[sensor].data_attribute + not in mikrotik_controller.data[SENSOR_TYPES[sensor].data_path] + or mikrotik_controller.data[SENSOR_TYPES[sensor].data_path][ + SENSOR_TYPES[sensor].data_attribute ] == "unknown" ): @@ -323,52 +124,55 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se continue sensors[item_id] = MikrotikControllerSensor( - mikrotik_controller=mikrotik_controller, inst=inst, sid_data=sensor + inst=inst, + uid="", + mikrotik_controller=mikrotik_controller, + entity_description=SENSOR_TYPES[sensor], ) new_sensors.append(sensors[item_id]) - if sensor.startswith("traffic_"): - if not config_entry.options.get( - CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC - ): - continue - - for uid in mikrotik_controller.data["interface"]: - if mikrotik_controller.data["interface"][uid]["type"] != "bridge": - item_id = f"{inst}-{sensor}-{mikrotik_controller.data['interface'][uid]['default-name']}" - _LOGGER.debug("Updating sensor %s", item_id) - 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 sensor.startswith("client_traffic_"): - for uid in mikrotik_controller.data["client_traffic"]: - item_id = f"{inst}-{sensor}-{mikrotik_controller.data['client_traffic'][uid]['mac-address']}" - if item_id in sensors: - if sensors[item_id].enabled: - sensors[item_id].async_schedule_update_ha_state() - continue - - if ( - SENSOR_TYPES[sensor][ATTR_ATTR] - in mikrotik_controller.data["client_traffic"][uid].keys() - ): - sensors[item_id] = MikrotikClientTrafficSensor( - mikrotik_controller=mikrotik_controller, - inst=inst, - sensor=sensor, - uid=uid, - ) - new_sensors.append(sensors[item_id]) + # if sensor.startswith("traffic_"): + # if not config_entry.options.get( + # CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC + # ): + # continue + # + # for uid in mikrotik_controller.data["interface"]: + # if mikrotik_controller.data["interface"][uid]["type"] != "bridge": + # item_id = f"{inst}-{sensor}-{mikrotik_controller.data['interface'][uid]['default-name']}" + # _LOGGER.debug("Updating sensor %s", item_id) + # 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_TYPES[sensor], + # uid=uid, + # ) + # new_sensors.append(sensors[item_id]) + # + # if sensor.startswith("client_traffic_"): + # for uid in mikrotik_controller.data["client_traffic"]: + # item_id = f"{inst}-{sensor}-{mikrotik_controller.data['client_traffic'][uid]['mac-address']}" + # if item_id in sensors: + # if sensors[item_id].enabled: + # sensors[item_id].async_schedule_update_ha_state() + # continue + # + # if ( + # SENSOR_TYPES[sensor].data_attribute + # in mikrotik_controller.data["client_traffic"][uid].keys() + # ): + # sensors[item_id] = MikrotikClientTrafficSensor( + # mikrotik_controller=mikrotik_controller, + # inst=inst, + # sensor=SENSOR_TYPES[sensor], + # uid=uid, + # ) + # new_sensors.append(sensors[item_id]) if new_sensors: async_add_entities(new_sensors, True) @@ -380,76 +184,49 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se class MikrotikControllerSensor(SensorEntity): """Define an Mikrotik Controller sensor.""" - def __init__(self, mikrotik_controller, inst, sid_data): + def __init__( + self, + mikrotik_controller, + inst, + uid: "", + entity_description: MikrotikSensorEntityDescription, + ): """Initialize.""" self._inst = inst - self._sensor = sid_data + self.entity_description = entity_description self._ctrl = mikrotik_controller - - if sid_data in SENSOR_TYPES: - self._data = mikrotik_controller.data[SENSOR_TYPES[sid_data][ATTR_PATH]] - self._type = SENSOR_TYPES[sid_data] - self._icon = self._type[ATTR_ICON] - self._attr = self._type[ATTR_ATTR] - self._dcls = self._type[ATTR_DEVICE_CLASS] - self._ctgr = self._type[ATTR_CTGR] + self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION} + self._uid = uid + if self._uid: + self._data = mikrotik_controller.data[self.entity_description.data_path][ + self._uid + ] else: - self._type = {} - self._icon = None - self._attr = None - self._dcls = None - self._ctgr = None - - self._state = None - self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} + self._data = mikrotik_controller.data[self.entity_description.data_path] @property def name(self) -> str: """Return the name.""" - return f"{self._inst} {self._type[ATTR_LABEL]}" - - @property - def state(self) -> Optional[str]: - """Return the state.""" - val = "unknown" - if self._attr in self._data: - val = self._data[self._attr] - - return val - - @property - def extra_state_attributes(self) -> Dict[str, Any]: - """Return the state attributes.""" - return self._attrs - - @property - def icon(self) -> str: - """Return the icon.""" - return self._icon - - @property - def entity_category(self) -> str: - """Return entity category""" - return self._ctgr - - @property - def device_class(self) -> Optional[str]: - """Return the device class.""" - return self._dcls + if self._uid: + return f"{self._inst} {self._data[self.entity_description.data_reference]}" + else: + return f"{self._inst} {self.entity_description.name}" @property def unique_id(self) -> str: """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self._sensor.lower()}" + if self._uid: + return f"{self._inst.lower()}-{self.entity_description.key}-{self._data[self.entity_description.data_reference].lower()}" + else: + return f"{self._inst.lower()}-{self.entity_description.key}" @property - def unit_of_measurement(self): - """Return the unit the value is expressed in.""" - if ATTR_UNIT_ATTR in self._type: - return self._data[SENSOR_TYPES[self._sensor][ATTR_UNIT_ATTR]] - - if ATTR_UNIT in self._type: - return self._type[ATTR_UNIT] + def state(self) -> Optional[str]: + """Return the state.""" + if self.entity_description.data_attribute: + return self._data[self.entity_description.data_attribute] + else: + return "unknown" @property def available(self) -> bool: @@ -457,40 +234,28 @@ class MikrotikControllerSensor(SensorEntity): return self._ctrl.connected() @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> DeviceInfo: """Return a description for device registry.""" - if self._type[ATTR_GROUP] == "System": - self._type[ATTR_GROUP] = self._ctrl.data["resource"]["board-name"] + di_domain = self.entity_description.data_reference + if self.entity_description.ha_group == "System": + self.entity_description.ha_group = self._ctrl.data["resource"]["board-name"] + di_domain = self._ctrl.data["routerboard"]["serial-number"] - info = { - "connections": { - (DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}") - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} {self._type[ATTR_GROUP]}", - "sw_version": self._ctrl.data["resource"]["version"], - "configuration_url": f"http://{self._ctrl.config_entry.data[CONF_HOST]}", - } - if ATTR_GROUP in self._type: - info["identifiers"] = { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "sensor", - f"{self._inst} {self._type[ATTR_GROUP]}", - ) - } + info = DeviceInfo( + connections={(DOMAIN, f"{di_domain}")}, + name=f"{self._inst} {self.entity_description.ha_group}", + model=f"{self._ctrl.data['resource']['board-name']}", + manufacturer=f"{self._ctrl.data['resource']['platform']}", + sw_version=f"{self._ctrl.data['resource']['version']}", + configuration_url=f"http://{self._ctrl.config_entry.data[CONF_HOST]}", + via_device=(DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}"), + ) return info - async def async_update(self): - """Synchronize state with controller.""" - async def async_added_to_hass(self): """Run when entity about to be added to hass.""" - _LOGGER.debug("New sensor %s (%s)", self._inst, self._sensor) + _LOGGER.debug("New sensor %s (%s)", self._inst, self.entity_description.name) # --------------------------- @@ -502,18 +267,19 @@ class MikrotikControllerTrafficSensor(MikrotikControllerSensor): 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] + if uid: + self._uid = uid + self._data = mikrotik_controller.data[sensor.data_path][uid] @property def name(self) -> str: """Return the name.""" - return f"{self._inst} {self._data['name']} {self._type[ATTR_LABEL]}" + return f"{self._inst} {self._data['name']} {self.entity_description.name}" @property def unique_id(self) -> str: """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self._sensor.lower()}-{self._data['default-name'].lower()}" + return f"{self._inst.lower()}-{self.entity_description.lower()}-{self._data['default-name'].lower()}" @property def state_class(self) -> str: @@ -543,17 +309,17 @@ class MikrotikClientTrafficSensor(MikrotikControllerSensor): """Initialize.""" super().__init__(mikrotik_controller, inst, sensor) self._uid = uid - self._data = mikrotik_controller.data[SENSOR_TYPES[sensor][ATTR_PATH]][uid] + self._data = mikrotik_controller.data[sensor.data_path][uid] @property def name(self) -> str: """Return the name.""" - return f"{self._data['host-name']} {self._type[ATTR_LABEL]}" + return f"{self._data['host-name']} {self.entity_description.name}" @property def unique_id(self) -> str: """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self._sensor.lower()}-{self._data['mac-address'].lower()}" + return f"{self._inst.lower()}-{self.entity_description.lower()}-{self._data['mac-address'].lower()}" @property def available(self) -> bool: @@ -590,57 +356,3 @@ class MikrotikClientTrafficSensor(MikrotikControllerSensor): attributes[format_attribute(variable)] = self._data[variable] return attributes - - -# --------------------------- -# MikrotikControllerEnvironmentSensor -# --------------------------- -class MikrotikControllerEnvironmentSensor(MikrotikControllerSensor): - """Define an Enviroment variable sensor.""" - - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - super().__init__(mikrotik_controller, inst, "") - self._uid = uid - self._sid_data = sid_data - self._data = mikrotik_controller.data[self._sid_data["sid"]][uid] - - @property - def name(self) -> str: - """Return the name.""" - return f"{self._inst} {self._data[self._sid_data['sid_ref']]}" - - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self._sid_data['sid']}-{self._data[self._sid_data['sid_ref']]}" - - @property - def state(self) -> Optional[str]: - """Return the state.""" - return self._data[self._sid_data["sid_val"]] - - @property - def icon(self) -> str: - """Return the icon.""" - return "mdi:clipboard-list" - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "identifiers": { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "sensor", - "Environment", - ) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} Environment", - } - - return info diff --git a/custom_components/mikrotik_router/sensor_types.py b/custom_components/mikrotik_router/sensor_types.py new file mode 100644 index 0000000..2c3058f --- /dev/null +++ b/custom_components/mikrotik_router/sensor_types.py @@ -0,0 +1,329 @@ +"""Definitions for Mikrotik Router sensor entities.""" +from dataclasses import dataclass +from homeassistant.helpers.entity import EntityCategory, DeviceInfo +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorStateClass, + SensorEntityDescription, +) +from homeassistant.const import ( + TEMP_CELSIUS, + ELECTRIC_POTENTIAL_VOLT, + POWER_WATT, + PERCENTAGE, +) + + +@dataclass +class MikrotikSensorEntityDescriptionAdd: + """Required values when describing secondary sensor attributes.""" + + ha_group: str + data_path: str + data_attribute: str + data_name: str + data_uid: str + data_reference: str + data_attributes_list: [] + + +@dataclass +class MikrotikSensorEntityDescription( + SensorEntityDescription, + MikrotikSensorEntityDescriptionAdd, +): + """Class describing mikrotik entities.""" + + +SENSOR_TYPES = { + "system_temperature": MikrotikSensorEntityDescription( + key="system_temperature", + name="Temperature", + icon="mdi:thermometer", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + entity_category=None, + ha_group="System", + data_path="health", + data_attribute="temperature", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_voltage": MikrotikSensorEntityDescription( + key="system_voltage", + name="Voltage", + icon="mdi:lightning-bolt", + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, + device_class=SensorDeviceClass.VOLTAGE, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ha_group="System", + data_path="health", + data_attribute="voltage", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_cpu-temperature": MikrotikSensorEntityDescription( + key="system_cpu-temperature", + name="CPU temperature", + icon="mdi:thermometer", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + entity_category=None, + ha_group="System", + data_path="health", + data_attribute="cpu-temperature", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_board-temperature1": MikrotikSensorEntityDescription( + key="system_board-temperature1", + name="Board temperature", + icon="mdi:thermometer", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + entity_category=None, + ha_group="System", + data_path="health", + data_attribute="board-temperature1", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_power-consumption": MikrotikSensorEntityDescription( + key="system_power-consumption", + name="Power consumption", + icon="mdi:transmission-tower", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + entity_category=None, + ha_group="System", + data_path="health", + data_attribute="power-consumption", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_fan1-speed": MikrotikSensorEntityDescription( + key="system_fan1-speed", + name="Fan1 speed", + icon="mdi:fan", + native_unit_of_measurement="RPM", + device_class=None, + state_class=None, + entity_category=None, + ha_group="System", + data_path="health", + data_attribute="fan1-speed", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_fan2-speed": MikrotikSensorEntityDescription( + key="system_fan2-speed", + name="Fan2 speed", + icon="mdi:fan", + native_unit_of_measurement="RPM", + device_class=None, + state_class=None, + entity_category=None, + ha_group="System", + data_path="health", + data_attribute="fan2-speed", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_uptime": MikrotikSensorEntityDescription( + key="system_uptime", + name="Uptime", + icon=None, + native_unit_of_measurement=None, + device_class=SensorDeviceClass.TIMESTAMP, + state_class=None, + entity_category=EntityCategory.DIAGNOSTIC, + ha_group="System", + data_path="resource", + data_attribute="uptime", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_cpu-load": MikrotikSensorEntityDescription( + key="system_cpu-load", + name="CPU load", + icon="mdi:speedometer", + native_unit_of_measurement=PERCENTAGE, + device_class=None, + state_class=None, + entity_category=None, + ha_group="System", + data_path="resource", + data_attribute="cpu-load", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_memory-usage": MikrotikSensorEntityDescription( + key="system_memory-usage", + name="Memory usage", + icon="mdi:memory", + native_unit_of_measurement=PERCENTAGE, + device_class=None, + state_class=None, + entity_category=None, + ha_group="System", + data_path="resource", + data_attribute="memory-usage", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "system_hdd-usage": MikrotikSensorEntityDescription( + key="system_hdd-usage", + name="HDD usage", + icon="mdi:harddisk", + native_unit_of_measurement=PERCENTAGE, + device_class=None, + state_class=None, + entity_category=None, + ha_group="System", + data_path="resource", + data_attribute="hdd-usage", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "traffic_tx": MikrotikSensorEntityDescription( + key="traffic_tx", + name="TX", + icon="mdi:upload-network-outline", + native_unit_of_measurement="ps", + device_class=None, + state_class=None, + entity_category=None, + ha_group="", + data_path="interface", + data_attribute="tx-bits-per-second", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "traffic_rx": MikrotikSensorEntityDescription( + key="traffic_rx", + name="RX", + icon="mdi:download-network-outline", + native_unit_of_measurement="ps", + device_class=None, + state_class=None, + entity_category=None, + ha_group="", + data_path="interface", + data_attribute="rx-bits-per-second", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "client_traffic_lan_tx": MikrotikSensorEntityDescription( + key="client_traffic_lan_tx", + name="LAN TX", + icon="mdi:upload-network", + native_unit_of_measurement="tx-rx-attr", + device_class=None, + state_class=None, + entity_category=None, + ha_group="", + data_path="client_traffic", + data_attribute="lan-tx", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "client_traffic_lan_rx": MikrotikSensorEntityDescription( + key="client_traffic_lan_rx", + name="LAN RX", + icon="mdi:download-network", + native_unit_of_measurement="tx-rx-attr", + device_class=None, + state_class=None, + entity_category=None, + ha_group="", + data_path="client_traffic", + data_attribute="lan-rx", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "client_traffic_wan_tx": MikrotikSensorEntityDescription( + key="client_traffic_wan_tx", + name="WAN TX", + icon="mdi:upload-network", + native_unit_of_measurement="tx-rx-attr", + device_class=None, + state_class=None, + entity_category=None, + ha_group="", + data_path="client_traffic", + data_attribute="wan-tx", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "client_traffic_wan_rx": MikrotikSensorEntityDescription( + key="client_traffic_wan_rx", + name="WAN RX", + icon="mdi:download-network", + native_unit_of_measurement="tx-rx-attr", + device_class=None, + state_class=None, + entity_category=None, + ha_group="", + data_path="client_traffic", + data_attribute="wan-rx", + data_name="", + data_uid="", + data_reference="", + data_attributes_list=None, + ), + "environment": MikrotikSensorEntityDescription( + key="environment", + name="", + icon="mdi:clipboard-list", + native_unit_of_measurement="", + device_class=None, + state_class=None, + entity_category=None, + ha_group="Environment", + data_path="environment", + data_attribute="value", + data_name="name", + data_uid="name", + data_reference="name", + data_attributes_list=None, + ), +} + +DEVICE_ATTRIBUTES_CLIENT_TRAFFIC = ["address", "mac-address", "host-name"] From 97d7b649e31ddfc260e0fb0093d13911a16eaa50 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Tue, 1 Feb 2022 14:05:36 +0100 Subject: [PATCH 03/36] updated MikrotikSensorEntityDescription class --- .../mikrotik_router/sensor_types.py | 126 +++++++++--------- 1 file changed, 62 insertions(+), 64 deletions(-) diff --git a/custom_components/mikrotik_router/sensor_types.py b/custom_components/mikrotik_router/sensor_types.py index 2c3058f..86bdcc4 100644 --- a/custom_components/mikrotik_router/sensor_types.py +++ b/custom_components/mikrotik_router/sensor_types.py @@ -1,5 +1,7 @@ """Definitions for Mikrotik Router sensor entities.""" -from dataclasses import dataclass +from dataclasses import dataclass, field +from typing import List +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import EntityCategory, DeviceInfo from homeassistant.components.sensor import ( SensorDeviceClass, @@ -12,28 +14,26 @@ from homeassistant.const import ( POWER_WATT, PERCENTAGE, ) +from .const import DOMAIN + + +DEVICE_ATTRIBUTES_CLIENT_TRAFFIC = ["address", "mac-address", "host-name"] @dataclass -class MikrotikSensorEntityDescriptionAdd: - """Required values when describing secondary sensor attributes.""" - - ha_group: str - data_path: str - data_attribute: str - data_name: str - data_uid: str - data_reference: str - data_attributes_list: [] - - -@dataclass -class MikrotikSensorEntityDescription( - SensorEntityDescription, - MikrotikSensorEntityDescriptionAdd, -): +class MikrotikSensorEntityDescription(SensorEntityDescription): """Class describing mikrotik entities.""" + ha_group: str = "" + ha_connection: str = "" + ha_connection_value: str = "" + data_path: str = "" + data_attribute: str = "" + data_name: str = "" + data_uid: str = "" + data_reference: str = "" + data_attributes_list: List = field(default_factory=lambda: []) + SENSOR_TYPES = { "system_temperature": MikrotikSensorEntityDescription( @@ -50,7 +50,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_voltage": MikrotikSensorEntityDescription( key="system_voltage", @@ -66,7 +65,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_cpu-temperature": MikrotikSensorEntityDescription( key="system_cpu-temperature", @@ -82,7 +80,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_board-temperature1": MikrotikSensorEntityDescription( key="system_board-temperature1", @@ -98,7 +95,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_power-consumption": MikrotikSensorEntityDescription( key="system_power-consumption", @@ -114,7 +110,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_fan1-speed": MikrotikSensorEntityDescription( key="system_fan1-speed", @@ -130,7 +125,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_fan2-speed": MikrotikSensorEntityDescription( key="system_fan2-speed", @@ -146,7 +140,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_uptime": MikrotikSensorEntityDescription( key="system_uptime", @@ -162,7 +155,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_cpu-load": MikrotikSensorEntityDescription( key="system_cpu-load", @@ -178,7 +170,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_memory-usage": MikrotikSensorEntityDescription( key="system_memory-usage", @@ -194,7 +185,6 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "system_hdd-usage": MikrotikSensorEntityDescription( key="system_hdd-usage", @@ -210,103 +200,112 @@ SENSOR_TYPES = { data_name="", data_uid="", data_reference="", - data_attributes_list=None, ), "traffic_tx": MikrotikSensorEntityDescription( key="traffic_tx", name="TX", icon="mdi:upload-network-outline", - native_unit_of_measurement="ps", + native_unit_of_measurement="data__tx-bits-per-second-attr", device_class=None, - state_class=None, + state_class=SensorStateClass.MEASUREMENT, entity_category=None, - ha_group="", + ha_group="data__default-name", + ha_connection=CONNECTION_NETWORK_MAC, + ha_connection_value="data__port-mac-address", data_path="interface", data_attribute="tx-bits-per-second", - data_name="", + data_name="name", data_uid="", - data_reference="", - data_attributes_list=None, + data_reference="default-name", ), "traffic_rx": MikrotikSensorEntityDescription( key="traffic_rx", name="RX", icon="mdi:download-network-outline", - native_unit_of_measurement="ps", + native_unit_of_measurement="data__rx-bits-per-second-attr", device_class=None, - state_class=None, + state_class=SensorStateClass.MEASUREMENT, entity_category=None, - ha_group="", + ha_group="data__default-name", + ha_connection=CONNECTION_NETWORK_MAC, + ha_connection_value="data__port-mac-address", data_path="interface", data_attribute="rx-bits-per-second", - data_name="", + data_name="name", data_uid="", - data_reference="", - data_attributes_list=None, + data_reference="default-name", ), "client_traffic_lan_tx": MikrotikSensorEntityDescription( key="client_traffic_lan_tx", name="LAN TX", icon="mdi:upload-network", - native_unit_of_measurement="tx-rx-attr", + native_unit_of_measurement="data__tx-rx-attr", device_class=None, - state_class=None, + state_class=SensorStateClass.MEASUREMENT, entity_category=None, ha_group="", + ha_connection=CONNECTION_NETWORK_MAC, + ha_connection_value="data__mac-address", data_path="client_traffic", data_attribute="lan-tx", - data_name="", + data_name="host-name", data_uid="", - data_reference="", - data_attributes_list=None, + data_reference="mac-address", + data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC, ), "client_traffic_lan_rx": MikrotikSensorEntityDescription( key="client_traffic_lan_rx", name="LAN RX", icon="mdi:download-network", - native_unit_of_measurement="tx-rx-attr", + native_unit_of_measurement="data__tx-rx-attr", device_class=None, - state_class=None, + state_class=SensorStateClass.MEASUREMENT, entity_category=None, ha_group="", + ha_connection=CONNECTION_NETWORK_MAC, + ha_connection_value="data__mac-address", data_path="client_traffic", data_attribute="lan-rx", - data_name="", + data_name="host-name", data_uid="", - data_reference="", - data_attributes_list=None, + data_reference="mac-address", + data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC, ), "client_traffic_wan_tx": MikrotikSensorEntityDescription( key="client_traffic_wan_tx", name="WAN TX", icon="mdi:upload-network", - native_unit_of_measurement="tx-rx-attr", + native_unit_of_measurement="data__tx-rx-attr", device_class=None, - state_class=None, + state_class=SensorStateClass.MEASUREMENT, entity_category=None, ha_group="", + ha_connection=CONNECTION_NETWORK_MAC, + ha_connection_value="data__mac-address", data_path="client_traffic", data_attribute="wan-tx", - data_name="", + data_name="host-name", data_uid="", - data_reference="", - data_attributes_list=None, + data_reference="mac-address", + data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC, ), "client_traffic_wan_rx": MikrotikSensorEntityDescription( key="client_traffic_wan_rx", name="WAN RX", icon="mdi:download-network", - native_unit_of_measurement="tx-rx-attr", + native_unit_of_measurement="data__tx-rx-attr", device_class=None, - state_class=None, + state_class=SensorStateClass.MEASUREMENT, entity_category=None, ha_group="", + ha_connection=CONNECTION_NETWORK_MAC, + ha_connection_value="data__mac-address", data_path="client_traffic", data_attribute="wan-rx", - data_name="", + data_name="host-name", data_uid="", - data_reference="", - data_attributes_list=None, + data_reference="mac-address", + data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC, ), "environment": MikrotikSensorEntityDescription( key="environment", @@ -317,13 +316,12 @@ SENSOR_TYPES = { state_class=None, entity_category=None, ha_group="Environment", + ha_connection=DOMAIN, + ha_connection_value="Environment", data_path="environment", data_attribute="value", data_name="name", data_uid="name", data_reference="name", - data_attributes_list=None, ), } - -DEVICE_ATTRIBUTES_CLIENT_TRAFFIC = ["address", "mac-address", "host-name"] From fa8b64625c772e962c1b9d036a1c1c3f81fa50aa Mon Sep 17 00:00:00 2001 From: Tomaae Date: Tue, 1 Feb 2022 14:06:33 +0100 Subject: [PATCH 04/36] redo traffic sensors --- custom_components/mikrotik_router/sensor.py | 237 +++++++++----------- 1 file changed, 110 insertions(+), 127 deletions(-) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 66a2537..031e5c4 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -3,6 +3,7 @@ import logging from typing import Any, Dict, Optional +from collections.abc import Mapping from homeassistant.const import ( CONF_NAME, @@ -26,7 +27,6 @@ from .const import DOMAIN, DATA_CLIENT, ATTRIBUTION from .sensor_types import ( MikrotikSensorEntityDescription, SENSOR_TYPES, - DEVICE_ATTRIBUTES_CLIENT_TRAFFIC, ) _LOGGER = logging.getLogger(__name__) @@ -131,48 +131,48 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se ) new_sensors.append(sensors[item_id]) - # if sensor.startswith("traffic_"): - # if not config_entry.options.get( - # CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC - # ): - # continue - # - # for uid in mikrotik_controller.data["interface"]: - # if mikrotik_controller.data["interface"][uid]["type"] != "bridge": - # item_id = f"{inst}-{sensor}-{mikrotik_controller.data['interface'][uid]['default-name']}" - # _LOGGER.debug("Updating sensor %s", item_id) - # 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_TYPES[sensor], - # uid=uid, - # ) - # new_sensors.append(sensors[item_id]) - # - # if sensor.startswith("client_traffic_"): - # for uid in mikrotik_controller.data["client_traffic"]: - # item_id = f"{inst}-{sensor}-{mikrotik_controller.data['client_traffic'][uid]['mac-address']}" - # if item_id in sensors: - # if sensors[item_id].enabled: - # sensors[item_id].async_schedule_update_ha_state() - # continue - # - # if ( - # SENSOR_TYPES[sensor].data_attribute - # in mikrotik_controller.data["client_traffic"][uid].keys() - # ): - # sensors[item_id] = MikrotikClientTrafficSensor( - # mikrotik_controller=mikrotik_controller, - # inst=inst, - # sensor=SENSOR_TYPES[sensor], - # uid=uid, - # ) - # new_sensors.append(sensors[item_id]) + if sensor.startswith("traffic_"): + if not config_entry.options.get( + CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC + ): + continue + + for uid in mikrotik_controller.data["interface"]: + if mikrotik_controller.data["interface"][uid]["type"] != "bridge": + item_id = f"{inst}-{sensor}-{mikrotik_controller.data['interface'][uid]['default-name']}" + _LOGGER.debug("Updating sensor %s", item_id) + if item_id in sensors: + if sensors[item_id].enabled: + sensors[item_id].async_schedule_update_ha_state() + continue + + sensors[item_id] = MikrotikControllerSensor( + inst=inst, + mikrotik_controller=mikrotik_controller, + uid=uid, + entity_description=SENSOR_TYPES[sensor], + ) + new_sensors.append(sensors[item_id]) + + if sensor.startswith("client_traffic_"): + for uid in mikrotik_controller.data["client_traffic"]: + item_id = f"{inst}-{sensor}-{mikrotik_controller.data['client_traffic'][uid]['mac-address']}" + if item_id in sensors: + if sensors[item_id].enabled: + sensors[item_id].async_schedule_update_ha_state() + continue + + if ( + SENSOR_TYPES[sensor].data_attribute + in mikrotik_controller.data["client_traffic"][uid].keys() + ): + sensors[item_id] = MikrotikClientTrafficSensor( + inst=inst, + mikrotik_controller=mikrotik_controller, + uid=uid, + entity_description=SENSOR_TYPES[sensor], + ) + new_sensors.append(sensors[item_id]) if new_sensors: async_add_entities(new_sensors, True) @@ -192,8 +192,8 @@ class MikrotikControllerSensor(SensorEntity): entity_description: MikrotikSensorEntityDescription, ): """Initialize.""" - self._inst = inst self.entity_description = entity_description + self._inst = inst self._ctrl = mikrotik_controller self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION} self._uid = uid @@ -208,7 +208,10 @@ class MikrotikControllerSensor(SensorEntity): def name(self) -> str: """Return the name.""" if self._uid: - return f"{self._inst} {self._data[self.entity_description.data_reference]}" + if self.entity_description.name: + return f"{self._inst} {self._data[self.entity_description.data_name]} {self.entity_description.name}" + + return f"{self._inst} {self._data[self.entity_description.data_name]}" else: return f"{self._inst} {self.entity_description.name}" @@ -228,6 +231,20 @@ class MikrotikControllerSensor(SensorEntity): else: return "unknown" + @property + def native_unit_of_measurement(self): + """Return the unit the value is expressed in.""" + if self.entity_description.native_unit_of_measurement: + if self.entity_description.native_unit_of_measurement.startswith("data__"): + uom = self.entity_description.native_unit_of_measurement[6:] + if uom in self._data: + uom = self._data[uom] + return uom + + return self.entity_description.native_unit_of_measurement + + return None + @property def available(self) -> bool: """Return if controller is available.""" @@ -236,14 +253,31 @@ class MikrotikControllerSensor(SensorEntity): @property def device_info(self) -> DeviceInfo: """Return a description for device registry.""" - di_domain = self.entity_description.data_reference + dev_connection = DOMAIN + dev_connection_value = self.entity_description.data_reference + dev_name = self.entity_description.ha_group if self.entity_description.ha_group == "System": - self.entity_description.ha_group = self._ctrl.data["resource"]["board-name"] - di_domain = self._ctrl.data["routerboard"]["serial-number"] + dev_name = self._ctrl.data["resource"]["board-name"] + dev_connection_value = self._ctrl.data["routerboard"]["serial-number"] + + if self.entity_description.ha_group.startswith("data__"): + dev_name = self.entity_description.ha_group[6:] + if dev_name in self._data: + dev_name = self._data[dev_name] + dev_connection_value = dev_name + + if self.entity_description.ha_connection: + dev_connection = self.entity_description.ha_connection + + if self.entity_description.ha_connection_value: + dev_connection_value = self.entity_description.ha_connection_value + if dev_connection_value.startswith("data__"): + dev_connection_value = dev_connection_value[6:] + dev_connection_value = self._data[dev_connection_value] info = DeviceInfo( - connections={(DOMAIN, f"{di_domain}")}, - name=f"{self._inst} {self.entity_description.ha_group}", + connections={(dev_connection, f"{dev_connection_value}")}, + default_name=f"{self._inst} {dev_name}", model=f"{self._ctrl.data['resource']['board-name']}", manufacturer=f"{self._ctrl.data['resource']['platform']}", sw_version=f"{self._ctrl.data['resource']['version']}", @@ -251,52 +285,34 @@ class MikrotikControllerSensor(SensorEntity): via_device=(DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}"), ) + if "mac-address" in self.entity_description.data_reference: + info = DeviceInfo( + connections={(dev_connection, f"{dev_connection_value}")}, + default_name=f"{self._data[self.entity_description.data_name]}", + via_device=( + DOMAIN, + f"{self._ctrl.data['routerboard']['serial-number']}", + ), + ) + + if "manufacturer" in self._data and self._data["manufacturer"] != "": + info["manufacturer"] = self._data["manufacturer"] + return info + @property + def extra_state_attributes(self) -> Mapping[str, Any]: + """Return the state attributes.""" + attributes = super().extra_state_attributes + for variable in self.entity_description.data_attributes_list: + if variable in self._data: + attributes[format_attribute(variable)] = self._data[variable] + + return attributes + async def async_added_to_hass(self): """Run when entity about to be added to hass.""" - _LOGGER.debug("New sensor %s (%s)", self._inst, self.entity_description.name) - - -# --------------------------- -# MikrotikControllerTrafficSensor -# --------------------------- -class MikrotikControllerTrafficSensor(MikrotikControllerSensor): - """Define a traffic sensor.""" - - def __init__(self, mikrotik_controller, inst, sensor, uid): - """Initialize.""" - super().__init__(mikrotik_controller, inst, sensor) - if uid: - self._uid = uid - self._data = mikrotik_controller.data[sensor.data_path][uid] - - @property - def name(self) -> str: - """Return the name.""" - return f"{self._inst} {self._data['name']} {self.entity_description.name}" - - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self.entity_description.lower()}-{self._data['default-name'].lower()}" - - @property - def state_class(self) -> str: - """Return the state_class""" - return f"measurement" - - @property - def device_info(self) -> Dict[str, Any]: - """Return a 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": f"{self._inst} {self._data['default-name']}", - } - - return info + _LOGGER.debug("New sensor %s (%s)", self._inst, self.unique_id) # --------------------------- @@ -305,28 +321,17 @@ class MikrotikControllerTrafficSensor(MikrotikControllerSensor): class MikrotikClientTrafficSensor(MikrotikControllerSensor): """Define an Mikrotik MikrotikClientTrafficSensor 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.data_path][uid] - @property def name(self) -> str: """Return the name.""" - return f"{self._data['host-name']} {self.entity_description.name}" - - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self.entity_description.lower()}-{self._data['mac-address'].lower()}" + return f"{self._data[self.entity_description.data_name]} {self.entity_description.name}" @property def available(self) -> bool: """Return if controller and accounting feature in Mikrotik is available. Additional check for lan-tx/rx sensors """ - if self._attr in ["lan-tx", "lan-rx"]: + if self.entity_description.data_attribute in ["lan-tx", "lan-rx"]: return ( self._ctrl.connected() and self._data["available"] @@ -334,25 +339,3 @@ class MikrotikClientTrafficSensor(MikrotikControllerSensor): ) else: return self._ctrl.connected() and self._data["available"] - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "connections": {(CONNECTION_NETWORK_MAC, self._data["mac-address"])}, - "default_name": self._data["host-name"], - } - if "manufacturer" in self._data and self._data["manufacturer"] != "": - info["manufacturer"] = self._data["manufacturer"] - - return info - - @property - def extra_state_attributes(self) -> Dict[str, Any]: - """Return the state attributes.""" - attributes = self._attrs - for variable in DEVICE_ATTRIBUTES_CLIENT_TRAFFIC: - if variable in self._data: - attributes[format_attribute(variable)] = self._data[variable] - - return attributes From 21f795c0de510cc52279eea5dc5ac45bdee33bc9 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Tue, 1 Feb 2022 14:07:21 +0100 Subject: [PATCH 05/36] cleanup imports --- custom_components/mikrotik_router/sensor.py | 3 +-- custom_components/mikrotik_router/sensor_types.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 031e5c4..7004fac 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -2,7 +2,7 @@ import logging -from typing import Any, Dict, Optional +from typing import Any, Optional from collections.abc import Mapping from homeassistant.const import ( @@ -20,7 +20,6 @@ from .const import ( ) from homeassistant.core import callback -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DOMAIN, DATA_CLIENT, ATTRIBUTION diff --git a/custom_components/mikrotik_router/sensor_types.py b/custom_components/mikrotik_router/sensor_types.py index 86bdcc4..3ff6d11 100644 --- a/custom_components/mikrotik_router/sensor_types.py +++ b/custom_components/mikrotik_router/sensor_types.py @@ -2,7 +2,7 @@ from dataclasses import dataclass, field from typing import List from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.entity import EntityCategory, DeviceInfo +from homeassistant.helpers.entity import EntityCategory from homeassistant.components.sensor import ( SensorDeviceClass, SensorStateClass, From 867b66d5005ac745f563c3b56f077a8c806f3379 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Tue, 1 Feb 2022 14:45:54 +0100 Subject: [PATCH 06/36] added client name and manufacturer resolution for client traffic sensors --- custom_components/mikrotik_router/sensor.py | 25 +++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 7004fac..ec6bf3a 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -276,6 +276,7 @@ class MikrotikControllerSensor(SensorEntity): info = DeviceInfo( connections={(dev_connection, f"{dev_connection_value}")}, + identifiers={(dev_connection, f"{dev_connection_value}")}, default_name=f"{self._inst} {dev_name}", model=f"{self._ctrl.data['resource']['board-name']}", manufacturer=f"{self._ctrl.data['resource']['platform']}", @@ -284,19 +285,35 @@ class MikrotikControllerSensor(SensorEntity): via_device=(DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}"), ) + if ( + dev_connection != DOMAIN + and dev_connection_value + != f"{self._ctrl.data['routerboard']['serial-number']}" + ): + info["via_device"] = ( + DOMAIN, + f"{self._ctrl.data['routerboard']['serial-number']}", + ) + if "mac-address" in self.entity_description.data_reference: + dev_name = self._data[self.entity_description.data_name] + dev_manufacturer = "" + if dev_connection_value in self._ctrl.data["host"]: + dev_name = self._ctrl.data["host"][dev_connection_value]["host-name"] + dev_manufacturer = self._ctrl.data["host"][dev_connection_value][ + "manufacturer" + ] + info = DeviceInfo( connections={(dev_connection, f"{dev_connection_value}")}, - default_name=f"{self._data[self.entity_description.data_name]}", + default_name=f"{dev_name}", + manufacturer=f"{dev_manufacturer}", via_device=( DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}", ), ) - if "manufacturer" in self._data and self._data["manufacturer"] != "": - info["manufacturer"] = self._data["manufacturer"] - return info @property From d7f63b3a50a5f85dcefab9d8354d4dac463130be Mon Sep 17 00:00:00 2001 From: Tomaae Date: Tue, 1 Feb 2022 15:05:06 +0100 Subject: [PATCH 07/36] removed duplicate via_device --- custom_components/mikrotik_router/sensor.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index ec6bf3a..af8a7ad 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -285,16 +285,6 @@ class MikrotikControllerSensor(SensorEntity): via_device=(DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}"), ) - if ( - dev_connection != DOMAIN - and dev_connection_value - != f"{self._ctrl.data['routerboard']['serial-number']}" - ): - info["via_device"] = ( - DOMAIN, - f"{self._ctrl.data['routerboard']['serial-number']}", - ) - if "mac-address" in self.entity_description.data_reference: dev_name = self._data[self.entity_description.data_name] dev_manufacturer = "" From efb0bd8069e76015da32a4748d5a90e96ba548d5 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Wed, 2 Feb 2022 21:00:34 +0100 Subject: [PATCH 08/36] changed data source to data_path in sensor update loop --- custom_components/mikrotik_router/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index af8a7ad..f5989a7 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -88,7 +88,7 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se MikrotikControllerSensor, ], ): - for uid in mikrotik_controller.data[sensor]: + for uid in mikrotik_controller.data[SENSOR_TYPES[sensor].data_path]: item_id = f"{inst}-{sensor}-{mikrotik_controller.data[sensor][uid][SENSOR_TYPES[sensor].data_uid]}" _LOGGER.debug("Updating sensor %s", item_id) if item_id in sensors: From 6f7057b8b3019320f92a060e36aa2ea0fbd75519 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Wed, 2 Feb 2022 21:07:23 +0100 Subject: [PATCH 09/36] changed item_id postfix in sensor from name to data_reference --- custom_components/mikrotik_router/sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index f5989a7..7bb1167 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -81,15 +81,15 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se new_sensors = [] for sensor, sid_func in zip( - # Data point name + # Sensor type name ["environment"], - # Switch function + # Entity function [ MikrotikControllerSensor, ], ): for uid in mikrotik_controller.data[SENSOR_TYPES[sensor].data_path]: - item_id = f"{inst}-{sensor}-{mikrotik_controller.data[sensor][uid][SENSOR_TYPES[sensor].data_uid]}" + item_id = f"{inst}-{sensor}-{mikrotik_controller.data[sensor][uid][SENSOR_TYPES[sensor].data_reference]}" _LOGGER.debug("Updating sensor %s", item_id) if item_id in sensors: if sensors[item_id].enabled: From 79430ef1ef3fff873cc799b32f3b26d508c9cc43 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Wed, 2 Feb 2022 21:54:42 +0100 Subject: [PATCH 10/36] consolidated sensor update_items --- custom_components/mikrotik_router/sensor.py | 93 ++++++++++----------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 7bb1167..d169c08 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -82,14 +82,47 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se for sensor, sid_func in zip( # Sensor type name - ["environment"], + [ + "environment", + "traffic_rx", + "traffic_tx", + "client_traffic_lan_rx", + "client_traffic_lan_tx", + "client_traffic_wan_rx", + "client_traffic_wan_tx", + ], # Entity function [ MikrotikControllerSensor, + MikrotikControllerSensor, + MikrotikControllerSensor, + MikrotikClientTrafficSensor, + MikrotikClientTrafficSensor, + MikrotikClientTrafficSensor, + MikrotikClientTrafficSensor, ], ): + if sensor.startswith("traffic_") and not config_entry.options.get( + CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC + ): + continue + + uid_sensor = SENSOR_TYPES[sensor] for uid in mikrotik_controller.data[SENSOR_TYPES[sensor].data_path]: - item_id = f"{inst}-{sensor}-{mikrotik_controller.data[sensor][uid][SENSOR_TYPES[sensor].data_reference]}" + uid_data = mikrotik_controller.data[SENSOR_TYPES[sensor].data_path] + if ( + uid_sensor.data_path == "interface" + and uid_data[uid]["type"] == "bridge" + ): + continue + + if ( + uid_sensor.data_path == "client_traffic" + and uid_sensor.data_attribute not in uid_data[uid].keys() + ): + continue + + item_id = f"{inst}-{sensor}-{uid_data[uid][uid_sensor.data_reference]}" _LOGGER.debug("Updating sensor %s", item_id) if item_id in sensors: if sensors[item_id].enabled: @@ -100,17 +133,18 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se inst=inst, uid=uid, mikrotik_controller=mikrotik_controller, - entity_description=SENSOR_TYPES[sensor], + entity_description=uid_sensor, ) new_sensors.append(sensors[item_id]) for sensor in SENSOR_TYPES: if sensor.startswith("system_"): + uid_sensor = SENSOR_TYPES[sensor] if ( - SENSOR_TYPES[sensor].data_attribute - not in mikrotik_controller.data[SENSOR_TYPES[sensor].data_path] - or mikrotik_controller.data[SENSOR_TYPES[sensor].data_path][ - SENSOR_TYPES[sensor].data_attribute + uid_sensor.data_attribute + not in mikrotik_controller.data[uid_sensor.data_path] + or mikrotik_controller.data[uid_sensor.data_path][ + uid_sensor.data_attribute ] == "unknown" ): @@ -126,53 +160,10 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se inst=inst, uid="", mikrotik_controller=mikrotik_controller, - entity_description=SENSOR_TYPES[sensor], + entity_description=uid_sensor, ) new_sensors.append(sensors[item_id]) - if sensor.startswith("traffic_"): - if not config_entry.options.get( - CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC - ): - continue - - for uid in mikrotik_controller.data["interface"]: - if mikrotik_controller.data["interface"][uid]["type"] != "bridge": - item_id = f"{inst}-{sensor}-{mikrotik_controller.data['interface'][uid]['default-name']}" - _LOGGER.debug("Updating sensor %s", item_id) - if item_id in sensors: - if sensors[item_id].enabled: - sensors[item_id].async_schedule_update_ha_state() - continue - - sensors[item_id] = MikrotikControllerSensor( - inst=inst, - mikrotik_controller=mikrotik_controller, - uid=uid, - entity_description=SENSOR_TYPES[sensor], - ) - new_sensors.append(sensors[item_id]) - - if sensor.startswith("client_traffic_"): - for uid in mikrotik_controller.data["client_traffic"]: - item_id = f"{inst}-{sensor}-{mikrotik_controller.data['client_traffic'][uid]['mac-address']}" - if item_id in sensors: - if sensors[item_id].enabled: - sensors[item_id].async_schedule_update_ha_state() - continue - - if ( - SENSOR_TYPES[sensor].data_attribute - in mikrotik_controller.data["client_traffic"][uid].keys() - ): - sensors[item_id] = MikrotikClientTrafficSensor( - inst=inst, - mikrotik_controller=mikrotik_controller, - uid=uid, - entity_description=SENSOR_TYPES[sensor], - ) - new_sensors.append(sensors[item_id]) - if new_sensors: async_add_entities(new_sensors, True) From 1ea1f3a4b70c92e728ca29e142a4a46806b1fa05 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Wed, 2 Feb 2022 22:01:12 +0100 Subject: [PATCH 11/36] renamed helper.py to apiparser.py --- custom_components/mikrotik_router/{helper.py => apiparser.py} | 2 +- custom_components/mikrotik_router/mikrotik_controller.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename custom_components/mikrotik_router/{helper.py => apiparser.py} (99%) diff --git a/custom_components/mikrotik_router/helper.py b/custom_components/mikrotik_router/apiparser.py similarity index 99% rename from custom_components/mikrotik_router/helper.py rename to custom_components/mikrotik_router/apiparser.py index 02518dd..1ffce21 100644 --- a/custom_components/mikrotik_router/helper.py +++ b/custom_components/mikrotik_router/apiparser.py @@ -1,4 +1,4 @@ -"""Helper functions for Mikrotik Router.""" +"""API parser functions for Mikrotik Router.""" import logging diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index 21c71cf..fafe3b4 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -59,7 +59,7 @@ from .const import ( DEFAULT_SENSOR_ENVIRONMENT, ) from .exceptions import ApiEntryNotFound -from .helper import parse_api +from .apiparser import parse_api from .mikrotikapi import MikrotikAPI _LOGGER = logging.getLogger(__name__) From a3e6761c78a09944e2d815f43be742920a4d9c79 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Wed, 2 Feb 2022 22:13:39 +0100 Subject: [PATCH 12/36] renamed format_attribute to helper.py --- .../mikrotik_router/binary_sensor.py | 19 +----------- custom_components/mikrotik_router/button.py | 19 +----------- .../mikrotik_router/device_tracker.py | 18 +---------- custom_components/mikrotik_router/helper.py | 17 ++++++++++ custom_components/mikrotik_router/sensor.py | 31 ++++--------------- custom_components/mikrotik_router/switch.py | 18 +---------- 6 files changed, 27 insertions(+), 95 deletions(-) create mode 100644 custom_components/mikrotik_router/helper.py diff --git a/custom_components/mikrotik_router/binary_sensor.py b/custom_components/mikrotik_router/binary_sensor.py index 1cb4648..d575ba9 100644 --- a/custom_components/mikrotik_router/binary_sensor.py +++ b/custom_components/mikrotik_router/binary_sensor.py @@ -2,7 +2,6 @@ import logging from typing import Any, Dict, Optional - from homeassistant.helpers.entity import EntityCategory from homeassistant.components.binary_sensor import ( BinarySensorEntity, @@ -17,7 +16,7 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC - +from .helper import format_attribute from .const import ( DOMAIN, DATA_CLIENT, @@ -116,22 +115,6 @@ DEVICE_ATTRIBUTES_PPP_SECRET = [ ] -# --------------------------- -# 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") - res = res.replace("Sfp", "SFP") - res = res.replace("Poe", "POE") - res = res.replace(" tx", " TX") - res = res.replace(" rx", " RX") - return res - - # --------------------------- # format_value # --------------------------- diff --git a/custom_components/mikrotik_router/button.py b/custom_components/mikrotik_router/button.py index 914717b..30e5144 100644 --- a/custom_components/mikrotik_router/button.py +++ b/custom_components/mikrotik_router/button.py @@ -2,13 +2,12 @@ import logging from typing import Any, Dict - from homeassistant.components.button import ButtonEntity from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity - +from .helper import format_attribute from .const import DOMAIN, DATA_CLIENT, ATTRIBUTION _LOGGER = logging.getLogger(__name__) @@ -19,22 +18,6 @@ DEVICE_ATTRIBUTES_SCRIPT = [ ] -# --------------------------- -# 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") - res = res.replace("Sfp", "SFP") - res = res.replace("Poe", "POE") - res = res.replace(" tx", " TX") - res = res.replace(" rx", " RX") - return res - - # --------------------------- # async_setup_entry # --------------------------- diff --git a/custom_components/mikrotik_router/device_tracker.py b/custom_components/mikrotik_router/device_tracker.py index 5618432..508e6fc 100644 --- a/custom_components/mikrotik_router/device_tracker.py +++ b/custom_components/mikrotik_router/device_tracker.py @@ -15,7 +15,7 @@ from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.dt import get_age, utcnow - +from .helper import format_attribute from .const import ( DOMAIN, DATA_CLIENT, @@ -38,22 +38,6 @@ DEVICE_ATTRIBUTES_HOST = [ ] -# --------------------------- -# 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") - res = res.replace("Sfp", "SFP") - res = res.replace("Poe", "POE") - res = res.replace(" tx", " TX") - res = res.replace(" rx", " RX") - return res - - # --------------------------- # format_value # --------------------------- diff --git a/custom_components/mikrotik_router/helper.py b/custom_components/mikrotik_router/helper.py new file mode 100644 index 0000000..507e254 --- /dev/null +++ b/custom_components/mikrotik_router/helper.py @@ -0,0 +1,17 @@ +"""Helper functions for Mikrotik Router.""" + + +# --------------------------- +# 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") + res = res.replace("Sfp", "SFP") + res = res.replace("Poe", "POE") + res = res.replace(" tx", " TX") + res = res.replace(" rx", " RX") + return res diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index d169c08..56a5146 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -1,28 +1,25 @@ """Implementation of Mikrotik Router sensor entities.""" import logging - from typing import Any, Optional from collections.abc import Mapping - from homeassistant.const import ( CONF_NAME, CONF_HOST, ATTR_ATTRIBUTION, ) - from homeassistant.helpers.entity import DeviceInfo from homeassistant.components.sensor import SensorEntity - +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .helper import format_attribute from .const import ( CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC, + DOMAIN, + DATA_CLIENT, + ATTRIBUTION, ) - -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect - -from .const import DOMAIN, DATA_CLIENT, ATTRIBUTION from .sensor_types import ( MikrotikSensorEntityDescription, SENSOR_TYPES, @@ -31,22 +28,6 @@ from .sensor_types import ( _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") - res = res.replace("Sfp", "SFP") - res = res.replace("Poe", "POE") - res = res.replace(" tx", " TX") - res = res.replace(" rx", " RX") - return res - - # --------------------------- # async_setup_entry # --------------------------- diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index bef19cd..96592b3 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -9,7 +9,7 @@ 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.restore_state import RestoreEntity - +from .helper import format_attribute from .const import DOMAIN, DATA_CLIENT, ATTRIBUTION _LOGGER = logging.getLogger(__name__) @@ -151,22 +151,6 @@ DEVICE_ATTRIBUTES_QUEUE = [ ] -# --------------------------- -# 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") - res = res.replace("Sfp", "SFP") - res = res.replace("Poe", "POE") - res = res.replace(" tx", " TX") - res = res.replace(" rx", " RX") - return res - - # --------------------------- # async_setup_entry # --------------------------- From 1503cd79d1327aa39a6b15bf1f1b6debb479562c Mon Sep 17 00:00:00 2001 From: Tomaae Date: Wed, 2 Feb 2022 22:18:05 +0100 Subject: [PATCH 13/36] renamed format_value to helper.py --- custom_components/mikrotik_router/binary_sensor.py | 13 +------------ .../mikrotik_router/device_tracker.py | 14 +------------- custom_components/mikrotik_router/helper.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/custom_components/mikrotik_router/binary_sensor.py b/custom_components/mikrotik_router/binary_sensor.py index d575ba9..cc86436 100644 --- a/custom_components/mikrotik_router/binary_sensor.py +++ b/custom_components/mikrotik_router/binary_sensor.py @@ -2,6 +2,7 @@ import logging from typing import Any, Dict, Optional + from homeassistant.helpers.entity import EntityCategory from homeassistant.components.binary_sensor import ( BinarySensorEntity, @@ -115,18 +116,6 @@ DEVICE_ATTRIBUTES_PPP_SECRET = [ ] -# --------------------------- -# format_value -# --------------------------- -def format_value(res): - res = res.replace("dhcp", "DHCP") - res = res.replace("dns", "DNS") - res = res.replace("capsman", "CAPsMAN") - res = res.replace("wireless", "Wireless") - res = res.replace("restored", "Restored") - return res - - # --------------------------- # async_setup_entry # --------------------------- diff --git a/custom_components/mikrotik_router/device_tracker.py b/custom_components/mikrotik_router/device_tracker.py index 508e6fc..f41cd4d 100644 --- a/custom_components/mikrotik_router/device_tracker.py +++ b/custom_components/mikrotik_router/device_tracker.py @@ -15,7 +15,7 @@ from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.dt import get_age, utcnow -from .helper import format_attribute +from .helper import format_attribute, format_value from .const import ( DOMAIN, DATA_CLIENT, @@ -38,18 +38,6 @@ DEVICE_ATTRIBUTES_HOST = [ ] -# --------------------------- -# format_value -# --------------------------- -def format_value(res): - res = res.replace("dhcp", "DHCP") - res = res.replace("dns", "DNS") - res = res.replace("capsman", "CAPsMAN") - res = res.replace("wireless", "Wireless") - res = res.replace("restored", "Restored") - return res - - # --------------------------- # async_setup_entry # --------------------------- diff --git a/custom_components/mikrotik_router/helper.py b/custom_components/mikrotik_router/helper.py index 507e254..2722693 100644 --- a/custom_components/mikrotik_router/helper.py +++ b/custom_components/mikrotik_router/helper.py @@ -15,3 +15,15 @@ def format_attribute(attr): res = res.replace(" tx", " TX") res = res.replace(" rx", " RX") return res + + +# --------------------------- +# format_value +# --------------------------- +def format_value(res): + res = res.replace("dhcp", "DHCP") + res = res.replace("dns", "DNS") + res = res.replace("capsman", "CAPsMAN") + res = res.replace("wireless", "Wireless") + res = res.replace("restored", "Restored") + return res From cccbe012791c99af4e28942e4cd0910d1391d3a6 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Thu, 3 Feb 2022 08:12:26 +0100 Subject: [PATCH 14/36] created types definition file for switches --- .../mikrotik_router/switch_types.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 custom_components/mikrotik_router/switch_types.py diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py new file mode 100644 index 0000000..7251957 --- /dev/null +++ b/custom_components/mikrotik_router/switch_types.py @@ -0,0 +1,47 @@ +"""Definitions for Mikrotik Router sensor entities.""" +from dataclasses import dataclass, field +from typing import List +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import EntityCategory +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntityDescription, +) + +from .const import DOMAIN + + +DEVICE_ATTRIBUTES_CLIENT_TRAFFIC = ["address", "mac-address", "host-name"] + + +@dataclass +class MikrotikSwitchEntityDescription(SwitchEntityDescription): + """Class describing mikrotik entities.""" + + device_class: str = SwitchDeviceClass.SWITCH + + ha_group: str = "" + ha_connection: str = "" + ha_connection_value: str = "" + data_path: str = "" + data_attribute: str = "" + data_name: str = "" + data_uid: str = "" + data_reference: str = "" + data_attributes_list: List = field(default_factory=lambda: []) + + +SENSOR_TYPES = { + "system_temperature": MikrotikSwitchEntityDescription( + key="system_temperature", + name="Temperature", + icon="mdi:thermometer", + entity_category=None, + ha_group="System", + data_path="health", + data_attribute="temperature", + data_name="", + data_uid="", + data_reference="", + ), +} From 81dd82ac646c9b9e329af733853979e8a2d7f05f Mon Sep 17 00:00:00 2001 From: Tomaae Date: Thu, 3 Feb 2022 10:24:38 +0100 Subject: [PATCH 15/36] Added device attribute to switch type definition file --- .../mikrotik_router/switch_types.py | 123 +++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index 7251957..7f9ca5c 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -10,8 +10,129 @@ from homeassistant.components.switch import ( from .const import DOMAIN +DEVICE_ATTRIBUTES_IFACE = [ + "running", + "enabled", + "comment", + "client-ip-address", + "client-mac-address", + "port-mac-address", + "last-link-down-time", + "last-link-up-time", + "link-downs", + "actual-mtu", + "type", + "name", +] -DEVICE_ATTRIBUTES_CLIENT_TRAFFIC = ["address", "mac-address", "host-name"] +DEVICE_ATTRIBUTES_IFACE_ETHER = [ + "status", + "auto-negotiation", + "rate", + "full-duplex", + "default-name", + "poe-out", +] + +DEVICE_ATTRIBUTES_IFACE_SFP = [ + "status", + "auto-negotiation", + "advertising", + "link-partner-advertising", + "sfp-temperature", + "sfp-supply-voltage", + "sfp-module-present", + "sfp-tx-bias-current", + "sfp-tx-power", + "sfp-rx-power", + "sfp-rx-loss", + "sfp-tx-fault", + "sfp-type", + "sfp-connector-type", + "sfp-vendor-name", + "sfp-vendor-part-number", + "sfp-vendor-revision", + "sfp-vendor-serial", + "sfp-manufacturing-date", + "eeprom-checksum", +] + +DEVICE_ATTRIBUTES_NAT = [ + "protocol", + "dst-port", + "in-interface", + "to-addresses", + "to-ports", + "comment", +] + +DEVICE_ATTRIBUTES_MANGLE = [ + "chain", + "action", + "passthrough", + "protocol", + "src-address", + "src-port", + "dst-address", + "dst-port", + "comment", +] + +DEVICE_ATTRIBUTES_FILTER = [ + "chain", + "action", + "address-list", + "protocol", + "layer7-protocol", + "tcp-flags", + "connection-state", + "in-interface", + "src-address", + "src-port", + "out-interface", + "dst-address", + "dst-port", + "comment", +] + +DEVICE_ATTRIBUTES_PPP_SECRET = [ + "connected", + "service", + "profile", + "comment", + "caller-id", + "encoding", +] + +DEVICE_ATTRIBUTES_KIDCONTROL = [ + "rate-limit", + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", + "sun", +] + +DEVICE_ATTRIBUTES_QUEUE = [ + "target", + "download-rate", + "upload-rate", + "download-max-limit", + "upload-max-limit", + "upload-limit-at", + "download-limit-at", + "upload-burst-limit", + "download-burst-limit", + "upload-burst-threshold", + "download-burst-threshold", + "upload-burst-time", + "download-burst-time", + "packet-marks", + "parent", + "comment", +] @dataclass From 2d94aceedfd8e47fdd5b5769e54cd29d6545a525 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Thu, 3 Feb 2022 10:25:23 +0100 Subject: [PATCH 16/36] expanded switch entity description --- custom_components/mikrotik_router/switch_types.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index 7f9ca5c..a0a28b3 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -141,11 +141,16 @@ class MikrotikSwitchEntityDescription(SwitchEntityDescription): device_class: str = SwitchDeviceClass.SWITCH + icon_enabled: str = "" + icon_disabled: str = "" ha_group: str = "" ha_connection: str = "" ha_connection_value: str = "" data_path: str = "" data_attribute: str = "" + data_is_on: str = "enabled" + data_switch_path: str = "" + data_switch_parameter: str = "disabled" data_name: str = "" data_uid: str = "" data_reference: str = "" From 4920950e65c975a2f5546b178c45cbaa7c536dc2 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Thu, 3 Feb 2022 10:28:22 +0100 Subject: [PATCH 17/36] redo port switches --- custom_components/mikrotik_router/switch.py | 423 +++++++++--------- .../mikrotik_router/switch_types.py | 25 +- 2 files changed, 225 insertions(+), 223 deletions(-) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index 96592b3..a5f66f4 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -2,77 +2,25 @@ import logging from typing import Any, Dict, Optional - +from collections.abc import Mapping from homeassistant.components.switch import SwitchEntity -from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION +from homeassistant.const import CONF_NAME, CONF_HOST, ATTR_ATTRIBUTION from homeassistant.core import callback +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity from .helper import format_attribute from .const import DOMAIN, DATA_CLIENT, ATTRIBUTION +from .switch_types import ( + MikrotikSwitchEntityDescription, + SWITCH_TYPES, + DEVICE_ATTRIBUTES_IFACE_ETHER, + DEVICE_ATTRIBUTES_IFACE_SFP, +) _LOGGER = logging.getLogger(__name__) -DEVICE_ATTRIBUTES_IFACE = [ - "running", - "enabled", - "comment", - "client-ip-address", - "client-mac-address", - "port-mac-address", - "last-link-down-time", - "last-link-up-time", - "link-downs", - "actual-mtu", - "type", - "name", -] - -DEVICE_ATTRIBUTES_IFACE_ETHER = [ - "running", - "enabled", - "comment", - "client-ip-address", - "client-mac-address", - "port-mac-address", - "last-link-down-time", - "last-link-up-time", - "link-downs", - "actual-mtu", - "type", - "name", - "status", - "auto-negotiation", - "rate", - "full-duplex", - "default-name", - "poe-out", -] - -DEVICE_ATTRIBUTES_IFACE_SFP = [ - "status", - "auto-negotiation", - "advertising", - "link-partner-advertising", - "sfp-temperature", - "sfp-supply-voltage", - "sfp-module-present", - "sfp-tx-bias-current", - "sfp-tx-power", - "sfp-rx-power", - "sfp-rx-loss", - "sfp-tx-fault", - "sfp-type", - "sfp-connector-type", - "sfp-vendor-name", - "sfp-vendor-part-number", - "sfp-vendor-revision", - "sfp-vendor-serial", - "sfp-manufacturing-date", - "eeprom-checksum", -] - DEVICE_ATTRIBUTES_NAT = [ "protocol", "dst-port", @@ -183,96 +131,124 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): new_switches = [] # Add switches - for sid, sid_uid, sid_name, sid_ref, sid_attr, sid_func in zip( - # Data point name + for switch, sid_func in zip( + # Switch type name [ "interface", - "nat", - "mangle", - "filter", - "ppp_secret", - "queue", - "kid-control", - "kid-control", ], - # Data point unique id - [ - "name", - "uniq-id", - "uniq-id", - "uniq-id", - "name", - "name", - "name", - "name", - ], - # Entry Name - [ - "name", - "name", - "name", - "name", - "name", - "name", - "name", - "name", - ], - # Entry Unique id - [ - "port-mac-address", - "uniq-id", - "uniq-id", - "uniq-id", - "name", - "name", - "name", - "name", - ], - # Attr - [ - DEVICE_ATTRIBUTES_IFACE, - DEVICE_ATTRIBUTES_NAT, - DEVICE_ATTRIBUTES_MANGLE, - DEVICE_ATTRIBUTES_FILTER, - DEVICE_ATTRIBUTES_PPP_SECRET, - DEVICE_ATTRIBUTES_QUEUE, - DEVICE_ATTRIBUTES_KIDCONTROL, - DEVICE_ATTRIBUTES_KIDCONTROL, - ], - # Switch function + # Entity function [ MikrotikControllerPortSwitch, - MikrotikControllerNATSwitch, - MikrotikControllerMangleSwitch, - MikrotikControllerFilterSwitch, - MikrotikControllerPPPSecretSwitch, - MikrotikControllerQueueSwitch, - MikrotikControllerKidcontrolSwitch, - MikrotikControllerKidcontrolPauseSwitch, ], ): - for uid in mikrotik_controller.data[sid]: - item_id = f"{inst}-{sid}-{mikrotik_controller.data[sid][uid][sid_uid]}" - if sid_func.__name__ == "MikrotikControllerKidcontrolPauseSwitch": - item_id = f"{inst}-kid-control-pause-{mikrotik_controller.data[sid][uid][sid_uid]}" - - _LOGGER.debug("Updating switch %s", item_id) + uid_switch = SWITCH_TYPES[switch] + for uid in mikrotik_controller.data[SWITCH_TYPES[switch].data_path]: + uid_data = mikrotik_controller.data[SWITCH_TYPES[switch].data_path] + item_id = f"{inst}-{switch}-{uid_data[uid][uid_switch.data_reference]}" + _LOGGER.debug("Updating sensor %s", item_id) if item_id in switches: if switches[item_id].enabled: switches[item_id].async_schedule_update_ha_state() continue - # Create new entity - sid_data = { - "sid": sid, - "sid_uid": sid_uid, - "sid_name": sid_name, - "sid_ref": sid_ref, - "sid_attr": sid_attr, - } - switches[item_id] = sid_func(inst, uid, mikrotik_controller, sid_data) + switches[item_id] = sid_func( + inst=inst, + uid=uid, + mikrotik_controller=mikrotik_controller, + entity_description=uid_switch, + ) new_switches.append(switches[item_id]) + # for sid, sid_uid, sid_name, sid_ref, sid_attr, sid_func in zip( + # # Data point name + # [ + # "interface", + # "nat", + # "mangle", + # "filter", + # "ppp_secret", + # "queue", + # "kid-control", + # "kid-control", + # ], + # # Data point unique id + # [ + # "name", + # "uniq-id", + # "uniq-id", + # "uniq-id", + # "name", + # "name", + # "name", + # "name", + # ], + # # Entry Name + # [ + # "name", + # "name", + # "name", + # "name", + # "name", + # "name", + # "name", + # "name", + # ], + # # Entry Unique id + # [ + # "port-mac-address", + # "uniq-id", + # "uniq-id", + # "uniq-id", + # "name", + # "name", + # "name", + # "name", + # ], + # # Attr + # [ + # DEVICE_ATTRIBUTES_IFACE, + # DEVICE_ATTRIBUTES_NAT, + # DEVICE_ATTRIBUTES_MANGLE, + # DEVICE_ATTRIBUTES_FILTER, + # DEVICE_ATTRIBUTES_PPP_SECRET, + # DEVICE_ATTRIBUTES_QUEUE, + # DEVICE_ATTRIBUTES_KIDCONTROL, + # DEVICE_ATTRIBUTES_KIDCONTROL, + # ], + # # Switch function + # [ + # MikrotikControllerPortSwitch, + # MikrotikControllerNATSwitch, + # MikrotikControllerMangleSwitch, + # MikrotikControllerFilterSwitch, + # MikrotikControllerPPPSecretSwitch, + # MikrotikControllerQueueSwitch, + # MikrotikControllerKidcontrolSwitch, + # MikrotikControllerKidcontrolPauseSwitch, + # ], + # ): + # for uid in mikrotik_controller.data[sid]: + # item_id = f"{inst}-{sid}-{mikrotik_controller.data[sid][uid][sid_uid]}" + # if sid_func.__name__ == "MikrotikControllerKidcontrolPauseSwitch": + # item_id = f"{inst}-kid-control-pause-{mikrotik_controller.data[sid][uid][sid_uid]}" + # + # _LOGGER.debug("Updating switch %s", item_id) + # if item_id in switches: + # if switches[item_id].enabled: + # switches[item_id].async_schedule_update_ha_state() + # continue + # + # # Create new entity + # sid_data = { + # "sid": sid, + # "sid_uid": sid_uid, + # "sid_name": sid_name, + # "sid_ref": sid_ref, + # "sid_attr": sid_attr, + # } + # switches[item_id] = sid_func(inst, uid, mikrotik_controller, sid_data) + # new_switches.append(switches[item_id]) + # if new_switches: async_add_entities(new_switches) @@ -283,28 +259,18 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): class MikrotikControllerSwitch(SwitchEntity, RestoreEntity): """Representation of a switch.""" - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - self._sid_data = sid_data + def __init__( + self, + inst, + uid, + mikrotik_controller, + entity_description: MikrotikSwitchEntityDescription, + ): + self.entity_description = entity_description self._inst = inst self._ctrl = mikrotik_controller - self._data = mikrotik_controller.data[self._sid_data["sid"]][uid] - - self._attrs = { - ATTR_ATTRIBUTION: ATTRIBUTION, - } - - async def async_added_to_hass(self): - """Run when entity about to be added to hass.""" - _LOGGER.debug( - "New switch %s (%s %s)", - self._inst, - self._sid_data["sid"], - self._data[self._sid_data["sid_uid"]], - ) - - async def async_update(self): - """Synchronize state with controller.""" + self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION} + self._data = mikrotik_controller.data[self.entity_description.data_path][uid] @property def available(self) -> bool: @@ -314,36 +280,99 @@ class MikrotikControllerSwitch(SwitchEntity, RestoreEntity): @property def name(self) -> str: """Return the name.""" - return f"{self._inst} {self._sid_data['sid']} {self._data[self._sid_data['sid_name']]}" + return f"{self._inst} {self.entity_description.name} {self._data[self.entity_description.data_name]}" @property def unique_id(self) -> str: """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self._sid_data['sid']}_switch-{self._data[self._sid_data['sid_ref']]}" + return f"{self._inst.lower()}-{self.entity_description.key}-{self._data[self.entity_description.data_reference].lower()}" @property def is_on(self) -> bool: """Return true if device is on.""" - return self._data["enabled"] + return self._data[self.entity_description.data_is_on] @property - def extra_state_attributes(self) -> Dict[str, Any]: - """Return the state attributes.""" - attributes = self._attrs + def icon(self) -> str: + """Return the icon.""" + if self._data[self.entity_description.data_is_on]: + return self.entity_description.icon_enabled + else: + return self.entity_description.icon_disabled - for variable in self._sid_data["sid_attr"]: + @property + def extra_state_attributes(self) -> Mapping[str, Any]: + """Return the state attributes.""" + attributes = super().extra_state_attributes + for variable in self.entity_description.data_attributes_list: if variable in self._data: attributes[format_attribute(variable)] = self._data[variable] return attributes + def turn_on(self, **kwargs: Any) -> None: + """Required abstract method.""" + pass + def turn_off(self, **kwargs: Any) -> None: """Required abstract method.""" pass - def turn_on(self, **kwargs: Any) -> None: - """Required abstract method.""" - pass + async def async_turn_on(self) -> None: + """Turn on the switch.""" + path = self.entity_description.data_switch_path + param = self.entity_description.data_reference + value = self._data[self.entity_description.data_reference] + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, False) + await self._ctrl.force_update() + + async def async_turn_off(self) -> None: + """Turn off the switch.""" + path = self.entity_description.data_switch_path + param = self.entity_description.data_reference + value = self._data[self.entity_description.data_reference] + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, True) + await self._ctrl.async_update() + + @property + def device_info(self) -> DeviceInfo: + """Return a description for device registry.""" + dev_connection = DOMAIN + dev_connection_value = self.entity_description.data_reference + dev_group = self.entity_description.ha_group + if self.entity_description.ha_group.startswith("data__"): + dev_group = self.entity_description.ha_group[6:] + if dev_group in self._data: + dev_group = self._data[dev_group] + dev_connection_value = dev_group + + if self.entity_description.ha_connection: + dev_connection = self.entity_description.ha_connection + + if self.entity_description.ha_connection_value: + dev_connection_value = self.entity_description.ha_connection_value + if dev_connection_value.startswith("data__"): + dev_connection_value = dev_connection_value[6:] + dev_connection_value = self._data[dev_connection_value] + + info = DeviceInfo( + connections={(dev_connection, f"{dev_connection_value}")}, + identifiers={(dev_connection, f"{dev_connection_value}")}, + default_name=f"{self._inst} {dev_group}", + model=f"{self._ctrl.data['resource']['board-name']}", + manufacturer=f"{self._ctrl.data['resource']['platform']}", + sw_version=f"{self._ctrl.data['resource']['version']}", + configuration_url=f"http://{self._ctrl.config_entry.data[CONF_HOST]}", + via_device=(DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}"), + ) + + return info + + async def async_added_to_hass(self): + """Run when entity about to be added to hass.""" + _LOGGER.debug("New switch %s (%s)", self._inst, self.unique_id) # --------------------------- @@ -352,24 +381,10 @@ class MikrotikControllerSwitch(SwitchEntity, RestoreEntity): class MikrotikControllerPortSwitch(MikrotikControllerSwitch): """Representation of a network port switch.""" - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - super().__init__(inst, uid, mikrotik_controller, sid_data) - @property - def name(self) -> str: - """Return the name.""" - return f"{self._inst} port {self._data[self._sid_data['sid_name']]}" - - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-enable_switch-{self._data['port-mac-address']}_{self._data['default-name']}" - - @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Mapping[str, Any]: """Return the state attributes.""" - attributes = self._attrs + attributes = super().extra_state_attributes if self._data["type"] == "ether": for variable in DEVICE_ATTRIBUTES_IFACE_ETHER: @@ -381,50 +396,33 @@ class MikrotikControllerPortSwitch(MikrotikControllerSwitch): if variable in self._data: attributes[format_attribute(variable)] = self._data[variable] - else: - for variable in self._sid_data["sid_attr"]: - if variable in self._data: - attributes[format_attribute(variable)] = self._data[variable] - return attributes @property def icon(self) -> str: """Return the icon.""" if self._data["running"]: - icon = "mdi:lan-connect" + icon = self.entity_description.icon_enabled else: - icon = "mdi:lan-pending" + icon = self.entity_description.icon_disabled if not self._data["enabled"]: icon = "mdi:lan-disconnect" return icon - @property - def device_info(self) -> Dict[str, Any]: - """Return a 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": f"{self._inst} {self._data['default-name']}", - } - return info - async def async_turn_on(self) -> Optional[str]: """Turn on the switch.""" - path = "/interface" - param = "default-name" + path = self.entity_description.data_switch_path + param = self.entity_description.data_reference if self._data["about"] == "managed by CAPsMAN": _LOGGER.error("Unable to enable %s, managed by CAPsMAN", self._data[param]) return "managed by CAPsMAN" if "-" in self._data["port-mac-address"]: param = "name" - value = self._data[param] - mod_param = "disabled" - mod_value = False - self._ctrl.set_value(path, param, value, mod_param, mod_value) + value = self._data[self.entity_description.data_reference] + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, False) if "poe-out" in self._data and self._data["poe-out"] == "off": path = "/interface/ethernet" @@ -434,17 +432,16 @@ class MikrotikControllerPortSwitch(MikrotikControllerSwitch): async def async_turn_off(self) -> Optional[str]: """Turn off the switch.""" - path = "/interface" - param = "default-name" + path = self.entity_description.data_switch_path + param = self.entity_description.data_reference if self._data["about"] == "managed by CAPsMAN": _LOGGER.error("Unable to disable %s, managed by CAPsMAN", self._data[param]) return "managed by CAPsMAN" if "-" in self._data["port-mac-address"]: param = "name" - value = self._data[param] - mod_param = "disabled" - mod_value = True - self._ctrl.set_value(path, param, value, mod_param, mod_value) + value = self._data[self.entity_description.data_reference] + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, True) if "poe-out" in self._data and self._data["poe-out"] == "auto-on": path = "/interface/ethernet" diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index a0a28b3..4fea1a0 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -157,17 +157,22 @@ class MikrotikSwitchEntityDescription(SwitchEntityDescription): data_attributes_list: List = field(default_factory=lambda: []) -SENSOR_TYPES = { - "system_temperature": MikrotikSwitchEntityDescription( - key="system_temperature", - name="Temperature", - icon="mdi:thermometer", +SWITCH_TYPES = { + "interface": MikrotikSwitchEntityDescription( + key="interface", + name="port", + icon_enabled="mdi:lan-connect", + icon_disabled="mdi:lan-pending", entity_category=None, - ha_group="System", - data_path="health", + ha_group="data__default-name", + ha_connection=CONNECTION_NETWORK_MAC, + ha_connection_value="data__port-mac-address", + data_path="interface", data_attribute="temperature", - data_name="", - data_uid="", - data_reference="", + data_switch_path="/interface", + data_name="name", + data_uid="name", + data_reference="default-name", + data_attributes_list=DEVICE_ATTRIBUTES_IFACE, ), } From 663e177a826e7012c8e67db4bba64d45fca3cceb Mon Sep 17 00:00:00 2001 From: Tomaae Date: Thu, 3 Feb 2022 10:29:04 +0100 Subject: [PATCH 18/36] renamed device info internal group attribute --- custom_components/mikrotik_router/sensor.py | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 56a5146..c42efcb 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -157,9 +157,9 @@ class MikrotikControllerSensor(SensorEntity): def __init__( self, - mikrotik_controller, inst, uid: "", + mikrotik_controller, entity_description: MikrotikSensorEntityDescription, ): """Initialize.""" @@ -226,16 +226,16 @@ class MikrotikControllerSensor(SensorEntity): """Return a description for device registry.""" dev_connection = DOMAIN dev_connection_value = self.entity_description.data_reference - dev_name = self.entity_description.ha_group + dev_group = self.entity_description.ha_group if self.entity_description.ha_group == "System": - dev_name = self._ctrl.data["resource"]["board-name"] + dev_group = self._ctrl.data["resource"]["board-name"] dev_connection_value = self._ctrl.data["routerboard"]["serial-number"] if self.entity_description.ha_group.startswith("data__"): - dev_name = self.entity_description.ha_group[6:] - if dev_name in self._data: - dev_name = self._data[dev_name] - dev_connection_value = dev_name + dev_group = self.entity_description.ha_group[6:] + if dev_group in self._data: + dev_group = self._data[dev_group] + dev_connection_value = dev_group if self.entity_description.ha_connection: dev_connection = self.entity_description.ha_connection @@ -249,7 +249,7 @@ class MikrotikControllerSensor(SensorEntity): info = DeviceInfo( connections={(dev_connection, f"{dev_connection_value}")}, identifiers={(dev_connection, f"{dev_connection_value}")}, - default_name=f"{self._inst} {dev_name}", + default_name=f"{self._inst} {dev_group}", model=f"{self._ctrl.data['resource']['board-name']}", manufacturer=f"{self._ctrl.data['resource']['platform']}", sw_version=f"{self._ctrl.data['resource']['version']}", @@ -258,17 +258,17 @@ class MikrotikControllerSensor(SensorEntity): ) if "mac-address" in self.entity_description.data_reference: - dev_name = self._data[self.entity_description.data_name] + dev_group = self._data[self.entity_description.data_name] dev_manufacturer = "" if dev_connection_value in self._ctrl.data["host"]: - dev_name = self._ctrl.data["host"][dev_connection_value]["host-name"] + dev_group = self._ctrl.data["host"][dev_connection_value]["host-name"] dev_manufacturer = self._ctrl.data["host"][dev_connection_value][ "manufacturer" ] info = DeviceInfo( connections={(dev_connection, f"{dev_connection_value}")}, - default_name=f"{dev_name}", + default_name=f"{dev_group}", manufacturer=f"{dev_manufacturer}", via_device=( DOMAIN, From 89d2dc95133c321d9a41844aa0ef4d3abda78e4a Mon Sep 17 00:00:00 2001 From: Tomaae Date: Thu, 3 Feb 2022 10:30:11 +0100 Subject: [PATCH 19/36] removed unused import --- custom_components/mikrotik_router/switch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index a5f66f4..8cf53fa 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -7,7 +7,6 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.const import CONF_NAME, CONF_HOST, ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity from .helper import format_attribute From 18f2e56691829dfd804c566c97a5e48300f2c9e7 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 20:33:54 +0100 Subject: [PATCH 20/36] added description variable to prioritize entry comment over name --- custom_components/mikrotik_router/switch.py | 5 +++++ custom_components/mikrotik_router/switch_types.py | 1 + 2 files changed, 6 insertions(+) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index 8cf53fa..a6ce7c0 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -279,6 +279,11 @@ class MikrotikControllerSwitch(SwitchEntity, RestoreEntity): @property def name(self) -> str: """Return the name.""" + if self.entity_description.data_name_comment and self._data["comment"]: + return ( + f"{self._inst} {self.entity_description.name} {self._data['comment']}" + ) + return f"{self._inst} {self.entity_description.name} {self._data[self.entity_description.data_name]}" @property diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index 4fea1a0..3e64deb 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -152,6 +152,7 @@ class MikrotikSwitchEntityDescription(SwitchEntityDescription): data_switch_path: str = "" data_switch_parameter: str = "disabled" data_name: str = "" + data_name_comment: bool = False data_uid: str = "" data_reference: str = "" data_attributes_list: List = field(default_factory=lambda: []) From 6206c84cc9942b4f0f38619dc3eb1eeb15605831 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 20:34:51 +0100 Subject: [PATCH 21/36] redo nat switches --- custom_components/mikrotik_router/switch.py | 57 +++---------------- .../mikrotik_router/switch_types.py | 19 ++++++- 2 files changed, 25 insertions(+), 51 deletions(-) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index a6ce7c0..0aaa8d3 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -134,11 +134,10 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): # Switch type name [ "interface", + "nat", ], # Entity function - [ - MikrotikControllerPortSwitch, - ], + [MikrotikControllerPortSwitch, MikrotikControllerNATSwitch], ): uid_switch = SWITCH_TYPES[switch] for uid in mikrotik_controller.data[SWITCH_TYPES[switch].data_path]: @@ -460,10 +459,6 @@ class MikrotikControllerPortSwitch(MikrotikControllerSwitch): class MikrotikControllerNATSwitch(MikrotikControllerSwitch): """Representation of a NAT switch.""" - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - super().__init__(inst, uid, mikrotik_controller, sid_data) - @property def name(self) -> str: """Return the name.""" @@ -472,43 +467,9 @@ class MikrotikControllerNATSwitch(MikrotikControllerSwitch): return f"{self._inst} NAT {self._data['name']}" - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-enable_nat-{self._data['uniq-id']}" - - @property - def icon(self) -> str: - """Return the icon.""" - if not self._data["enabled"]: - icon = "mdi:network-off-outline" - else: - icon = "mdi:network-outline" - - return icon - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "identifiers": { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "switch", - "NAT", - ) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} NAT", - } - return info - async def async_turn_on(self) -> None: """Turn on the switch.""" - path = "/ip/firewall/nat" + path = self.entity_description.data_switch_path param = ".id" value = None for uid in self._ctrl.data["nat"]: @@ -519,14 +480,13 @@ class MikrotikControllerNATSwitch(MikrotikControllerSwitch): ): value = self._ctrl.data["nat"][uid][".id"] - mod_param = "disabled" - mod_value = False - self._ctrl.set_value(path, param, value, mod_param, mod_value) + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, False) await self._ctrl.force_update() async def async_turn_off(self) -> None: """Turn off the switch.""" - path = "/ip/firewall/nat" + path = self.entity_description.data_switch_path param = ".id" value = None for uid in self._ctrl.data["nat"]: @@ -537,9 +497,8 @@ class MikrotikControllerNATSwitch(MikrotikControllerSwitch): ): value = self._ctrl.data["nat"][uid][".id"] - mod_param = "disabled" - mod_value = True - self._ctrl.set_value(path, param, value, mod_param, mod_value) + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, True) await self._ctrl.async_update() diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index 3e64deb..6cb3ba7 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -147,7 +147,6 @@ class MikrotikSwitchEntityDescription(SwitchEntityDescription): ha_connection: str = "" ha_connection_value: str = "" data_path: str = "" - data_attribute: str = "" data_is_on: str = "enabled" data_switch_path: str = "" data_switch_parameter: str = "disabled" @@ -169,11 +168,27 @@ SWITCH_TYPES = { ha_connection=CONNECTION_NETWORK_MAC, ha_connection_value="data__port-mac-address", data_path="interface", - data_attribute="temperature", data_switch_path="/interface", data_name="name", data_uid="name", data_reference="default-name", data_attributes_list=DEVICE_ATTRIBUTES_IFACE, ), + "nat": MikrotikSwitchEntityDescription( + key="nat", + name="NAT", + icon_enabled="network-outline", + icon_disabled="network-off-outline", + entity_category=None, + ha_group="NAT", + ha_connection=DOMAIN, + ha_connection_value="NAT", + data_path="nat", + data_switch_path="/ip/firewall/nat", + data_name="name", + data_name_comment=True, + data_uid="uniq-id", + data_reference="uniq-id", + data_attributes_list=DEVICE_ATTRIBUTES_NAT, + ), } From 6a659c51bf220a5b4eaa0883d465eda4ad7ba6d1 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 20:44:26 +0100 Subject: [PATCH 22/36] fixed missing prefix for nat switches --- custom_components/mikrotik_router/switch_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index 6cb3ba7..e1d0b23 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -177,8 +177,8 @@ SWITCH_TYPES = { "nat": MikrotikSwitchEntityDescription( key="nat", name="NAT", - icon_enabled="network-outline", - icon_disabled="network-off-outline", + icon_enabled="mdi:network-outline", + icon_disabled="mdi:network-off-outline", entity_category=None, ha_group="NAT", ha_connection=DOMAIN, From 2b0e364f51658561ba8994ceb920439fcb8189f2 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 20:44:59 +0100 Subject: [PATCH 23/36] redo mangle switches --- custom_components/mikrotik_router/switch.py | 92 +++---------------- .../mikrotik_router/switch_types.py | 17 ++++ 2 files changed, 29 insertions(+), 80 deletions(-) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index 0aaa8d3..36e7e52 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -20,27 +20,6 @@ from .switch_types import ( _LOGGER = logging.getLogger(__name__) -DEVICE_ATTRIBUTES_NAT = [ - "protocol", - "dst-port", - "in-interface", - "to-addresses", - "to-ports", - "comment", -] - -DEVICE_ATTRIBUTES_MANGLE = [ - "chain", - "action", - "passthrough", - "protocol", - "src-address", - "src-port", - "dst-address", - "dst-port", - "comment", -] - DEVICE_ATTRIBUTES_FILTER = [ "chain", "action", @@ -132,12 +111,13 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): # Add switches for switch, sid_func in zip( # Switch type name - [ - "interface", - "nat", - ], + ["interface", "nat", "mangle"], # Entity function - [MikrotikControllerPortSwitch, MikrotikControllerNATSwitch], + [ + MikrotikControllerPortSwitch, + MikrotikControllerNATSwitch, + MikrotikControllerMangleSwitch, + ], ): uid_switch = SWITCH_TYPES[switch] for uid in mikrotik_controller.data[SWITCH_TYPES[switch].data_path]: @@ -508,55 +488,9 @@ class MikrotikControllerNATSwitch(MikrotikControllerSwitch): class MikrotikControllerMangleSwitch(MikrotikControllerSwitch): """Representation of a Mangle switch.""" - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - super().__init__(inst, uid, mikrotik_controller, sid_data) - - @property - def name(self) -> str: - """Return the name.""" - if self._data["comment"]: - return f"{self._inst} Mangle {self._data['comment']}" - - return f"{self._inst} Mangle {self._data['name']}" - - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-enable_mangle-{self._data['uniq-id']}" - - @property - def icon(self) -> str: - """Return the icon.""" - if not self._data["enabled"]: - icon = "mdi:bookmark-off-outline" - else: - icon = "mdi:bookmark-outline" - - return icon - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "identifiers": { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "switch", - "Mangle", - ) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} Mangle", - } - return info - async def async_turn_on(self) -> None: """Turn on the switch.""" - path = "/ip/firewall/mangle" + path = self.entity_description.data_switch_path param = ".id" value = None for uid in self._ctrl.data["mangle"]: @@ -568,14 +502,13 @@ class MikrotikControllerMangleSwitch(MikrotikControllerSwitch): ): value = self._ctrl.data["mangle"][uid][".id"] - mod_param = "disabled" - mod_value = False - self._ctrl.set_value(path, param, value, mod_param, mod_value) + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, False) await self._ctrl.force_update() async def async_turn_off(self) -> None: """Turn off the switch.""" - path = "/ip/firewall/mangle" + path = self.entity_description.data_switch_path param = ".id" value = None for uid in self._ctrl.data["mangle"]: @@ -587,9 +520,8 @@ class MikrotikControllerMangleSwitch(MikrotikControllerSwitch): ): value = self._ctrl.data["mangle"][uid][".id"] - mod_param = "disabled" - mod_value = True - self._ctrl.set_value(path, param, value, mod_param, mod_value) + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, True) await self._ctrl.async_update() diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index e1d0b23..c823a88 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -191,4 +191,21 @@ SWITCH_TYPES = { data_reference="uniq-id", data_attributes_list=DEVICE_ATTRIBUTES_NAT, ), + "mangle": MikrotikSwitchEntityDescription( + key="mangle", + name="Mangle", + icon_enabled="mdi:bookmark-outline", + icon_disabled="mdi:bookmark-off-outline", + entity_category=None, + ha_group="Mangle", + ha_connection=DOMAIN, + ha_connection_value="Mangle", + data_path="mangle", + data_switch_path="/ip/firewall/mangle", + data_name="name", + data_name_comment=True, + data_uid="uniq-id", + data_reference="uniq-id", + data_attributes_list=DEVICE_ATTRIBUTES_MANGLE, + ), } From 9b2d5c9b68799603526bba7f8343603489e1136c Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 20:52:22 +0100 Subject: [PATCH 24/36] redo filter switches --- custom_components/mikrotik_router/switch.py | 80 ++----------------- .../mikrotik_router/switch_types.py | 17 ++++ 2 files changed, 25 insertions(+), 72 deletions(-) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index 36e7e52..b77efb4 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -20,23 +20,6 @@ from .switch_types import ( _LOGGER = logging.getLogger(__name__) -DEVICE_ATTRIBUTES_FILTER = [ - "chain", - "action", - "address-list", - "protocol", - "layer7-protocol", - "tcp-flags", - "connection-state", - "in-interface", - "src-address", - "src-port", - "out-interface", - "dst-address", - "dst-port", - "comment", -] - DEVICE_ATTRIBUTES_PPP_SECRET = [ "connected", "service", @@ -111,12 +94,13 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): # Add switches for switch, sid_func in zip( # Switch type name - ["interface", "nat", "mangle"], + ["interface", "nat", "mangle", "filter"], # Entity function [ MikrotikControllerPortSwitch, MikrotikControllerNATSwitch, MikrotikControllerMangleSwitch, + MikrotikControllerFilterSwitch, ], ): uid_switch = SWITCH_TYPES[switch] @@ -531,55 +515,9 @@ class MikrotikControllerMangleSwitch(MikrotikControllerSwitch): class MikrotikControllerFilterSwitch(MikrotikControllerSwitch): """Representation of a Filter switch.""" - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - super().__init__(inst, uid, mikrotik_controller, sid_data) - - @property - def name(self) -> str: - """Return the name.""" - if self._data["comment"]: - return f"{self._inst} Filter {self._data['comment']}" - - return f"{self._inst} Filter {self._data['name']}" - - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-enable_filter-{self._data['uniq-id']}" - - @property - def icon(self) -> str: - """Return the icon.""" - if not self._data["enabled"]: - icon = "mdi:filter-variant-remove" - else: - icon = "mdi:filter-variant" - - return icon - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "identifiers": { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "switch", - "Filter", - ) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} Filter", - } - return info - async def async_turn_on(self) -> None: """Turn on the switch.""" - path = "/ip/firewall/filter" + path = self.entity_description.data_switch_path param = ".id" value = None for uid in self._ctrl.data["filter"]: @@ -590,14 +528,13 @@ class MikrotikControllerFilterSwitch(MikrotikControllerSwitch): ): value = self._ctrl.data["filter"][uid][".id"] - mod_param = "disabled" - mod_value = False - self._ctrl.set_value(path, param, value, mod_param, mod_value) + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, False) await self._ctrl.force_update() async def async_turn_off(self) -> None: """Turn off the switch.""" - path = "/ip/firewall/filter" + path = self.entity_description.data_switch_path param = ".id" value = None for uid in self._ctrl.data["filter"]: @@ -608,9 +545,8 @@ class MikrotikControllerFilterSwitch(MikrotikControllerSwitch): ): value = self._ctrl.data["filter"][uid][".id"] - mod_param = "disabled" - mod_value = True - self._ctrl.set_value(path, param, value, mod_param, mod_value) + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, True) await self._ctrl.async_update() diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index c823a88..a750fee 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -208,4 +208,21 @@ SWITCH_TYPES = { data_reference="uniq-id", data_attributes_list=DEVICE_ATTRIBUTES_MANGLE, ), + "filter": MikrotikSwitchEntityDescription( + key="filter", + name="Filter", + icon_enabled="mdi:filter-variant", + icon_disabled="mdi:filter-variant-remove", + entity_category=None, + ha_group="Filter", + ha_connection=DOMAIN, + ha_connection_value="Filter", + data_path="filter", + data_switch_path="/ip/firewall/filter", + data_name="name", + data_name_comment=True, + data_uid="uniq-id", + data_reference="uniq-id", + data_attributes_list=DEVICE_ATTRIBUTES_FILTER, + ), } From 9b27bf4241071ffcd43b92e4e2098b2e62ff92a5 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 21:01:23 +0100 Subject: [PATCH 25/36] redo ppp switches --- custom_components/mikrotik_router/switch.py | 81 +------------------ .../mikrotik_router/switch_types.py | 16 ++++ 2 files changed, 18 insertions(+), 79 deletions(-) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index b77efb4..8f998fe 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -20,14 +20,6 @@ from .switch_types import ( _LOGGER = logging.getLogger(__name__) -DEVICE_ATTRIBUTES_PPP_SECRET = [ - "connected", - "service", - "profile", - "comment", - "caller-id", - "encoding", -] DEVICE_ATTRIBUTES_KIDCONTROL = [ "rate-limit", @@ -94,13 +86,14 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): # Add switches for switch, sid_func in zip( # Switch type name - ["interface", "nat", "mangle", "filter"], + ["interface", "nat", "mangle", "filter", "ppp_secret"], # Entity function [ MikrotikControllerPortSwitch, MikrotikControllerNATSwitch, MikrotikControllerMangleSwitch, MikrotikControllerFilterSwitch, + MikrotikControllerSwitch, ], ): uid_switch = SWITCH_TYPES[switch] @@ -550,76 +543,6 @@ class MikrotikControllerFilterSwitch(MikrotikControllerSwitch): await self._ctrl.async_update() -# --------------------------- -# MikrotikControllerPPPSecretSwitch -# --------------------------- -class MikrotikControllerPPPSecretSwitch(MikrotikControllerSwitch): - """Representation of a PPP Secret switch.""" - - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - super().__init__(inst, uid, mikrotik_controller, sid_data) - - @property - def name(self) -> str: - """Return the name.""" - return f"{self._inst} PPP Secret {self._data['name']}" - - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-enable_ppp_secret-{self._data['name']}" - - @property - def icon(self) -> str: - """Return the icon.""" - if not self._data["enabled"]: - icon = "mdi:account-off-outline" - else: - icon = "mdi:account-outline" - - return icon - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "identifiers": { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "switch", - "PPP", - ) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} PPP", - } - return info - - async def async_turn_on(self) -> None: - """Turn on the switch.""" - path = "/ppp/secret" - param = "name" - value = self._data["name"] - mod_param = "disabled" - mod_value = False - self._ctrl.set_value(path, param, value, mod_param, mod_value) - await self._ctrl.force_update() - - async def async_turn_off(self) -> None: - """Turn off the switch.""" - path = "/ppp/secret" - param = "name" - value = self._data["name"] - mod_param = "disabled" - mod_value = True - self._ctrl.set_value(path, param, value, mod_param, mod_value) - await self._ctrl.async_update() - - # --------------------------- # MikrotikControllerQueueSwitch # --------------------------- diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index a750fee..08f977a 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -225,4 +225,20 @@ SWITCH_TYPES = { data_reference="uniq-id", data_attributes_list=DEVICE_ATTRIBUTES_FILTER, ), + "ppp_secret": MikrotikSwitchEntityDescription( + key="ppp_secret", + name="PPP Secret", + icon_enabled="mdi:account-outline", + icon_disabled="mdi:account-off-outline", + entity_category=None, + ha_group="PPP", + ha_connection=DOMAIN, + ha_connection_value="PPP", + data_path="ppp_secret", + data_switch_path="/ppp/secret", + data_name="name", + data_uid="name", + data_reference="name", + data_attributes_list=DEVICE_ATTRIBUTES_PPP_SECRET, + ), } From 9a799828b03f4992e6ac31b3069d8a22800d3403 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 21:24:24 +0100 Subject: [PATCH 26/36] redo queue switches --- custom_components/mikrotik_router/switch.py | 221 ++---------------- .../mikrotik_router/switch_types.py | 16 ++ 2 files changed, 31 insertions(+), 206 deletions(-) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index 8f998fe..862281b 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -21,37 +21,6 @@ from .switch_types import ( _LOGGER = logging.getLogger(__name__) -DEVICE_ATTRIBUTES_KIDCONTROL = [ - "rate-limit", - "mon", - "tue", - "wed", - "thu", - "fri", - "sat", - "sun", -] - -DEVICE_ATTRIBUTES_QUEUE = [ - "target", - "download-rate", - "upload-rate", - "download-max-limit", - "upload-max-limit", - "upload-limit-at", - "download-limit-at", - "upload-burst-limit", - "download-burst-limit", - "upload-burst-threshold", - "download-burst-threshold", - "upload-burst-time", - "download-burst-time", - "packet-marks", - "parent", - "comment", -] - - # --------------------------- # async_setup_entry # --------------------------- @@ -86,7 +55,14 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): # Add switches for switch, sid_func in zip( # Switch type name - ["interface", "nat", "mangle", "filter", "ppp_secret"], + [ + "interface", + "nat", + "mangle", + "filter", + "ppp_secret", + "queue", + ], # Entity function [ MikrotikControllerPortSwitch, @@ -94,6 +70,7 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): MikrotikControllerMangleSwitch, MikrotikControllerFilterSwitch, MikrotikControllerSwitch, + MikrotikControllerQueueSwitch, ], ): uid_switch = SWITCH_TYPES[switch] @@ -549,196 +526,28 @@ class MikrotikControllerFilterSwitch(MikrotikControllerSwitch): class MikrotikControllerQueueSwitch(MikrotikControllerSwitch): """Representation of a queue switch.""" - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - super().__init__(inst, uid, mikrotik_controller, sid_data) - - @property - def icon(self) -> str: - """Return the icon.""" - if not self._data["enabled"]: - icon = "mdi:leaf-off" - else: - icon = "mdi:leaf" - - return icon - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "identifiers": { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "switch", - "Queue", - ) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} Queue", - } - return info - async def async_turn_on(self) -> None: """Turn on the switch.""" - path = "/queue/simple" + path = self.entity_description.data_switch_path param = ".id" value = None for uid in self._ctrl.data["queue"]: if self._ctrl.data["queue"][uid]["name"] == f"{self._data['name']}": value = self._ctrl.data["queue"][uid][".id"] - mod_param = "disabled" - mod_value = False - self._ctrl.set_value(path, param, value, mod_param, mod_value) + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, False) await self._ctrl.force_update() async def async_turn_off(self) -> None: """Turn off the switch.""" - path = "/queue/simple" + path = self.entity_description.data_switch_path param = ".id" value = None for uid in self._ctrl.data["queue"]: if self._ctrl.data["queue"][uid]["name"] == f"{self._data['name']}": value = self._ctrl.data["queue"][uid][".id"] - mod_param = "disabled" - mod_value = True - self._ctrl.set_value(path, param, value, mod_param, mod_value) - await self._ctrl.async_update() - - -# --------------------------- -# MikrotikControllerKidcontrolSwitch -# --------------------------- -class MikrotikControllerKidcontrolSwitch(MikrotikControllerSwitch): - """Representation of a queue switch.""" - - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - super().__init__(inst, uid, mikrotik_controller, sid_data) - - @property - def icon(self) -> str: - """Return the icon.""" - if not self._data["enabled"]: - icon = "mdi:account-off" - else: - icon = "mdi:account" - - return icon - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "identifiers": { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "switch", - "Kidcontrol", - ) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} Kidcontrol", - } - return info - - async def async_turn_on(self) -> None: - """Turn on the switch.""" - path = "/ip/kid-control" - param = "name" - value = self._data["name"] - mod_param = "disabled" - mod_value = False - self._ctrl.set_value(path, param, value, mod_param, mod_value) - await self._ctrl.force_update() - - async def async_turn_off(self) -> None: - """Turn off the switch.""" - path = "/ip/kid-control" - param = "name" - value = self._data["name"] - mod_param = "disabled" - mod_value = True - self._ctrl.set_value(path, param, value, mod_param, mod_value) - await self._ctrl.async_update() - - -# --------------------------- -# MikrotikControllerKidcontrolPauseSwitch -# --------------------------- -class MikrotikControllerKidcontrolPauseSwitch(MikrotikControllerSwitch): - """Representation of a queue switch.""" - - def __init__(self, inst, uid, mikrotik_controller, sid_data): - """Initialize.""" - super().__init__(inst, uid, mikrotik_controller, sid_data) - - @property - def name(self) -> str: - """Return the name.""" - return f"{self._inst} {self._sid_data['sid']} Pause {self._data[self._sid_data['sid_name']]}" - - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self._sid_data['sid']}-pause_switch-{self._data[self._sid_data['sid_ref']]}" - - @property - def is_on(self) -> bool: - """Return true if device is on.""" - return self._data["paused"] - - @property - def icon(self) -> str: - """Return the icon.""" - if not self._data["enabled"]: - icon = "mdi:account-off-outline" - else: - icon = "mdi:account-outline" - - return icon - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "identifiers": { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "switch", - "Kidcontrol", - ) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} Kidcontrol", - } - return info - - async def async_turn_on(self) -> None: - """Turn on the switch.""" - path = "/ip/kid-control" - param = "name" - value = self._data["name"] - command = "resume" - self._ctrl.execute(path, command, param, value) - await self._ctrl.force_update() - - async def async_turn_off(self) -> None: - """Turn off the switch.""" - path = "/ip/kid-control" - param = "name" - value = self._data["name"] - command = "pause" - self._ctrl.execute(path, command, param, value) + mod_param = self.entity_description.data_switch_parameter + self._ctrl.set_value(path, param, value, mod_param, True) await self._ctrl.async_update() diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index 08f977a..1151291 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -241,4 +241,20 @@ SWITCH_TYPES = { data_reference="name", data_attributes_list=DEVICE_ATTRIBUTES_PPP_SECRET, ), + "queue": MikrotikSwitchEntityDescription( + key="queue", + name="Queue", + icon_enabled="mdi:leaf", + icon_disabled="mdi:leaf-off", + entity_category=None, + ha_group="Queue", + ha_connection=DOMAIN, + ha_connection_value="Queue", + data_path="queue", + data_switch_path="/queue/simple", + data_name="name", + data_uid="name", + data_reference="name", + data_attributes_list=DEVICE_ATTRIBUTES_QUEUE, + ), } From 64303a329740f0eaed636f74ee78d0086b2aa592 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 21:25:01 +0100 Subject: [PATCH 27/36] redo kidcontrol switches --- custom_components/mikrotik_router/switch.py | 29 ++++++++++++++++ .../mikrotik_router/switch_types.py | 33 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index 862281b..37a87a5 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -62,6 +62,8 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): "filter", "ppp_secret", "queue", + "kidcontrol_enable", + "kidcontrol_pause", ], # Entity function [ @@ -71,6 +73,8 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): MikrotikControllerFilterSwitch, MikrotikControllerSwitch, MikrotikControllerQueueSwitch, + MikrotikControllerSwitch, + MikrotikControllerKidcontrolPauseSwitch, ], ): uid_switch = SWITCH_TYPES[switch] @@ -551,3 +555,28 @@ class MikrotikControllerQueueSwitch(MikrotikControllerSwitch): mod_param = self.entity_description.data_switch_parameter self._ctrl.set_value(path, param, value, mod_param, True) await self._ctrl.async_update() + + +# --------------------------- +# MikrotikControllerKidcontrolPauseSwitch +# --------------------------- +class MikrotikControllerKidcontrolPauseSwitch(MikrotikControllerSwitch): + """Representation of a queue switch.""" + + async def async_turn_on(self) -> None: + """Turn on the switch.""" + path = self.entity_description.data_switch_path + param = self.entity_description.data_reference + value = self._data[self.entity_description.data_reference] + command = "resume" + self._ctrl.execute(path, command, param, value) + await self._ctrl.force_update() + + async def async_turn_off(self) -> None: + """Turn off the switch.""" + path = self.entity_description.data_switch_path + param = self.entity_description.data_reference + value = self._data[self.entity_description.data_reference] + command = "pause" + self._ctrl.execute(path, command, param, value) + await self._ctrl.async_update() diff --git a/custom_components/mikrotik_router/switch_types.py b/custom_components/mikrotik_router/switch_types.py index 1151291..9f8c9fa 100644 --- a/custom_components/mikrotik_router/switch_types.py +++ b/custom_components/mikrotik_router/switch_types.py @@ -257,4 +257,37 @@ SWITCH_TYPES = { data_reference="name", data_attributes_list=DEVICE_ATTRIBUTES_QUEUE, ), + "kidcontrol_enable": MikrotikSwitchEntityDescription( + key="kidcontrol_enable", + name="kidcontrol", + icon_enabled="mdi:account", + icon_disabled="mdi:account-off", + entity_category=None, + ha_group="Kidcontrol", + ha_connection=DOMAIN, + ha_connection_value="Kidcontrol", + data_path="kid-control", + data_switch_path="/ip/kid-control", + data_name="name", + data_uid="name", + data_reference="name", + data_attributes_list=DEVICE_ATTRIBUTES_KIDCONTROL, + ), + "kidcontrol_pause": MikrotikSwitchEntityDescription( + key="kidcontrol_paused", + name="kidcontrol paused", + icon_enabled="mdi:account-outline", + icon_disabled="mdi:account-off-outline", + entity_category=None, + ha_group="Kidcontrol", + ha_connection=DOMAIN, + ha_connection_value="Kidcontrol", + data_path="kid-control", + data_is_on="paused", + data_switch_path="/ip/kid-control", + data_name="name", + data_uid="name", + data_reference="name", + data_attributes_list=DEVICE_ATTRIBUTES_KIDCONTROL, + ), } From 0b0e15be25ab95fe6e94b311d498900ad395a684 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 21:26:10 +0100 Subject: [PATCH 28/36] removed old code from switches --- custom_components/mikrotik_router/switch.py | 92 +-------------------- 1 file changed, 1 insertion(+), 91 deletions(-) diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index 37a87a5..815aec0 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -1,7 +1,7 @@ """Support for the Mikrotik Router switches.""" import logging -from typing import Any, Dict, Optional +from typing import Any, Optional from collections.abc import Mapping from homeassistant.components.switch import SwitchEntity from homeassistant.const import CONF_NAME, CONF_HOST, ATTR_ATTRIBUTION @@ -95,96 +95,6 @@ def update_items(inst, mikrotik_controller, async_add_entities, switches): ) new_switches.append(switches[item_id]) - # for sid, sid_uid, sid_name, sid_ref, sid_attr, sid_func in zip( - # # Data point name - # [ - # "interface", - # "nat", - # "mangle", - # "filter", - # "ppp_secret", - # "queue", - # "kid-control", - # "kid-control", - # ], - # # Data point unique id - # [ - # "name", - # "uniq-id", - # "uniq-id", - # "uniq-id", - # "name", - # "name", - # "name", - # "name", - # ], - # # Entry Name - # [ - # "name", - # "name", - # "name", - # "name", - # "name", - # "name", - # "name", - # "name", - # ], - # # Entry Unique id - # [ - # "port-mac-address", - # "uniq-id", - # "uniq-id", - # "uniq-id", - # "name", - # "name", - # "name", - # "name", - # ], - # # Attr - # [ - # DEVICE_ATTRIBUTES_IFACE, - # DEVICE_ATTRIBUTES_NAT, - # DEVICE_ATTRIBUTES_MANGLE, - # DEVICE_ATTRIBUTES_FILTER, - # DEVICE_ATTRIBUTES_PPP_SECRET, - # DEVICE_ATTRIBUTES_QUEUE, - # DEVICE_ATTRIBUTES_KIDCONTROL, - # DEVICE_ATTRIBUTES_KIDCONTROL, - # ], - # # Switch function - # [ - # MikrotikControllerPortSwitch, - # MikrotikControllerNATSwitch, - # MikrotikControllerMangleSwitch, - # MikrotikControllerFilterSwitch, - # MikrotikControllerPPPSecretSwitch, - # MikrotikControllerQueueSwitch, - # MikrotikControllerKidcontrolSwitch, - # MikrotikControllerKidcontrolPauseSwitch, - # ], - # ): - # for uid in mikrotik_controller.data[sid]: - # item_id = f"{inst}-{sid}-{mikrotik_controller.data[sid][uid][sid_uid]}" - # if sid_func.__name__ == "MikrotikControllerKidcontrolPauseSwitch": - # item_id = f"{inst}-kid-control-pause-{mikrotik_controller.data[sid][uid][sid_uid]}" - # - # _LOGGER.debug("Updating switch %s", item_id) - # if item_id in switches: - # if switches[item_id].enabled: - # switches[item_id].async_schedule_update_ha_state() - # continue - # - # # Create new entity - # sid_data = { - # "sid": sid, - # "sid_uid": sid_uid, - # "sid_name": sid_name, - # "sid_ref": sid_ref, - # "sid_attr": sid_attr, - # } - # switches[item_id] = sid_func(inst, uid, mikrotik_controller, sid_data) - # new_switches.append(switches[item_id]) - # if new_switches: async_add_entities(new_switches) From 889d270eb7012f60ef214f7289de1dd4c7400077 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 21:39:55 +0100 Subject: [PATCH 29/36] created types definition file for binary sensors --- .../mikrotik_router/binary_sensor_types.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 custom_components/mikrotik_router/binary_sensor_types.py diff --git a/custom_components/mikrotik_router/binary_sensor_types.py b/custom_components/mikrotik_router/binary_sensor_types.py new file mode 100644 index 0000000..e81bccc --- /dev/null +++ b/custom_components/mikrotik_router/binary_sensor_types.py @@ -0,0 +1,43 @@ +"""Definitions for Mikrotik Router sensor entities.""" +from dataclasses import dataclass, field +from typing import List +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import EntityCategory +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntityDescription, +) + +from .const import DOMAIN + + +@dataclass +class MikrotikBinarySensorEntityDescription(BinarySensorEntityDescription): + """Class describing mikrotik entities.""" + + ha_group: str = "" + ha_connection: str = "" + ha_connection_value: str = "" + data_path: str = "" + data_attribute: str = "" + data_name: str = "" + data_uid: str = "" + data_reference: str = "" + data_attributes_list: List = field(default_factory=lambda: []) + + +SENSOR_TYPES = { + "system_temperature": MikrotikBinarySensorEntityDescription( + key="system_temperature", + name="Temperature", + icon="mdi:thermometer", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + entity_category=EntityCategory.DIAGNOSTIC, + ha_group="System", + data_path="health", + data_attribute="temperature", + data_name="", + data_uid="", + data_reference="", + ), +} From d74db8f57d01f9d3cbc2f6d1be119168fb9306ef Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 21:50:25 +0100 Subject: [PATCH 30/36] added is_on into types definition file for binary sensors --- custom_components/mikrotik_router/binary_sensor_types.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/mikrotik_router/binary_sensor_types.py b/custom_components/mikrotik_router/binary_sensor_types.py index e81bccc..1fa6ff4 100644 --- a/custom_components/mikrotik_router/binary_sensor_types.py +++ b/custom_components/mikrotik_router/binary_sensor_types.py @@ -19,7 +19,7 @@ class MikrotikBinarySensorEntityDescription(BinarySensorEntityDescription): ha_connection: str = "" ha_connection_value: str = "" data_path: str = "" - data_attribute: str = "" + data_is_on: str = "available" data_name: str = "" data_uid: str = "" data_reference: str = "" @@ -35,7 +35,6 @@ SENSOR_TYPES = { entity_category=EntityCategory.DIAGNOSTIC, ha_group="System", data_path="health", - data_attribute="temperature", data_name="", data_uid="", data_reference="", From 8b2f75a0ff55799571521608259121dc475611a9 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 22:06:45 +0100 Subject: [PATCH 31/36] redo system binary sensors --- .../mikrotik_router/binary_sensor.py | 342 +++++++++++------- .../mikrotik_router/binary_sensor_types.py | 10 +- 2 files changed, 212 insertions(+), 140 deletions(-) diff --git a/custom_components/mikrotik_router/binary_sensor.py b/custom_components/mikrotik_router/binary_sensor.py index cc86436..5d40878 100644 --- a/custom_components/mikrotik_router/binary_sensor.py +++ b/custom_components/mikrotik_router/binary_sensor.py @@ -2,8 +2,8 @@ import logging from typing import Any, Dict, Optional - -from homeassistant.helpers.entity import EntityCategory +from collections.abc import Mapping +from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.components.binary_sensor import ( BinarySensorEntity, BinarySensorDeviceClass, @@ -28,24 +28,13 @@ from .const import ( DEFAULT_SENSOR_PORT_TRACKER, ) +from .binary_sensor_types import ( + MikrotikBinarySensorEntityDescription, + SENSOR_TYPES, +) + _LOGGER = logging.getLogger(__name__) -ATTR_LABEL = "label" -ATTR_GROUP = "group" -ATTR_PATH = "data_path" -ATTR_ATTR = "data_attr" -ATTR_CTGR = "entity_category" - -SENSOR_TYPES = { - "system_fwupdate": { - ATTR_DEVICE_CLASS: BinarySensorDeviceClass.UPDATE, - ATTR_LABEL: "Firmware update", - ATTR_GROUP: "System", - ATTR_PATH: "fw-update", - ATTR_ATTR: "available", - ATTR_CTGR: EntityCategory.DIAGNOSTIC, - }, -} DEVICE_ATTRIBUTES_IFACE = [ "running", @@ -148,71 +137,129 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se """Update sensor state from the controller.""" new_sensors = [] - # Add switches - for sid, sid_uid, sid_name, sid_ref, sid_attr, sid_func in zip( - # Data point name - ["ppp_secret", "interface"], - # Data point unique id - ["name", "default-name"], - # Entry Name - ["name", "name"], - # Entry Unique id - ["name", "port-mac-address"], - # Attr - [None, DEVICE_ATTRIBUTES_IFACE], - # Tracker function - [ - MikrotikControllerPPPSecretBinarySensor, - MikrotikControllerPortBinarySensor, - ], - ): - if ( - sid_func == MikrotikControllerPortBinarySensor - and not config_entry.options.get( - CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER - ) - ): - continue - for uid in mikrotik_controller.data[sid]: - if ( - # Skip if interface is wlan - sid == "interface" - and mikrotik_controller.data[sid][uid]["type"] == "wlan" - ): - continue - # Update entity - item_id = f"{inst}-{sid}-{mikrotik_controller.data[sid][uid][sid_uid]}" - _LOGGER.debug("Updating binary_sensor %s", item_id) + # for sensor, sid_func in zip( + # # Sensor type name + # [ + # "environment", + # ], + # # Entity function + # [ + # MikrotikControllerSensor, + # ], + # ): + # if sensor.startswith("traffic_") and not config_entry.options.get( + # CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC + # ): + # continue + # + # uid_sensor = SENSOR_TYPES[sensor] + # for uid in mikrotik_controller.data[SENSOR_TYPES[sensor].data_path]: + # uid_data = mikrotik_controller.data[SENSOR_TYPES[sensor].data_path] + # if ( + # uid_sensor.data_path == "interface" + # and uid_data[uid]["type"] == "bridge" + # ): + # continue + # + # item_id = f"{inst}-{sensor}-{uid_data[uid][uid_sensor.data_reference]}" + # _LOGGER.debug("Updating binary sensor %s", item_id) + # if item_id in sensors: + # if sensors[item_id].enabled: + # sensors[item_id].async_schedule_update_ha_state() + # continue + # + # sensors[item_id] = sid_func( + # inst=inst, + # uid=uid, + # mikrotik_controller=mikrotik_controller, + # entity_description=uid_sensor, + # ) + # new_sensors.append(sensors[item_id]) + + for sensor in SENSOR_TYPES: + if sensor.startswith("system_"): + uid_sensor = SENSOR_TYPES[sensor] + item_id = f"{inst}-{sensor}" + _LOGGER.debug("Updating binary sensor %s", item_id) if item_id in sensors: if sensors[item_id].enabled: sensors[item_id].async_schedule_update_ha_state() continue - # Create new entity - sid_data = { - "sid": sid, - "sid_uid": sid_uid, - "sid_name": sid_name, - "sid_ref": sid_ref, - "sid_attr": sid_attr, - } - sensors[item_id] = sid_func( - inst, uid, mikrotik_controller, config_entry, sid_data + sensors[item_id] = MikrotikControllerBinarySensor( + inst=inst, + uid="", + mikrotik_controller=mikrotik_controller, + entity_description=uid_sensor, ) new_sensors.append(sensors[item_id]) - for sensor in SENSOR_TYPES: - item_id = f"{inst}-{sensor}" - _LOGGER.debug("Updating binary_sensor %s", item_id) - if item_id in sensors: - if sensors[item_id].enabled: - sensors[item_id].async_schedule_update_ha_state() - continue - - sensors[item_id] = MikrotikControllerBinarySensor( - mikrotik_controller=mikrotik_controller, inst=inst, sid_data=sensor - ) - new_sensors.append(sensors[item_id]) + # + # # Add switches + # for sid, sid_uid, sid_name, sid_ref, sid_attr, sid_func in zip( + # # Data point name + # ["ppp_secret", "interface"], + # # Data point unique id + # ["name", "default-name"], + # # Entry Name + # ["name", "name"], + # # Entry Unique id + # ["name", "port-mac-address"], + # # Attr + # [None, DEVICE_ATTRIBUTES_IFACE], + # # Tracker function + # [ + # MikrotikControllerPPPSecretBinarySensor, + # MikrotikControllerPortBinarySensor, + # ], + # ): + # if ( + # sid_func == MikrotikControllerPortBinarySensor + # and not config_entry.options.get( + # CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER + # ) + # ): + # continue + # for uid in mikrotik_controller.data[sid]: + # if ( + # # Skip if interface is wlan + # sid == "interface" + # and mikrotik_controller.data[sid][uid]["type"] == "wlan" + # ): + # continue + # # Update entity + # item_id = f"{inst}-{sid}-{mikrotik_controller.data[sid][uid][sid_uid]}" + # _LOGGER.debug("Updating binary_sensor %s", item_id) + # if item_id in sensors: + # if sensors[item_id].enabled: + # sensors[item_id].async_schedule_update_ha_state() + # continue + # + # # Create new entity + # sid_data = { + # "sid": sid, + # "sid_uid": sid_uid, + # "sid_name": sid_name, + # "sid_ref": sid_ref, + # "sid_attr": sid_attr, + # } + # sensors[item_id] = sid_func( + # inst, uid, mikrotik_controller, config_entry, sid_data + # ) + # new_sensors.append(sensors[item_id]) + # + # for sensor in SENSOR_TYPES: + # item_id = f"{inst}-{sensor}" + # _LOGGER.debug("Updating binary_sensor %s", item_id) + # if item_id in sensors: + # if sensors[item_id].enabled: + # sensors[item_id].async_schedule_update_ha_state() + # continue + # + # sensors[item_id] = MikrotikControllerBinarySensor( + # mikrotik_controller=mikrotik_controller, inst=inst, sid_data=sensor + # ) + # new_sensors.append(sensors[item_id]) if new_sensors: async_add_entities(new_sensors, True) @@ -221,45 +268,44 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se class MikrotikControllerBinarySensor(BinarySensorEntity): """Define an Mikrotik Controller Binary Sensor.""" - def __init__(self, mikrotik_controller, inst, sid_data): + def __init__( + self, + inst, + uid: "", + mikrotik_controller, + entity_description: MikrotikBinarySensorEntityDescription, + ): """Initialize.""" + self.entity_description = entity_description self._inst = inst - self._sensor = sid_data self._ctrl = mikrotik_controller - if sid_data in SENSOR_TYPES: - self._data = mikrotik_controller.data[SENSOR_TYPES[sid_data][ATTR_PATH]] - self._type = SENSOR_TYPES[sid_data] - self._attr = SENSOR_TYPES[sid_data][ATTR_ATTR] - self._dcls = SENSOR_TYPES[sid_data][ATTR_DEVICE_CLASS] - self._ctgr = SENSOR_TYPES[sid_data][ATTR_CTGR] + self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION} + self._uid = uid + if self._uid: + self._data = mikrotik_controller.data[self.entity_description.data_path][ + self._uid + ] else: - self._type = {} - self._attr = None - self._dcls = None - self._ctgr = None - - self._state = None - self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} + self._data = mikrotik_controller.data[self.entity_description.data_path] @property def name(self) -> str: """Return the name.""" - return f"{self._inst} {self._type[ATTR_LABEL]}" + if self._uid: + if self.entity_description.name: + return f"{self._inst} {self._data[self.entity_description.data_name]} {self.entity_description.name}" - @property - def extra_state_attributes(self) -> Dict[str, Any]: - """Return the state attributes.""" - return self._attrs - - @property - def device_class(self) -> Optional[str]: - """Return the device class.""" - return self._dcls + return f"{self._inst} {self._data[self.entity_description.data_name]}" + else: + return f"{self._inst} {self.entity_description.name}" @property def unique_id(self) -> str: """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self._sensor.lower()}" + if self._uid: + return f"{self._inst.lower()}-{self.entity_description.key}-{self._data[self.entity_description.data_reference].lower()}" + else: + return f"{self._inst.lower()}-{self.entity_description.key}" @property def available(self) -> bool: @@ -267,54 +313,80 @@ class MikrotikControllerBinarySensor(BinarySensorEntity): return self._ctrl.connected() @property - def entity_category(self) -> str: - """Return entity category""" - return self._ctgr + def is_on(self) -> bool: + """Return true if device is on.""" + return self._data[self.entity_description.data_is_on] @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> DeviceInfo: """Return a description for device registry.""" - if self._type[ATTR_GROUP] == "System": - self._type[ATTR_GROUP] = self._ctrl.data["resource"]["board-name"] + dev_connection = DOMAIN + dev_connection_value = self.entity_description.data_reference + dev_group = self.entity_description.ha_group + if self.entity_description.ha_group == "System": + dev_group = self._ctrl.data["resource"]["board-name"] + dev_connection_value = self._ctrl.data["routerboard"]["serial-number"] - info = { - "connections": { - (DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}") - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} {self._type[ATTR_GROUP]}", - "sw_version": self._ctrl.data["resource"]["version"], - "configuration_url": f"http://{self._ctrl.config_entry.data[CONF_HOST]}", - } - if ATTR_GROUP in self._type: - info["identifiers"] = { - ( + if self.entity_description.ha_group.startswith("data__"): + dev_group = self.entity_description.ha_group[6:] + if dev_group in self._data: + dev_group = self._data[dev_group] + dev_connection_value = dev_group + + if self.entity_description.ha_connection: + dev_connection = self.entity_description.ha_connection + + if self.entity_description.ha_connection_value: + dev_connection_value = self.entity_description.ha_connection_value + if dev_connection_value.startswith("data__"): + dev_connection_value = dev_connection_value[6:] + dev_connection_value = self._data[dev_connection_value] + + info = DeviceInfo( + connections={(dev_connection, f"{dev_connection_value}")}, + identifiers={(dev_connection, f"{dev_connection_value}")}, + default_name=f"{self._inst} {dev_group}", + model=f"{self._ctrl.data['resource']['board-name']}", + manufacturer=f"{self._ctrl.data['resource']['platform']}", + sw_version=f"{self._ctrl.data['resource']['version']}", + configuration_url=f"http://{self._ctrl.config_entry.data[CONF_HOST]}", + via_device=(DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}"), + ) + + if "mac-address" in self.entity_description.data_reference: + dev_group = self._data[self.entity_description.data_name] + dev_manufacturer = "" + if dev_connection_value in self._ctrl.data["host"]: + dev_group = self._ctrl.data["host"][dev_connection_value]["host-name"] + dev_manufacturer = self._ctrl.data["host"][dev_connection_value][ + "manufacturer" + ] + + info = DeviceInfo( + connections={(dev_connection, f"{dev_connection_value}")}, + default_name=f"{dev_group}", + manufacturer=f"{dev_manufacturer}", + via_device=( DOMAIN, - "serial-number", f"{self._ctrl.data['routerboard']['serial-number']}", - "sensor", - f"{self._inst} {self._type[ATTR_GROUP]}", - ) - } + ), + ) return info @property - def is_on(self) -> bool: - """Return true if device is on.""" - val = False - if self._attr in self._data: - val = self._data[self._attr] + def extra_state_attributes(self) -> Mapping[str, Any]: + """Return the state attributes.""" + attributes = super().extra_state_attributes + for variable in self.entity_description.data_attributes_list: + if variable in self._data: + attributes[format_attribute(variable)] = self._data[variable] - return val - - async def async_update(self): - """Synchronize state with controller.""" + return attributes async def async_added_to_hass(self): """Run when entity about to be added to hass.""" - _LOGGER.debug("New sensor %s (%s)", self._inst, self._sensor) + _LOGGER.debug("New binary sensor %s (%s)", self._inst, self.unique_id) # --------------------------- diff --git a/custom_components/mikrotik_router/binary_sensor_types.py b/custom_components/mikrotik_router/binary_sensor_types.py index 1fa6ff4..bd10f39 100644 --- a/custom_components/mikrotik_router/binary_sensor_types.py +++ b/custom_components/mikrotik_router/binary_sensor_types.py @@ -27,14 +27,14 @@ class MikrotikBinarySensorEntityDescription(BinarySensorEntityDescription): SENSOR_TYPES = { - "system_temperature": MikrotikBinarySensorEntityDescription( - key="system_temperature", - name="Temperature", + "system_fwupdate": MikrotikBinarySensorEntityDescription( + key="system_fwupdate", + name="Firmware update", icon="mdi:thermometer", - device_class=BinarySensorDeviceClass.CONNECTIVITY, + device_class=BinarySensorDeviceClass.UPDATE, entity_category=EntityCategory.DIAGNOSTIC, ha_group="System", - data_path="health", + data_path="fw-update", data_name="", data_uid="", data_reference="", From 167469b7d125b99b05ad2daa054e9d512bd8e908 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 22:30:44 +0100 Subject: [PATCH 32/36] added icons into types definition file for binary sensors --- custom_components/mikrotik_router/binary_sensor.py | 9 +++++++++ custom_components/mikrotik_router/binary_sensor_types.py | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/custom_components/mikrotik_router/binary_sensor.py b/custom_components/mikrotik_router/binary_sensor.py index 5d40878..ed4c15a 100644 --- a/custom_components/mikrotik_router/binary_sensor.py +++ b/custom_components/mikrotik_router/binary_sensor.py @@ -317,6 +317,15 @@ class MikrotikControllerBinarySensor(BinarySensorEntity): """Return true if device is on.""" return self._data[self.entity_description.data_is_on] + @property + def icon(self) -> str: + """Return the icon.""" + if self.entity_description.icon_enabled: + if self._data[self.entity_description.data_is_on]: + return self.entity_description.icon_enabled + else: + return self.entity_description.icon_disabled + @property def device_info(self) -> DeviceInfo: """Return a description for device registry.""" diff --git a/custom_components/mikrotik_router/binary_sensor_types.py b/custom_components/mikrotik_router/binary_sensor_types.py index bd10f39..c9a3ed3 100644 --- a/custom_components/mikrotik_router/binary_sensor_types.py +++ b/custom_components/mikrotik_router/binary_sensor_types.py @@ -15,6 +15,8 @@ from .const import DOMAIN class MikrotikBinarySensorEntityDescription(BinarySensorEntityDescription): """Class describing mikrotik entities.""" + icon_enabled: str = "" + icon_disabled: str = "" ha_group: str = "" ha_connection: str = "" ha_connection_value: str = "" @@ -30,7 +32,8 @@ SENSOR_TYPES = { "system_fwupdate": MikrotikBinarySensorEntityDescription( key="system_fwupdate", name="Firmware update", - icon="mdi:thermometer", + icon_enabled="", + icon_disabled="", device_class=BinarySensorDeviceClass.UPDATE, entity_category=EntityCategory.DIAGNOSTIC, ha_group="System", From bd1cc7925ca0c6a8c73846a42650372204798da0 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 22:46:18 +0100 Subject: [PATCH 33/36] redo ppp and interface binary sensors --- .../mikrotik_router/binary_sensor.py | 202 ++++-------------- .../mikrotik_router/binary_sensor_types.py | 88 ++++++++ custom_components/mikrotik_router/sensor.py | 4 +- 3 files changed, 131 insertions(+), 163 deletions(-) diff --git a/custom_components/mikrotik_router/binary_sensor.py b/custom_components/mikrotik_router/binary_sensor.py index ed4c15a..a967107 100644 --- a/custom_components/mikrotik_router/binary_sensor.py +++ b/custom_components/mikrotik_router/binary_sensor.py @@ -95,15 +95,6 @@ DEVICE_ATTRIBUTES_IFACE_SFP = [ "eeprom-checksum", ] -DEVICE_ATTRIBUTES_PPP_SECRET = [ - "connected", - "service", - "profile", - "comment", - "caller-id", - "encoding", -] - # --------------------------- # async_setup_entry @@ -137,44 +128,38 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se """Update sensor state from the controller.""" new_sensors = [] - # for sensor, sid_func in zip( - # # Sensor type name - # [ - # "environment", - # ], - # # Entity function - # [ - # MikrotikControllerSensor, - # ], - # ): - # if sensor.startswith("traffic_") and not config_entry.options.get( - # CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC - # ): - # continue - # - # uid_sensor = SENSOR_TYPES[sensor] - # for uid in mikrotik_controller.data[SENSOR_TYPES[sensor].data_path]: - # uid_data = mikrotik_controller.data[SENSOR_TYPES[sensor].data_path] - # if ( - # uid_sensor.data_path == "interface" - # and uid_data[uid]["type"] == "bridge" - # ): - # continue - # - # item_id = f"{inst}-{sensor}-{uid_data[uid][uid_sensor.data_reference]}" - # _LOGGER.debug("Updating binary sensor %s", item_id) - # if item_id in sensors: - # if sensors[item_id].enabled: - # sensors[item_id].async_schedule_update_ha_state() - # continue - # - # sensors[item_id] = sid_func( - # inst=inst, - # uid=uid, - # mikrotik_controller=mikrotik_controller, - # entity_description=uid_sensor, - # ) - # new_sensors.append(sensors[item_id]) + for sensor, sid_func in zip( + # Sensor type name + ["ppp_tracker", "interface"], + # Entity function + [MikrotikControllerPPPSecretBinarySensor, MikrotikControllerPortBinarySensor], + ): + if sensor == "interface" and not config_entry.options.get( + CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER + ): + continue + + uid_sensor = SENSOR_TYPES[sensor] + for uid in mikrotik_controller.data[uid_sensor.data_path]: + uid_data = mikrotik_controller.data[uid_sensor.data_path] + if uid_sensor.data_path == "interface" and uid_data[uid]["type"] == "wlan": + continue + + item_id = f"{inst}-{sensor}-{uid_data[uid][uid_sensor.data_reference]}" + _LOGGER.debug("Updating binary sensor %s", item_id) + if item_id in sensors: + if sensors[item_id].enabled: + sensors[item_id].async_schedule_update_ha_state() + continue + + sensors[item_id] = sid_func( + inst=inst, + uid=uid, + mikrotik_controller=mikrotik_controller, + entity_description=uid_sensor, + config_entry=config_entry, + ) + new_sensors.append(sensors[item_id]) for sensor in SENSOR_TYPES: if sensor.startswith("system_"): @@ -191,6 +176,7 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se uid="", mikrotik_controller=mikrotik_controller, entity_description=uid_sensor, + config_entry=config_entry, ) new_sensors.append(sensors[item_id]) @@ -274,9 +260,11 @@ class MikrotikControllerBinarySensor(BinarySensorEntity): uid: "", mikrotik_controller, entity_description: MikrotikBinarySensorEntityDescription, + config_entry, ): """Initialize.""" self.entity_description = entity_description + self._config_entry = config_entry self._inst = inst self._ctrl = mikrotik_controller self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -404,35 +392,18 @@ class MikrotikControllerBinarySensor(BinarySensorEntity): class MikrotikControllerPPPSecretBinarySensor(MikrotikControllerBinarySensor): """Representation of a network device.""" - def __init__(self, inst, uid, mikrotik_controller, config_entry, sid_data): - """Initialize.""" - super().__init__(mikrotik_controller, inst, uid) - self._sid_data = sid_data - self._data = mikrotik_controller.data[self._sid_data["sid"]][uid] - self._config_entry = config_entry - @property def option_sensor_ppp(self) -> bool: """Config entry option.""" return self._config_entry.options.get(CONF_SENSOR_PPP, DEFAULT_SENSOR_PPP) - @property - def name(self) -> str: - """Return the name.""" - return f"{self._inst} PPP {self._data['name']}" - @property def is_on(self) -> bool: """Return true if device is on.""" if not self.option_sensor_ppp: return False - return self._data["connected"] - - @property - def device_class(self) -> Optional[str]: - """Return the device class.""" - return BinarySensorDeviceClass.CONNECTIVITY + return self._data[self.entity_description.data_is_on] @property def available(self) -> bool: @@ -442,48 +413,6 @@ class MikrotikControllerPPPSecretBinarySensor(MikrotikControllerBinarySensor): return self._ctrl.connected() - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self._sid_data['sid']}_tracker-{self._data[self._sid_data['sid_ref']]}" - - @property - def icon(self) -> str: - """Return the icon.""" - if self._data["connected"]: - return "mdi:account-network-outline" - else: - return "mdi:account-off-outline" - - @property - def extra_state_attributes(self) -> Dict[str, Any]: - """Return the state attributes.""" - attributes = self._attrs - for variable in DEVICE_ATTRIBUTES_PPP_SECRET: - if variable in self._data: - attributes[format_attribute(variable)] = self._data[variable] - - return attributes - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "identifiers": { - ( - DOMAIN, - "serial-number", - f"{self._ctrl.data['routerboard']['serial-number']}", - "switch", - "PPP", - ) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} PPP", - } - return info - # --------------------------- # MikrotikControllerPortBinarySensor @@ -491,13 +420,6 @@ class MikrotikControllerPPPSecretBinarySensor(MikrotikControllerBinarySensor): class MikrotikControllerPortBinarySensor(MikrotikControllerBinarySensor): """Representation of a network port.""" - def __init__(self, inst, uid, mikrotik_controller, config_entry, sid_data): - """Initialize.""" - super().__init__(mikrotik_controller, inst, uid) - self._sid_data = sid_data - self._data = mikrotik_controller.data[self._sid_data["sid"]][uid] - self._config_entry = config_entry - @property def option_sensor_port_tracker(self) -> bool: """Config entry option to not track ARP.""" @@ -505,29 +427,6 @@ class MikrotikControllerPortBinarySensor(MikrotikControllerBinarySensor): CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER ) - @property - def name(self) -> str: - """Return the name.""" - return f"{self._inst} {self._data[self._sid_data['sid_name']]}" - - @property - def unique_id(self) -> str: - """Return a unique id for this entity.""" - return ( - f"{self._inst.lower()}-{self._sid_data['sid']}-" - f"{self._data['port-mac-address']}_{self._data['default-name']}" - ) - - @property - def is_on(self) -> bool: - """Return true if device is on.""" - return self._data["running"] - - @property - def device_class(self) -> Optional[str]: - """Return the device class.""" - return BinarySensorDeviceClass.CONNECTIVITY - @property def available(self) -> bool: """Return if controller is available.""" @@ -539,10 +438,10 @@ class MikrotikControllerPortBinarySensor(MikrotikControllerBinarySensor): @property def icon(self) -> str: """Return the icon.""" - if self._data["running"]: - icon = "mdi:lan-connect" + if self._data[self.entity_description.data_is_on]: + icon = self.entity_description.icon_enabled else: - icon = "mdi:lan-pending" + icon = self.entity_description.icon_disabled if not self._data["enabled"]: icon = "mdi:lan-disconnect" @@ -550,9 +449,9 @@ class MikrotikControllerPortBinarySensor(MikrotikControllerBinarySensor): return icon @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Mapping[str, Any]: """Return the state attributes.""" - attributes = self._attrs + attributes = super().extra_state_attributes if self._data["type"] == "ether": for variable in DEVICE_ATTRIBUTES_IFACE_ETHER: @@ -564,23 +463,4 @@ class MikrotikControllerPortBinarySensor(MikrotikControllerBinarySensor): if variable in self._data: attributes[format_attribute(variable)] = self._data[variable] - else: - for variable in self._sid_data["sid_attr"]: - if variable in self._data: - attributes[format_attribute(variable)] = self._data[variable] - return attributes - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "connections": { - (CONNECTION_NETWORK_MAC, self._data[self._sid_data["sid_ref"]]) - }, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": f"{self._inst} {self._data[self._sid_data['sid_name']]}", - } - - return info diff --git a/custom_components/mikrotik_router/binary_sensor_types.py b/custom_components/mikrotik_router/binary_sensor_types.py index c9a3ed3..f717218 100644 --- a/custom_components/mikrotik_router/binary_sensor_types.py +++ b/custom_components/mikrotik_router/binary_sensor_types.py @@ -10,6 +10,62 @@ from homeassistant.components.binary_sensor import ( from .const import DOMAIN +DEVICE_ATTRIBUTES_PPP_SECRET = [ + "connected", + "service", + "profile", + "comment", + "caller-id", + "encoding", +] + +DEVICE_ATTRIBUTES_IFACE = [ + "running", + "enabled", + "comment", + "client-ip-address", + "client-mac-address", + "port-mac-address", + "last-link-down-time", + "last-link-up-time", + "link-downs", + "actual-mtu", + "type", + "name", +] + +DEVICE_ATTRIBUTES_IFACE_ETHER = [ + "status", + "auto-negotiation", + "rate", + "full-duplex", + "default-name", + "poe-out", +] + +DEVICE_ATTRIBUTES_IFACE_SFP = [ + "status", + "auto-negotiation", + "advertising", + "link-partner-advertising", + "sfp-temperature", + "sfp-supply-voltage", + "sfp-module-present", + "sfp-tx-bias-current", + "sfp-tx-power", + "sfp-rx-power", + "sfp-rx-loss", + "sfp-tx-fault", + "sfp-type", + "sfp-connector-type", + "sfp-vendor-name", + "sfp-vendor-part-number", + "sfp-vendor-revision", + "sfp-vendor-serial", + "sfp-manufacturing-date", + "eeprom-checksum", +] + @dataclass class MikrotikBinarySensorEntityDescription(BinarySensorEntityDescription): @@ -42,4 +98,36 @@ SENSOR_TYPES = { data_uid="", data_reference="", ), + "ppp_tracker": MikrotikBinarySensorEntityDescription( + key="ppp_tracker", + name="PPP", + icon_enabled="mdi:account-network-outline", + icon_disabled="mdi:account-off-outline", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + ha_group="PPP", + ha_connection=DOMAIN, + ha_connection_value="PPP", + data_path="ppp_secret", + data_is_on="connected", + data_name="name", + data_uid="name", + data_reference="name", + data_attributes_list=DEVICE_ATTRIBUTES_PPP_SECRET, + ), + "interface": MikrotikBinarySensorEntityDescription( + key="interface", + name="", + icon_enabled="mdi:lan-connect", + icon_disabled="mdi:lan-pending", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + ha_group="data__default-name", + ha_connection=CONNECTION_NETWORK_MAC, + ha_connection_value="data__port-mac-address", + data_path="interface", + data_is_on="running", + data_name="name", + data_uid="default-name", + data_reference="default-name", + data_attributes_list=DEVICE_ATTRIBUTES_IFACE, + ), } diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index c42efcb..91b54ee 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -89,8 +89,8 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se continue uid_sensor = SENSOR_TYPES[sensor] - for uid in mikrotik_controller.data[SENSOR_TYPES[sensor].data_path]: - uid_data = mikrotik_controller.data[SENSOR_TYPES[sensor].data_path] + for uid in mikrotik_controller.data[uid_sensor.data_path]: + uid_data = mikrotik_controller.data[uid_sensor.data_path] if ( uid_sensor.data_path == "interface" and uid_data[uid]["type"] == "bridge" From aa101df3633446010927601a424038eaf3db1ea4 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 22:53:59 +0100 Subject: [PATCH 34/36] cleaned up obsolete code in binary sensors --- .../mikrotik_router/binary_sensor.py | 69 ++----------------- 1 file changed, 4 insertions(+), 65 deletions(-) diff --git a/custom_components/mikrotik_router/binary_sensor.py b/custom_components/mikrotik_router/binary_sensor.py index a967107..d092912 100644 --- a/custom_components/mikrotik_router/binary_sensor.py +++ b/custom_components/mikrotik_router/binary_sensor.py @@ -1,22 +1,19 @@ """Support for the Mikrotik Router binary sensor service.""" import logging -from typing import Any, Dict, Optional +from typing import Any from collections.abc import Mapping -from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity import DeviceInfo from homeassistant.components.binary_sensor import ( BinarySensorEntity, - BinarySensorDeviceClass, ) from homeassistant.const import ( CONF_NAME, CONF_HOST, - ATTR_DEVICE_CLASS, ATTR_ATTRIBUTION, ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from .helper import format_attribute from .const import ( DOMAIN, @@ -31,71 +28,13 @@ from .const import ( from .binary_sensor_types import ( MikrotikBinarySensorEntityDescription, SENSOR_TYPES, + DEVICE_ATTRIBUTES_IFACE_ETHER, + DEVICE_ATTRIBUTES_IFACE_SFP, ) _LOGGER = logging.getLogger(__name__) -DEVICE_ATTRIBUTES_IFACE = [ - "running", - "enabled", - "comment", - "client-ip-address", - "client-mac-address", - "port-mac-address", - "last-link-down-time", - "last-link-up-time", - "link-downs", - "actual-mtu", - "type", - "name", -] - -DEVICE_ATTRIBUTES_IFACE_ETHER = [ - "running", - "enabled", - "comment", - "client-ip-address", - "client-mac-address", - "port-mac-address", - "last-link-down-time", - "last-link-up-time", - "link-downs", - "actual-mtu", - "type", - "name", - "status", - "auto-negotiation", - "rate", - "full-duplex", - "default-name", - "poe-out", -] - -DEVICE_ATTRIBUTES_IFACE_SFP = [ - "status", - "auto-negotiation", - "advertising", - "link-partner-advertising", - "sfp-temperature", - "sfp-supply-voltage", - "sfp-module-present", - "sfp-tx-bias-current", - "sfp-tx-power", - "sfp-rx-power", - "sfp-rx-loss", - "sfp-tx-fault", - "sfp-type", - "sfp-connector-type", - "sfp-vendor-name", - "sfp-vendor-part-number", - "sfp-vendor-revision", - "sfp-vendor-serial", - "sfp-manufacturing-date", - "eeprom-checksum", -] - - # --------------------------- # async_setup_entry # --------------------------- From 731cf6cde4d3447a2c7f6400aaffb862097b3639 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Fri, 4 Feb 2022 22:55:20 +0100 Subject: [PATCH 35/36] added state attributes to interface traffic sensors --- custom_components/mikrotik_router/sensor.py | 30 +++++++++++- .../mikrotik_router/sensor_types.py | 48 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 91b54ee..2f885ee 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -23,6 +23,8 @@ from .const import ( from .sensor_types import ( MikrotikSensorEntityDescription, SENSOR_TYPES, + DEVICE_ATTRIBUTES_IFACE_ETHER, + DEVICE_ATTRIBUTES_IFACE_SFP, ) _LOGGER = logging.getLogger(__name__) @@ -75,8 +77,8 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, se # Entity function [ MikrotikControllerSensor, - MikrotikControllerSensor, - MikrotikControllerSensor, + MikrotikInterfaceTrafficSensor, + MikrotikInterfaceTrafficSensor, MikrotikClientTrafficSensor, MikrotikClientTrafficSensor, MikrotikClientTrafficSensor, @@ -293,6 +295,30 @@ class MikrotikControllerSensor(SensorEntity): _LOGGER.debug("New sensor %s (%s)", self._inst, self.unique_id) +# --------------------------- +# MikrotikInterfaceTrafficSensor +# --------------------------- +class MikrotikInterfaceTrafficSensor(MikrotikControllerSensor): + """Define an Mikrotik MikrotikInterfaceTrafficSensor sensor.""" + + @property + def extra_state_attributes(self) -> Mapping[str, Any]: + """Return the state attributes.""" + attributes = super().extra_state_attributes + + if self._data["type"] == "ether": + for variable in DEVICE_ATTRIBUTES_IFACE_ETHER: + if variable in self._data: + attributes[format_attribute(variable)] = self._data[variable] + + if "sfp-shutdown-temperature" in self._data: + for variable in DEVICE_ATTRIBUTES_IFACE_SFP: + if variable in self._data: + attributes[format_attribute(variable)] = self._data[variable] + + return attributes + + # --------------------------- # MikrotikClientTrafficSensor # --------------------------- diff --git a/custom_components/mikrotik_router/sensor_types.py b/custom_components/mikrotik_router/sensor_types.py index 3ff6d11..83c86cb 100644 --- a/custom_components/mikrotik_router/sensor_types.py +++ b/custom_components/mikrotik_router/sensor_types.py @@ -16,6 +16,52 @@ from homeassistant.const import ( ) from .const import DOMAIN +DEVICE_ATTRIBUTES_IFACE = [ + "running", + "enabled", + "comment", + "client-ip-address", + "client-mac-address", + "port-mac-address", + "last-link-down-time", + "last-link-up-time", + "link-downs", + "actual-mtu", + "type", + "name", +] + +DEVICE_ATTRIBUTES_IFACE_ETHER = [ + "status", + "auto-negotiation", + "rate", + "full-duplex", + "default-name", + "poe-out", +] + +DEVICE_ATTRIBUTES_IFACE_SFP = [ + "status", + "auto-negotiation", + "advertising", + "link-partner-advertising", + "sfp-temperature", + "sfp-supply-voltage", + "sfp-module-present", + "sfp-tx-bias-current", + "sfp-tx-power", + "sfp-rx-power", + "sfp-rx-loss", + "sfp-tx-fault", + "sfp-type", + "sfp-connector-type", + "sfp-vendor-name", + "sfp-vendor-part-number", + "sfp-vendor-revision", + "sfp-vendor-serial", + "sfp-manufacturing-date", + "eeprom-checksum", +] DEVICE_ATTRIBUTES_CLIENT_TRAFFIC = ["address", "mac-address", "host-name"] @@ -217,6 +263,7 @@ SENSOR_TYPES = { data_name="name", data_uid="", data_reference="default-name", + data_attributes_list=DEVICE_ATTRIBUTES_IFACE, ), "traffic_rx": MikrotikSensorEntityDescription( key="traffic_rx", @@ -234,6 +281,7 @@ SENSOR_TYPES = { data_name="name", data_uid="", data_reference="default-name", + data_attributes_list=DEVICE_ATTRIBUTES_IFACE, ), "client_traffic_lan_tx": MikrotikSensorEntityDescription( key="client_traffic_lan_tx", From 81a61e18070a33d53ef3c3737c8ff3ed4db13188 Mon Sep 17 00:00:00 2001 From: Tomaae Date: Sat, 5 Feb 2022 00:56:30 +0100 Subject: [PATCH 36/36] redo device trackers --- .../mikrotik_router/binary_sensor.py | 1 - .../mikrotik_router/device_tracker.py | 333 +++++++++--------- .../mikrotik_router/device_tracker_types.py | 51 +++ 3 files changed, 223 insertions(+), 162 deletions(-) create mode 100644 custom_components/mikrotik_router/device_tracker_types.py diff --git a/custom_components/mikrotik_router/binary_sensor.py b/custom_components/mikrotik_router/binary_sensor.py index d092912..6b8ad19 100644 --- a/custom_components/mikrotik_router/binary_sensor.py +++ b/custom_components/mikrotik_router/binary_sensor.py @@ -24,7 +24,6 @@ from .const import ( CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER, ) - from .binary_sensor_types import ( MikrotikBinarySensorEntityDescription, SENSOR_TYPES, diff --git a/custom_components/mikrotik_router/device_tracker.py b/custom_components/mikrotik_router/device_tracker.py index f41cd4d..e7af87c 100644 --- a/custom_components/mikrotik_router/device_tracker.py +++ b/custom_components/mikrotik_router/device_tracker.py @@ -2,18 +2,20 @@ import logging from typing import Any, Dict +from collections.abc import Mapping from datetime import timedelta - from homeassistant.components.device_tracker.config_entry import ScannerEntity from homeassistant.components.device_tracker.const import SOURCE_TYPE_ROUTER from homeassistant.const import ( CONF_NAME, + CONF_HOST, ATTR_ATTRIBUTION, STATE_NOT_HOME, ) 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 DeviceInfo from homeassistant.util.dt import get_age, utcnow from .helper import format_attribute, format_value from .const import ( @@ -25,18 +27,13 @@ from .const import ( CONF_TRACK_HOSTS_TIMEOUT, DEFAULT_TRACK_HOST_TIMEOUT, ) +from .device_tracker_types import ( + MikrotikDeviceTrackerEntityDescription, + SENSOR_TYPES, +) _LOGGER = logging.getLogger(__name__) -DEVICE_ATTRIBUTES_HOST = [ - "host-name", - "address", - "mac-address", - "interface", - "source", - "last-seen", -] - # --------------------------- # async_setup_entry @@ -45,13 +42,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up device tracker for Mikrotik Router component.""" inst = config_entry.data[CONF_NAME] mikrotik_controller = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] - tracked = {} + trackers = {} @callback def update_controller(): """Update the values of the controller.""" update_items( - inst, config_entry, mikrotik_controller, async_add_entities, tracked + inst, config_entry, mikrotik_controller, async_add_entities, trackers ) mikrotik_controller.listeners.append( @@ -67,60 +64,45 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # update_items # --------------------------- @callback -def update_items(inst, config_entry, mikrotik_controller, async_add_entities, tracked): - """Update tracked device state from the controller.""" - new_tracked = [] +def update_items(inst, config_entry, mikrotik_controller, async_add_entities, trackers): + """Update trackers device state from the controller.""" + new_trackers = [] - # Add switches - for sid, sid_uid, sid_name, sid_ref, sid_func in zip( - # Data point name + for sensor, sid_func in zip( + # Sensor type name ["host"], - # Data point unique id - ["mac-address"], - # Entry Name - ["host-name"], - # Entry Unique id - ["mac-address"], - # Tracker function - [ - MikrotikControllerHostDeviceTracker, - ], + # Entity function + [MikrotikControllerHostDeviceTracker], ): - for uid in mikrotik_controller.data[sid]: - if ( - # Skip if host tracking is disabled - sid == "host" - and not config_entry.options.get(CONF_TRACK_HOSTS, DEFAULT_TRACK_HOSTS) - ): + uid_sensor = SENSOR_TYPES[sensor] + if ( + # Skip if host tracking is disabled + sensor == "host" + and not config_entry.options.get(CONF_TRACK_HOSTS, DEFAULT_TRACK_HOSTS) + ): + continue + + for uid in mikrotik_controller.data[uid_sensor.data_path]: + uid_data = mikrotik_controller.data[uid_sensor.data_path] + item_id = f"{inst}-{sensor}-{uid_data[uid][uid_sensor.data_reference]}" + _LOGGER.debug("Updating device tracker %s", item_id) + if item_id in trackers: + if trackers[item_id].enabled: + trackers[item_id].async_schedule_update_ha_state() continue - # Update entity - item_id = f"{inst}-{sid}-{mikrotik_controller.data[sid][uid][sid_uid]}" - _LOGGER.debug("Updating device_tracker %s", item_id) - _LOGGER.debug( - "Updating device_tracker data: %s ", - mikrotik_controller.data[sid][uid], + trackers[item_id] = sid_func( + inst=inst, + uid=uid, + mikrotik_controller=mikrotik_controller, + entity_description=uid_sensor, + config_entry=config_entry, ) - if item_id in tracked: - if tracked[item_id].enabled: - tracked[item_id].async_schedule_update_ha_state() - continue - - # Create new entity - sid_data = { - "sid": sid, - "sid_uid": sid_uid, - "sid_name": sid_name, - "sid_ref": sid_ref, - } - tracked[item_id] = sid_func( - inst, uid, mikrotik_controller, config_entry, sid_data - ) - new_tracked.append(tracked[item_id]) + new_trackers.append(trackers[item_id]) # Register new entities - if new_tracked: - async_add_entities(new_tracked) + if new_trackers: + async_add_entities(new_trackers) # --------------------------- @@ -129,58 +111,58 @@ def update_items(inst, config_entry, mikrotik_controller, async_add_entities, tr class MikrotikControllerDeviceTracker(ScannerEntity): """Representation of a device tracker.""" - def __init__(self, inst, uid, mikrotik_controller, config_entry, sid_data): - """Set up a device tracker.""" - _LOGGER.debug("Initializing device tracker sensor: %s", uid) - self._sid_data = sid_data + def __init__( + self, + inst, + uid: "", + mikrotik_controller, + entity_description: MikrotikDeviceTrackerEntityDescription, + config_entry, + ): + """Initialize.""" + self.entity_description = entity_description + self._config_entry = config_entry self._inst = inst self._ctrl = mikrotik_controller - self._data = mikrotik_controller.data[self._sid_data["sid"]][uid] - self._config_entry = config_entry - - self._attrs = { - ATTR_ATTRIBUTION: ATTRIBUTION, - } - - @property - def entity_registry_enabled_default(self): - """Return if the entity should be enabled when first added to the entity registry.""" - return True - - async def async_added_to_hass(self): - """Run when entity about to be added to hass.""" - _LOGGER.debug( - "New device tracker %s (%s %s)", - self._inst, - self._sid_data["sid"], - self._data[self._sid_data["sid_uid"]], - ) - - async def async_update(self): - """Synchronize state with controller.""" - - @property - def source_type(self) -> str: - """Return the source type of the port.""" - return SOURCE_TYPE_ROUTER + self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION} + self._data = mikrotik_controller.data[self.entity_description.data_path][uid] @property def name(self) -> str: """Return the name.""" - if self._sid_data["sid"] == "interface": - return f"{self._inst} {self._data[self._sid_data['sid_name']]}" + if self.entity_description.name: + return f"{self._inst} {self._data[self.entity_description.data_name]} {self.entity_description.name}" - return f"{self._data[self._sid_data['sid_name']]}" + return f"{self._inst} {self._data[self.entity_description.data_name]}" @property def unique_id(self) -> str: """Return a unique id for this entity.""" - return f"{self._inst.lower()}-{self._sid_data['sid']}-{self._data[self._sid_data['sid_ref']]}" + return f"{self._inst.lower()}-{self.entity_description.key}-{self._data[self.entity_description.data_reference].lower()}" @property - def available(self) -> bool: - """Return if controller is available.""" - return self._ctrl.connected() + def ip_address(self) -> str: + """Return the primary ip address of the device.""" + if "address" in self._data: + return self._data["address"] + + return None + + @property + def mac_address(self) -> str: + """Return the mac address of the device.""" + if self.entity_description.data_reference in self._data: + return self._data[self.entity_description.data_reference] + + return None + + @property + def hostname(self) -> str: + """Return hostname of the device.""" + if self.entity_description.data_name in self._data: + return self._data[self.entity_description.data_name] + + return None @property def device_info(self) -> Dict[str, Any]: @@ -198,14 +180,81 @@ class MikrotikControllerDeviceTracker(ScannerEntity): return info @property - def extra_state_attributes(self) -> Dict[str, Any]: + def is_connected(self) -> bool: + """Return true if device is connected.""" + return self._data[self.entity_description.data_is_on] + + @property + def device_info(self) -> DeviceInfo: + """Return a description for device registry.""" + dev_connection = DOMAIN + dev_connection_value = self.entity_description.data_reference + dev_group = self.entity_description.ha_group + if self.entity_description.ha_group.startswith("data__"): + dev_group = self.entity_description.ha_group[6:] + if dev_group in self._data: + dev_group = self._data[dev_group] + dev_connection_value = dev_group + + if self.entity_description.ha_connection: + dev_connection = self.entity_description.ha_connection + + if self.entity_description.ha_connection_value: + dev_connection_value = self.entity_description.ha_connection_value + if dev_connection_value.startswith("data__"): + dev_connection_value = dev_connection_value[6:] + dev_connection_value = self._data[dev_connection_value] + + info = DeviceInfo( + connections={(dev_connection, f"{dev_connection_value}")}, + identifiers={(dev_connection, f"{dev_connection_value}")}, + default_name=f"{self._inst} {dev_group}", + model=f"{self._ctrl.data['resource']['board-name']}", + manufacturer=f"{self._ctrl.data['resource']['platform']}", + sw_version=f"{self._ctrl.data['resource']['version']}", + configuration_url=f"http://{self._ctrl.config_entry.data[CONF_HOST]}", + via_device=(DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}"), + ) + + if "mac-address" in self.entity_description.data_reference: + dev_group = self._data[self.entity_description.data_name] + dev_manufacturer = "" + if dev_connection_value in self._ctrl.data["host"]: + dev_group = self._ctrl.data["host"][dev_connection_value]["host-name"] + dev_manufacturer = self._ctrl.data["host"][dev_connection_value][ + "manufacturer" + ] + + info = DeviceInfo( + connections={(dev_connection, f"{dev_connection_value}")}, + default_name=f"{dev_group}", + manufacturer=f"{dev_manufacturer}", + via_device=( + DOMAIN, + f"{self._ctrl.data['routerboard']['serial-number']}", + ), + ) + + return info + + @property + def extra_state_attributes(self) -> Mapping[str, Any]: """Return the state attributes.""" - attributes = self._attrs + attributes = super().extra_state_attributes + for variable in self.entity_description.data_attributes_list: + if variable in self._data: + attributes[format_attribute(variable)] = self._data[variable] + return attributes @property - def is_connected(self) -> bool: - return False + def source_type(self) -> str: + """Return the source type of the port.""" + return SOURCE_TYPE_ROUTER + + async def async_added_to_hass(self): + """Run when entity about to be added to hass.""" + _LOGGER.debug("New device tracker %s (%s)", self._inst, self.unique_id) # --------------------------- @@ -214,10 +263,6 @@ class MikrotikControllerDeviceTracker(ScannerEntity): class MikrotikControllerHostDeviceTracker(MikrotikControllerDeviceTracker): """Representation of a network device.""" - def __init__(self, inst, uid, mikrotik_controller, config_entry, sid_data): - """Set up tracked port.""" - super().__init__(inst, uid, mikrotik_controller, config_entry, sid_data) - @property def option_track_network_hosts(self): """Config entry option to not track ARP.""" @@ -231,6 +276,16 @@ class MikrotikControllerHostDeviceTracker(MikrotikControllerDeviceTracker): ) return timedelta(seconds=track_network_hosts_timeout) + @property + def name(self) -> str: + """Return the name.""" + return f"{self._data[self.entity_description.data_name]}" + + @property + def unique_id(self) -> str: + """Return a unique id for this entity.""" + return f"{self._data[self.entity_description.data_reference].lower()}" + @property def is_connected(self) -> bool: """Return true if the host is connected to the network.""" @@ -238,7 +293,7 @@ class MikrotikControllerHostDeviceTracker(MikrotikControllerDeviceTracker): return False if self._data["source"] in ["capsman", "wireless"]: - return self._data["available"] + return self._data[self.entity_description.data_is_on] if ( self._data["last-seen"] @@ -246,32 +301,25 @@ class MikrotikControllerHostDeviceTracker(MikrotikControllerDeviceTracker): < self.option_track_network_hosts_timeout ): return True + return False - @property - def available(self) -> bool: - """Return if controller is available.""" - if not self.option_track_network_hosts: - return False - - return self._ctrl.connected() - @property def icon(self) -> str: """Return the icon.""" if self._data["source"] in ["capsman", "wireless"]: - if self._data["available"]: - return "mdi:lan-connect" + if self._data[self.entity_description.data_is_on]: + return self.entity_description.icon_enabled else: - return "mdi:lan-disconnect" + return self.entity_description.icon_disabled if ( self._data["last-seen"] and (utcnow() - self._data["last-seen"]) < self.option_track_network_hosts_timeout ): - return "mdi:lan-connect" - return "mdi:lan-disconnect" + return self.entity_description.icon_enabled + return self.entity_description.icon_disabled @property def state(self) -> str: @@ -283,47 +331,10 @@ class MikrotikControllerHostDeviceTracker(MikrotikControllerDeviceTracker): @property def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes.""" - attributes = self._attrs - for variable in DEVICE_ATTRIBUTES_HOST: - if variable not in self._data: - continue - - if variable == "last-seen": - if self._data[variable]: - attributes[format_attribute(variable)] = get_age( - self._data[variable] - ) - else: - attributes[format_attribute(variable)] = "unknown" - else: - if self._data[variable] in [ - "dhcp", - "dns", - "capsman", - "wireless", - "restored", - ]: - attributes[format_attribute(variable)] = format_value( - self._data[variable] - ) - else: - attributes[format_attribute(variable)] = self._data[variable] + attributes = super().extra_state_attributes + if self._data["last-seen"]: + attributes[format_attribute("last-seen")] = get_age(self._data["last-seen"]) + else: + attributes[format_attribute("last-seen")] = "unknown" return attributes - - @property - def device_info(self) -> Dict[str, Any]: - """Return a description for device registry.""" - info = { - "connections": { - (CONNECTION_NETWORK_MAC, self._data[self._sid_data["sid_ref"]]) - }, - "default_name": self._data[self._sid_data["sid_name"]], - } - if self._data["manufacturer"] != "": - info["manufacturer"] = self._data["manufacturer"] - - if self._sid_data["sid"] == "interface": - info["name"] = f"{self._inst} {self._data[self._sid_data['sid_name']]}" - - return info diff --git a/custom_components/mikrotik_router/device_tracker_types.py b/custom_components/mikrotik_router/device_tracker_types.py new file mode 100644 index 0000000..52929fa --- /dev/null +++ b/custom_components/mikrotik_router/device_tracker_types.py @@ -0,0 +1,51 @@ +"""Definitions for Mikrotik Router device tracker entities.""" +from dataclasses import dataclass, field +from typing import List +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.components.switch import ( + SwitchEntityDescription, +) + +DEVICE_ATTRIBUTES_HOST = [ + "interface", + "source", + "last-seen", +] + + +@dataclass +class MikrotikDeviceTrackerEntityDescription(SwitchEntityDescription): + """Class describing mikrotik entities.""" + + key: str = "" + name: str = "" + device_class = None + icon_enabled: str = "" + icon_disabled: str = "" + ha_group: str = "" + ha_connection: str = "" + ha_connection_value: str = "" + data_path: str = "" + data_is_on: str = "available" + data_name: str = "" + data_uid: str = "" + data_reference: str = "" + data_attributes_list: List = field(default_factory=lambda: []) + + +SENSOR_TYPES = { + "host": MikrotikDeviceTrackerEntityDescription( + key="host", + name="", + icon_enabled="mdi:lan-connect", + icon_disabled="mdi:lan-disconnect", + ha_group="", + ha_connection=CONNECTION_NETWORK_MAC, + ha_connection_value="data__mac-address", + data_path="host", + data_name="host-name", + data_uid="mac-address", + data_reference="mac-address", + data_attributes_list=DEVICE_ATTRIBUTES_HOST, + ), +}