diff --git a/custom_components/mikrotik_router/.translations/ru.json b/custom_components/mikrotik_router/.translations/ru.json index 3b0fc8d..2cda4b9 100644 --- a/custom_components/mikrotik_router/.translations/ru.json +++ b/custom_components/mikrotik_router/.translations/ru.json @@ -1,9 +1,9 @@ { "config": { - "title": "Mikrotik Роутер", + "title": "Роутер Mikrotik", "step": { "user": { - "title": "Mikrotik Роутер", + "title": "Роутер Mikrotik", "description": "Настройка интеграции роутера Mikrotik.", "data": { "name": "Название интеграции", diff --git a/custom_components/mikrotik_router/__init__.py b/custom_components/mikrotik_router/__init__.py index c56f2ec..61d051b 100644 --- a/custom_components/mikrotik_router/__init__.py +++ b/custom_components/mikrotik_router/__init__.py @@ -1,103 +1,108 @@ -"""Mikrotik Router integration.""" - -import logging -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.const import ( - CONF_NAME, - CONF_HOST, - CONF_PORT, - CONF_UNIT_OF_MEASUREMENT, - CONF_USERNAME, - CONF_PASSWORD, - CONF_SSL, -) - -from .mikrotik_controller import MikrotikControllerData - -from .const import ( - DOMAIN, - DATA_CLIENT, - DEFAULT_TRAFFIC_TYPE, -) - -_LOGGER = logging.getLogger(__name__) - - -# --------------------------- -# async_setup -# --------------------------- -async def async_setup(hass, _config): - """Set up configured Mikrotik Controller.""" - hass.data[DOMAIN] = {} - hass.data[DOMAIN][DATA_CLIENT] = {} - return True - - -# --------------------------- -# async_setup_entry -# --------------------------- -async def async_setup_entry(hass, config_entry): - """Set up Mikrotik Router as config entry.""" - name = config_entry.data[CONF_NAME] - host = config_entry.data[CONF_HOST] - port = config_entry.data[CONF_PORT] - username = config_entry.data[CONF_USERNAME] - password = config_entry.data[CONF_PASSWORD] - use_ssl = config_entry.data[CONF_SSL] - if CONF_UNIT_OF_MEASUREMENT in config_entry.data: - traffic_type = config_entry.data[CONF_UNIT_OF_MEASUREMENT] - else: - traffic_type = DEFAULT_TRAFFIC_TYPE - - mikrotik_controller = MikrotikControllerData( - hass, config_entry, name, host, port, username, password, use_ssl, traffic_type - ) - await mikrotik_controller.hwinfo_update() - await mikrotik_controller.async_update() - - if not mikrotik_controller.data: - raise ConfigEntryNotReady() - - hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = mikrotik_controller - - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "sensor") - ) - - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor") - ) - - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "device_tracker") - ) - - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "switch") - ) - - device_registry = await hass.helpers.device_registry.async_get_registry() - device_registry.async_get_or_create( - config_entry_id=config_entry.entry_id, - manufacturer=mikrotik_controller.data["resource"]["platform"], - model=mikrotik_controller.data["routerboard"]["model"], - name=mikrotik_controller.data["routerboard"]["model"], - sw_version=mikrotik_controller.data["resource"]["version"], - ) - - return True - - -# --------------------------- -# async_unload_entry -# --------------------------- -async def async_unload_entry(hass, config_entry): - """Unload a config entry.""" - mikrotik_controller = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] - await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") - await hass.config_entries.async_forward_entry_unload(config_entry, "binary_sensor") - await hass.config_entries.async_forward_entry_unload(config_entry, "device_tracker") - await hass.config_entries.async_forward_entry_unload(config_entry, "switch") - await mikrotik_controller.async_reset() - hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) - return True +"""Mikrotik Router integration.""" + +import logging + +from homeassistant.const import ( + CONF_NAME, + CONF_HOST, + CONF_PORT, + CONF_UNIT_OF_MEASUREMENT, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SSL, +) +from homeassistant.exceptions import ConfigEntryNotReady + +from .const import ( + DOMAIN, + DATA_CLIENT, + DEFAULT_TRAFFIC_TYPE, +) +from .mikrotik_controller import MikrotikControllerData + +_LOGGER = logging.getLogger(__name__) + + +# --------------------------- +# async_setup +# --------------------------- +async def async_setup(hass, _config): + """Set up configured Mikrotik Controller.""" + hass.data[DOMAIN] = {} + hass.data[DOMAIN][DATA_CLIENT] = {} + return True + + +# --------------------------- +# async_setup_entry +# --------------------------- +async def async_setup_entry(hass, config_entry): + """Set up Mikrotik Router as config entry.""" + name = config_entry.data[CONF_NAME] + host = config_entry.data[CONF_HOST] + port = config_entry.data[CONF_PORT] + username = config_entry.data[CONF_USERNAME] + password = config_entry.data[CONF_PASSWORD] + use_ssl = config_entry.data[CONF_SSL] + if CONF_UNIT_OF_MEASUREMENT in config_entry.data: + traffic_type = config_entry.data[CONF_UNIT_OF_MEASUREMENT] + else: + traffic_type = DEFAULT_TRAFFIC_TYPE + + mikrotik_controller = MikrotikControllerData( + hass, config_entry, name, host, port, username, password, use_ssl, + traffic_type + ) + await mikrotik_controller.hwinfo_update() + await mikrotik_controller.async_update() + + if not mikrotik_controller.data: + raise ConfigEntryNotReady() + + hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = mikrotik_controller + + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, "sensor") + ) + + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, + "binary_sensor") + ) + + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, + "device_tracker") + ) + + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, "switch") + ) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + manufacturer=mikrotik_controller.data["resource"]["platform"], + model=mikrotik_controller.data["routerboard"]["model"], + name=mikrotik_controller.data["routerboard"]["model"], + sw_version=mikrotik_controller.data["resource"]["version"], + ) + + return True + + +# --------------------------- +# async_unload_entry +# --------------------------- +async def async_unload_entry(hass, config_entry): + """Unload a config entry.""" + mikrotik_controller = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] + await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") + await hass.config_entries.async_forward_entry_unload(config_entry, + "binary_sensor") + await hass.config_entries.async_forward_entry_unload(config_entry, + "device_tracker") + await hass.config_entries.async_forward_entry_unload(config_entry, "switch") + await mikrotik_controller.async_reset() + hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) + return True diff --git a/custom_components/mikrotik_router/binary_sensor.py b/custom_components/mikrotik_router/binary_sensor.py index 85e74a5..3374fc6 100644 --- a/custom_components/mikrotik_router/binary_sensor.py +++ b/custom_components/mikrotik_router/binary_sensor.py @@ -1,13 +1,14 @@ """Support for the Mikrotik Router binary sensor service.""" import logging -from homeassistant.core import callback + from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.const import ( CONF_NAME, ATTR_ATTRIBUTION, ) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( DOMAIN, diff --git a/custom_components/mikrotik_router/config_flow.py b/custom_components/mikrotik_router/config_flow.py index 7920178..3e9b3be 100644 --- a/custom_components/mikrotik_router/config_flow.py +++ b/custom_components/mikrotik_router/config_flow.py @@ -1,183 +1,185 @@ -"""Config flow to configure Mikrotik Router.""" - -import logging -import voluptuous as vol -from homeassistant.core import callback -from homeassistant.config_entries import ( - CONN_CLASS_LOCAL_POLL, - ConfigFlow, - OptionsFlow, -) -from homeassistant.const import ( - CONF_NAME, - CONF_HOST, - CONF_PORT, - CONF_UNIT_OF_MEASUREMENT, - CONF_USERNAME, - CONF_PASSWORD, - CONF_SSL, -) - -from .const import ( - DOMAIN, - CONF_TRACK_ARP, - DEFAULT_TRACK_ARP, - CONF_SCAN_INTERVAL, - DEFAULT_SCAN_INTERVAL, - DEFAULT_TRAFFIC_TYPE, - TRAFFIC_TYPES, -) - -from .mikrotikapi import MikrotikAPI - -_LOGGER = logging.getLogger(__name__) - -# --------------------------- -# configured_instances -# --------------------------- -@callback -def configured_instances(hass): - """Return a set of configured instances.""" - return set( - entry.data[CONF_NAME] for entry in hass.config_entries.async_entries(DOMAIN) - ) - - -# --------------------------- -# MikrotikControllerConfigFlow -# --------------------------- -class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN): - """MikrotikControllerConfigFlow class""" - - VERSION = 1 - CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL - - def __init__(self): - """Initialize MikrotikControllerConfigFlow.""" - - @staticmethod - @callback - def async_get_options_flow(config_entry): - """Get the options flow for this handler.""" - return MikrotikControllerOptionsFlowHandler(config_entry) - - async def async_step_import(self, user_input=None): - """Occurs when a previously entry setup fails and is re-initiated.""" - return await self.async_step_user(user_input) - - async def async_step_user(self, user_input=None): - """Handle a flow initialized by the user.""" - errors = {} - if user_input is not None: - # Check if instance with this name already exists - if user_input[CONF_NAME] in configured_instances(self.hass): - errors["base"] = "name_exists" - - # Test connection - api = MikrotikAPI( - host=user_input["host"], - username=user_input["username"], - password=user_input["password"], - port=user_input["port"], - use_ssl=user_input["ssl"], - ) - if not api.connect(): - errors[CONF_HOST] = api.error - - # Save instance - if not errors: - return self.async_create_entry( - title=user_input[CONF_NAME], data=user_input - ) - - return self._show_config_form( - host=user_input["host"], - username=user_input["username"], - password=user_input["password"], - port=user_input["port"], - name=user_input["name"], - use_ssl=user_input["ssl"], - errors=errors, - ) - - return self._show_config_form(errors=errors) - - # --------------------------- - # _show_config_form - # --------------------------- - def _show_config_form( - self, - host="10.0.0.1", - username="admin", - password="admin", - port=0, - name="Mikrotik", - use_ssl=False, - errors=None, - ): - """Show the configuration form to edit data.""" - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required(CONF_HOST, default=host): str, - vol.Required(CONF_USERNAME, default=username): str, - vol.Required(CONF_PASSWORD, default=password): str, - vol.Optional( - CONF_UNIT_OF_MEASUREMENT, default=DEFAULT_TRAFFIC_TYPE - ): vol.In(TRAFFIC_TYPES), - vol.Optional(CONF_PORT, default=port): int, - vol.Optional(CONF_NAME, default=name): str, - vol.Optional(CONF_SSL, default=use_ssl): bool, - } - ), - errors=errors, - ) - - -# --------------------------- -# MikrotikControllerOptionsFlowHandler -# --------------------------- -class MikrotikControllerOptionsFlowHandler(OptionsFlow): - """Handle options.""" - - def __init__(self, config_entry): - """Initialize options flow.""" - self.config_entry = config_entry - self.options = dict(config_entry.options) - - async def async_step_init(self, user_input=None): - """Manage the options.""" - return await self.async_step_device_tracker(user_input) - - async def async_step_device_tracker(self, user_input=None): - """Manage the device tracker options.""" - if user_input is not None: - self.options.update(user_input) - return self.async_create_entry(title="", data=self.options) - - return self.async_show_form( - step_id="device_tracker", - data_schema=vol.Schema( - { - vol.Optional( - CONF_TRACK_ARP, - default=self.config_entry.options.get( - CONF_TRACK_ARP, DEFAULT_TRACK_ARP - ), - ): bool, - vol.Optional( - CONF_SCAN_INTERVAL, - default=self.config_entry.options.get( - CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL - ), - ): int, - vol.Optional( - CONF_UNIT_OF_MEASUREMENT, - default=self.config_entry.options.get( - CONF_UNIT_OF_MEASUREMENT, DEFAULT_TRAFFIC_TYPE - ), - ): vol.In(TRAFFIC_TYPES), - } - ), - ) +"""Config flow to configure Mikrotik Router.""" + +import logging + +import voluptuous as vol +from homeassistant.config_entries import ( + CONN_CLASS_LOCAL_POLL, + ConfigFlow, + OptionsFlow, +) +from homeassistant.const import ( + CONF_NAME, + CONF_HOST, + CONF_PORT, + CONF_UNIT_OF_MEASUREMENT, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SSL, +) +from homeassistant.core import callback + +from .const import ( + DOMAIN, + CONF_TRACK_ARP, + DEFAULT_TRACK_ARP, + CONF_SCAN_INTERVAL, + DEFAULT_SCAN_INTERVAL, + DEFAULT_TRAFFIC_TYPE, + TRAFFIC_TYPES, +) +from .mikrotikapi import MikrotikAPI + +_LOGGER = logging.getLogger(__name__) + + +# --------------------------- +# configured_instances +# --------------------------- +@callback +def configured_instances(hass): + """Return a set of configured instances.""" + return set( + entry.data[CONF_NAME] for entry in + hass.config_entries.async_entries(DOMAIN) + ) + + +# --------------------------- +# MikrotikControllerConfigFlow +# --------------------------- +class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN): + """MikrotikControllerConfigFlow class""" + + VERSION = 1 + CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize MikrotikControllerConfigFlow.""" + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return MikrotikControllerOptionsFlowHandler(config_entry) + + async def async_step_import(self, user_input=None): + """Occurs when a previously entry setup fails and is re-initiated.""" + return await self.async_step_user(user_input) + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + if user_input is not None: + # Check if instance with this name already exists + if user_input[CONF_NAME] in configured_instances(self.hass): + errors["base"] = "name_exists" + + # Test connection + api = MikrotikAPI( + host=user_input["host"], + username=user_input["username"], + password=user_input["password"], + port=user_input["port"], + use_ssl=user_input["ssl"], + ) + if not api.connect(): + errors[CONF_HOST] = api.error + + # Save instance + if not errors: + return self.async_create_entry( + title=user_input[CONF_NAME], data=user_input + ) + + return self._show_config_form( + host=user_input["host"], + username=user_input["username"], + password=user_input["password"], + port=user_input["port"], + name=user_input["name"], + use_ssl=user_input["ssl"], + errors=errors, + ) + + return self._show_config_form(errors=errors) + + # --------------------------- + # _show_config_form + # --------------------------- + def _show_config_form( + self, + host="10.0.0.1", + username="admin", + password="admin", + port=0, + name="Mikrotik", + use_ssl=False, + errors=None, + ): + """Show the configuration form to edit data.""" + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_HOST, default=host): str, + vol.Required(CONF_USERNAME, default=username): str, + vol.Required(CONF_PASSWORD, default=password): str, + vol.Optional( + CONF_UNIT_OF_MEASUREMENT, default=DEFAULT_TRAFFIC_TYPE + ): vol.In(TRAFFIC_TYPES), + vol.Optional(CONF_PORT, default=port): int, + vol.Optional(CONF_NAME, default=name): str, + vol.Optional(CONF_SSL, default=use_ssl): bool, + } + ), + errors=errors, + ) + + +# --------------------------- +# MikrotikControllerOptionsFlowHandler +# --------------------------- +class MikrotikControllerOptionsFlowHandler(OptionsFlow): + """Handle options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + self.options = dict(config_entry.options) + + async def async_step_init(self, user_input=None): + """Manage the options.""" + return await self.async_step_device_tracker(user_input) + + async def async_step_device_tracker(self, user_input=None): + """Manage the device tracker options.""" + if user_input is not None: + self.options.update(user_input) + return self.async_create_entry(title="", data=self.options) + + return self.async_show_form( + step_id="device_tracker", + data_schema=vol.Schema( + { + vol.Optional( + CONF_TRACK_ARP, + default=self.config_entry.options.get( + CONF_TRACK_ARP, DEFAULT_TRACK_ARP + ), + ): bool, + vol.Optional( + CONF_SCAN_INTERVAL, + default=self.config_entry.options.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ), + ): int, + vol.Optional( + CONF_UNIT_OF_MEASUREMENT, + default=self.config_entry.options.get( + CONF_UNIT_OF_MEASUREMENT, DEFAULT_TRAFFIC_TYPE + ), + ): vol.In(TRAFFIC_TYPES), + } + ), + ) diff --git a/custom_components/mikrotik_router/device_tracker.py b/custom_components/mikrotik_router/device_tracker.py index 37bbdb2..cd1aaa0 100644 --- a/custom_components/mikrotik_router/device_tracker.py +++ b/custom_components/mikrotik_router/device_tracker.py @@ -1,191 +1,194 @@ -"""Support for the Mikrotik Router device tracker.""" - -import logging -from homeassistant.core import callback -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.components.device_tracker.config_entry import ScannerEntity -from homeassistant.components.device_tracker.const import SOURCE_TYPE_ROUTER -from homeassistant.const import ( - CONF_NAME, - ATTR_ATTRIBUTION, -) -from .const import ( - DOMAIN, - DATA_CLIENT, - ATTRIBUTION, -) - -_LOGGER = logging.getLogger(__name__) - -DEVICE_ATTRIBUTES = [ - "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", - "default-name", -] - - -# --------------------------- -# format_attribute -# --------------------------- -def format_attribute(attr): - res = attr.replace("-", " ") - res = res.capitalize() - res = res.replace(" ip ", " IP ") - res = res.replace(" mac ", " MAC ") - res = res.replace(" mtu", " MTU") - return res - - -# --------------------------- -# async_setup_entry -# --------------------------- -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 = {} - - @callback - def update_controller(): - """Update the values of the controller.""" - update_items(inst, mikrotik_controller, async_add_entities, tracked) - - mikrotik_controller.listeners.append( - async_dispatcher_connect( - hass, mikrotik_controller.signal_update, update_controller - ) - ) - - update_controller() - - -# --------------------------- -# update_items -# --------------------------- -@callback -def update_items(inst, mikrotik_controller, async_add_entities, tracked): - """Update tracked device state from the controller.""" - new_tracked = [] - - for uid in mikrotik_controller.data["interface"]: - if mikrotik_controller.data["interface"][uid]["type"] == "ether": - item_id = ( - f"{inst}-{mikrotik_controller.data['interface'][uid]['default-name']}" - ) - if item_id in tracked: - if tracked[item_id].enabled: - tracked[item_id].async_schedule_update_ha_state() - continue - - tracked[item_id] = MikrotikControllerPortDeviceTracker( - inst, uid, mikrotik_controller - ) - new_tracked.append(tracked[item_id]) - - if new_tracked: - async_add_entities(new_tracked) - - -# --------------------------- -# MikrotikControllerPortDeviceTracker -# --------------------------- -class MikrotikControllerPortDeviceTracker(ScannerEntity): - """Representation of a network port.""" - - def __init__(self, inst, uid, mikrotik_controller): - """Set up tracked port.""" - self._inst = inst - self._ctrl = mikrotik_controller - self._data = mikrotik_controller.data["interface"][uid] - - 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): - """Port entity created.""" - _LOGGER.debug( - "New port tracker %s (%s %s)", - self._inst, - self._data["default-name"], - self._data["port-mac-address"], - ) - - async def async_update(self): - """Synchronize state with controller.""" - - @property - def is_connected(self): - """Return true if the port is connected to the network.""" - return self._data["running"] - - @property - def source_type(self): - """Return the source type of the port.""" - return SOURCE_TYPE_ROUTER - - @property - def name(self): - """Return the name of the port.""" - return f"{self._inst} {self._data['default-name']}" - - @property - def unique_id(self): - """Return a unique identifier for this port.""" - return f"{self._inst.lower()}-{self._data['port-mac-address']}" - - @property - def available(self) -> bool: - """Return if controller is available.""" - return self._ctrl.connected() - - @property - def icon(self): - """Return the icon.""" - if self._data["running"]: - icon = "mdi:lan-connect" - else: - icon = "mdi:lan-pending" - - if not self._data["enabled"]: - icon = "mdi:lan-disconnect" - - return icon - - @property - def device_info(self): - """Return a port description for device registry.""" - info = { - "connections": {(CONNECTION_NETWORK_MAC, self._data["port-mac-address"])}, - "manufacturer": self._ctrl.data["resource"]["platform"], - "model": self._ctrl.data["resource"]["board-name"], - "name": self._data["default-name"], - } - return info - - @property - def device_state_attributes(self): - """Return the port state attributes.""" - attributes = self._attrs - - for variable in DEVICE_ATTRIBUTES: - if variable in self._data: - attributes[format_attribute(variable)] = self._data[variable] - - return attributes +"""Support for the Mikrotik Router device tracker.""" + +import logging + +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, + ATTR_ATTRIBUTION, +) +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, +) + +_LOGGER = logging.getLogger(__name__) + +DEVICE_ATTRIBUTES = [ + "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", + "default-name", +] + + +# --------------------------- +# format_attribute +# --------------------------- +def format_attribute(attr): + res = attr.replace("-", " ") + res = res.capitalize() + res = res.replace(" ip ", " IP ") + res = res.replace(" mac ", " MAC ") + res = res.replace(" mtu", " MTU") + return res + + +# --------------------------- +# async_setup_entry +# --------------------------- +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 = {} + + @callback + def update_controller(): + """Update the values of the controller.""" + update_items(inst, mikrotik_controller, async_add_entities, tracked) + + mikrotik_controller.listeners.append( + async_dispatcher_connect( + hass, mikrotik_controller.signal_update, update_controller + ) + ) + + update_controller() + + +# --------------------------- +# update_items +# --------------------------- +@callback +def update_items(inst, mikrotik_controller, async_add_entities, tracked): + """Update tracked device state from the controller.""" + new_tracked = [] + + for uid in mikrotik_controller.data["interface"]: + if mikrotik_controller.data["interface"][uid]["type"] == "ether": + item_id = ( + f"{inst}-{mikrotik_controller.data['interface'][uid]['default-name']}" + ) + if item_id in tracked: + if tracked[item_id].enabled: + tracked[item_id].async_schedule_update_ha_state() + continue + + tracked[item_id] = MikrotikControllerPortDeviceTracker( + inst, uid, mikrotik_controller + ) + new_tracked.append(tracked[item_id]) + + if new_tracked: + async_add_entities(new_tracked) + + +# --------------------------- +# MikrotikControllerPortDeviceTracker +# --------------------------- +class MikrotikControllerPortDeviceTracker(ScannerEntity): + """Representation of a network port.""" + + def __init__(self, inst, uid, mikrotik_controller): + """Set up tracked port.""" + self._inst = inst + self._ctrl = mikrotik_controller + self._data = mikrotik_controller.data["interface"][uid] + + 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): + """Port entity created.""" + _LOGGER.debug( + "New port tracker %s (%s %s)", + self._inst, + self._data["default-name"], + self._data["port-mac-address"], + ) + + async def async_update(self): + """Synchronize state with controller.""" + + @property + def is_connected(self): + """Return true if the port is connected to the network.""" + return self._data["running"] + + @property + def source_type(self): + """Return the source type of the port.""" + return SOURCE_TYPE_ROUTER + + @property + def name(self): + """Return the name of the port.""" + return f"{self._inst} {self._data['default-name']}" + + @property + def unique_id(self): + """Return a unique identifier for this port.""" + return f"{self._inst.lower()}-{self._data['port-mac-address']}" + + @property + def available(self) -> bool: + """Return if controller is available.""" + return self._ctrl.connected() + + @property + def icon(self): + """Return the icon.""" + if self._data["running"]: + icon = "mdi:lan-connect" + else: + icon = "mdi:lan-pending" + + if not self._data["enabled"]: + icon = "mdi:lan-disconnect" + + return icon + + @property + def device_info(self): + """Return a port description for device registry.""" + info = { + "connections": { + (CONNECTION_NETWORK_MAC, self._data["port-mac-address"])}, + "manufacturer": self._ctrl.data["resource"]["platform"], + "model": self._ctrl.data["resource"]["board-name"], + "name": self._data["default-name"], + } + return info + + @property + def device_state_attributes(self): + """Return the port state attributes.""" + attributes = self._attrs + + for variable in DEVICE_ATTRIBUTES: + if variable in self._data: + attributes[format_attribute(variable)] = self._data[variable] + + return attributes diff --git a/custom_components/mikrotik_router/helper.py b/custom_components/mikrotik_router/helper.py index 194a306..826dc81 100644 --- a/custom_components/mikrotik_router/helper.py +++ b/custom_components/mikrotik_router/helper.py @@ -265,7 +265,8 @@ def fill_vals_proc(data, uid, vals_proc) -> dict: if _action == "combine": if "key" in val: - tmp = _data[val["key"]] if val["key"] in _data else "unknown" + tmp = _data[val["key"]]\ + if val["key"] in _data else "unknown" if not _value: _value = tmp else: diff --git a/custom_components/mikrotik_router/mikrotik_controller.py b/custom_components/mikrotik_router/mikrotik_controller.py index 7e43b1d..1757427 100644 --- a/custom_components/mikrotik_router/mikrotik_controller.py +++ b/custom_components/mikrotik_router/mikrotik_controller.py @@ -1,8 +1,8 @@ """Mikrotik Controller for Mikrotik Router.""" -from datetime import timedelta import asyncio import logging +from datetime import timedelta from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -17,10 +17,9 @@ from .const import ( DEFAULT_SCAN_INTERVAL, DEFAULT_TRAFFIC_TYPE, ) - -from .mikrotikapi import MikrotikAPI -from .helper import from_entry, parse_api from .exceptions import ApiEntryNotFound +from .helper import from_entry, parse_api +from .mikrotikapi import MikrotikAPI _LOGGER = logging.getLogger(__name__) @@ -277,8 +276,10 @@ class MikrotikControllerData: traffic_div = 1 for uid in self.data["interface"]: - self.data["interface"][uid]["rx-bits-per-second-attr"] = traffic_type - self.data["interface"][uid]["tx-bits-per-second-attr"] = traffic_type + self.data["interface"][uid][ + "rx-bits-per-second-attr"] = traffic_type + self.data["interface"][uid][ + "tx-bits-per-second-attr"] = traffic_type self.data["interface"][uid]["rx-bits-per-second"] = round( self.data["interface"][uid]["rx-bits-per-second"] * traffic_div ) @@ -402,7 +403,8 @@ class MikrotikControllerData: self.data["arp"][uid]["mac-address"] = "multiple" self.data["arp"][uid]["address"] = "multiple" else: - self.data["arp"][uid]["mac-address"] = from_entry(entry, "mac-address") + self.data["arp"][uid]["mac-address"] = from_entry(entry, + "mac-address") self.data["arp"][uid]["address"] = ( mac2ip[self.data["arp"][uid]["mac-address"]] if self.data["arp"][uid]["mac-address"] in mac2ip @@ -541,8 +543,8 @@ class MikrotikControllerData: if "status" in self.data["fw-update"]: self.data["fw-update"]["available"] = ( - True - if self.data["fw-update"]["status"] == "New version is available" + True if self.data["fw-update"][ + "status"] == "New version is available" else False ) else: diff --git a/custom_components/mikrotik_router/mikrotikapi.py b/custom_components/mikrotik_router/mikrotikapi.py index da72b3b..0c08f48 100644 --- a/custom_components/mikrotik_router/mikrotikapi.py +++ b/custom_components/mikrotik_router/mikrotikapi.py @@ -1,22 +1,23 @@ """Mikrotik API for Mikrotik Router.""" -import ssl +import importlib import logging +import os +import ssl +import sys import time from threading import Lock from voluptuous import Optional -from .exceptions import ApiEntryNotFound from .const import ( DEFAULT_LOGIN_METHOD, DEFAULT_ENCODING, ) +from .exceptions import ApiEntryNotFound -import os -import sys -import importlib -MODULE_PATH = os.path.join(os.path.dirname(__file__), "librouteros_custom", "__init__.py") +MODULE_PATH = os.path.join(os.path.dirname(__file__), "librouteros_custom", + "__init__.py") MODULE_NAME = "librouteros_custom" spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH) librouteros_custom = importlib.util.module_from_spec(spec) @@ -112,7 +113,8 @@ class MikrotikAPI: ) as api_error: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while connecting: %s", self._host, api_error + "Mikrotik %s error while connecting: %s", self._host, + api_error ) self.connection_error_reported = True @@ -123,7 +125,8 @@ class MikrotikAPI: except: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while connecting: %s", self._host, "Unknown" + "Mikrotik %s error while connecting: %s", self._host, + "Unknown" ) self.connection_error_reported = True @@ -196,7 +199,8 @@ class MikrotikAPI: ValueError, ) as api_error: if not self.connection_error_reported: - _LOGGER.error("Mikrotik %s error while path %s", self._host, api_error) + _LOGGER.error("Mikrotik %s error while path %s", self._host, + api_error) self.connection_error_reported = True self.disconnect() @@ -204,7 +208,8 @@ class MikrotikAPI: return None except: if not self.connection_error_reported: - _LOGGER.error("Mikrotik %s error while path %s", self._host, "unknown") + _LOGGER.error("Mikrotik %s error while path %s", self._host, + "unknown") self.connection_error_reported = True self.disconnect() @@ -215,7 +220,8 @@ class MikrotikAPI: tuple(response) except librouteros_custom.exceptions.ConnectionClosed as api_error: if not self.connection_error_reported: - _LOGGER.error("Mikrotik %s error while path %s", self._host, api_error) + _LOGGER.error("Mikrotik %s error while path %s", self._host, + api_error) self.connection_error_reported = True self.disconnect() @@ -223,7 +229,8 @@ class MikrotikAPI: return None except: if not self.connection_error_reported: - _LOGGER.error("Mikrotik %s error while path %s", self._host, "unknown") + _LOGGER.error("Mikrotik %s error while path %s", self._host, + "unknown") self.connection_error_reported = True self.disconnect() @@ -283,7 +290,8 @@ class MikrotikAPI: ) as api_error: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while update %s", self._host, api_error + "Mikrotik %s error while update %s", self._host, + api_error ) self.connection_error_reported = True @@ -293,7 +301,8 @@ class MikrotikAPI: except: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while update %s", self._host, "unknown" + "Mikrotik %s error while update %s", self._host, + "unknown" ) self.connection_error_reported = True @@ -357,7 +366,8 @@ class MikrotikAPI: ) as api_error: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while run_script %s", self._host, api_error + "Mikrotik %s error while run_script %s", self._host, + api_error ) self.connection_error_reported = True @@ -367,7 +377,8 @@ class MikrotikAPI: except: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while run_script %s", self._host, "unknown" + "Mikrotik %s error while run_script %s", self._host, + "unknown" ) self.connection_error_reported = True @@ -426,7 +437,8 @@ class MikrotikAPI: ) as api_error: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while get_traffic %s", self._host, api_error + "Mikrotik %s error while get_traffic %s", self._host, + api_error ) self.connection_error_reported = True @@ -436,7 +448,8 @@ class MikrotikAPI: except: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while get_traffic %s", self._host, "unknown" + "Mikrotik %s error while get_traffic %s", self._host, + "unknown" ) self.connection_error_reported = True @@ -449,7 +462,8 @@ class MikrotikAPI: except librouteros_custom.exceptions.ConnectionClosed as api_error: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while get_traffic %s", self._host, api_error + "Mikrotik %s error while get_traffic %s", self._host, + api_error ) self.connection_error_reported = True @@ -459,7 +473,8 @@ class MikrotikAPI: except: if not self.connection_error_reported: _LOGGER.error( - "Mikrotik %s error while get_traffic %s", self._host, "unknown" + "Mikrotik %s error while get_traffic %s", self._host, + "unknown" ) self.connection_error_reported = True diff --git a/custom_components/mikrotik_router/sensor.py b/custom_components/mikrotik_router/sensor.py index 98904a3..5a883a2 100644 --- a/custom_components/mikrotik_router/sensor.py +++ b/custom_components/mikrotik_router/sensor.py @@ -1,21 +1,14 @@ """Support for the Mikrotik Router sensor service.""" import logging + +from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS) from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from homeassistant.const import ( - CONF_NAME, - ATTR_ATTRIBUTION, - ATTR_DEVICE_CLASS, -) -from .const import ( - DOMAIN, - DATA_CLIENT, - ATTRIBUTION, -) +from .const import (DOMAIN, DATA_CLIENT, ATTRIBUTION) _LOGGER = logging.getLogger(__name__) @@ -116,13 +109,15 @@ def update_items(inst, mikrotik_controller, async_add_entities, sensors): continue sensors[item_id] = MikrotikControllerSensor( - mikrotik_controller=mikrotik_controller, inst=inst, sensor=sensor + mikrotik_controller=mikrotik_controller, inst=inst, + sensor=sensor ) new_sensors.append(sensors[item_id]) if "traffic_" in sensor: for uid in mikrotik_controller.data["interface"]: - if mikrotik_controller.data["interface"][uid]["type"] == "ether": + if mikrotik_controller.data["interface"][uid][ + "type"] == "ether": item_id = f"{inst}-{sensor}-{mikrotik_controller.data['interface'][uid]['default-name']}" if item_id in sensors: if sensors[item_id].enabled: @@ -248,7 +243,8 @@ class MikrotikControllerTrafficSensor(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_TYPES[sensor][ATTR_PATH]][ + uid] @property def name(self): @@ -264,7 +260,8 @@ class MikrotikControllerTrafficSensor(MikrotikControllerSensor): def device_info(self): """Return a port description for device registry.""" info = { - "connections": {(CONNECTION_NETWORK_MAC, self._data["port-mac-address"])}, + "connections": { + (CONNECTION_NETWORK_MAC, self._data["port-mac-address"])}, "manufacturer": self._ctrl.data["resource"]["platform"], "model": self._ctrl.data["resource"]["board-name"], "name": self._data["default-name"], diff --git a/custom_components/mikrotik_router/switch.py b/custom_components/mikrotik_router/switch.py index 5992fcb..1a7bf5e 100644 --- a/custom_components/mikrotik_router/switch.py +++ b/custom_components/mikrotik_router/switch.py @@ -2,20 +2,13 @@ import logging from homeassistant.components.switch import SwitchDevice +from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION) 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 homeassistant.const import ( - CONF_NAME, - ATTR_ATTRIBUTION, -) -from .const import ( - DOMAIN, - DATA_CLIENT, - ATTRIBUTION, -) +from .const import (DOMAIN, DATA_CLIENT, ATTRIBUTION) _LOGGER = logging.getLogger(__name__) @@ -192,7 +185,8 @@ class MikrotikControllerPortSwitch(MikrotikControllerSwitch): def device_info(self): """Return a port description for device registry.""" info = { - "connections": {(CONNECTION_NETWORK_MAC, self._data["port-mac-address"])}, + "connections": { + (CONNECTION_NETWORK_MAC, self._data["port-mac-address"])}, "manufacturer": self._ctrl.data["resource"]["platform"], "model": self._ctrl.data["resource"]["board-name"], "name": self._data["default-name"], @@ -362,7 +356,8 @@ class MikrotikControllerScriptSwitch(MikrotikControllerSwitch): async def async_added_to_hass(self): """Script switch entity created.""" - _LOGGER.debug("New script switch %s (%s)", self._inst, self._data["name"]) + _LOGGER.debug("New script switch %s (%s)", self._inst, + self._data["name"]) @property def name(self) -> str: