diff --git a/custom_components/eaton_epdu/api.py b/custom_components/eaton_epdu/api.py index 951c833..d6dcfb5 100644 --- a/custom_components/eaton_epdu/api.py +++ b/custom_components/eaton_epdu/api.py @@ -4,12 +4,11 @@ import logging +from pysnmp.error import PySnmpError import pysnmp.hlapi.asyncio as hlapi from pysnmp.hlapi.asyncio import SnmpEngine -from homeassistant.components.snmp import async_get_snmp_engine from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant from .const import ( ATTR_AUTH_KEY, @@ -58,13 +57,26 @@ def __init__(self, entry: ConfigEntry, snmpEngine: SnmpEngine) -> None: """Init the SnmpApi.""" self._snmpEngine = snmpEngine - self._target = hlapi.UdpTransportTarget( - ( - entry.data.get(ATTR_HOST), - entry.data.get(ATTR_PORT, SNMP_PORT_DEFAULT), - ), - 10, - ) + try: + self._target = hlapi.UdpTransportTarget( + ( + entry.data.get(ATTR_HOST), + entry.data.get(ATTR_PORT, SNMP_PORT_DEFAULT), + ), + 10, + ) + except PySnmpError: + try: + self._target = hlapi.Udp6TransportTarget( + ( + entry.data.get(ATTR_HOST), + entry.data.get(ATTR_PORT, SNMP_PORT_DEFAULT), + ), + 10, + ) + except PySnmpError as err: + _LOGGER.error("Invalid SNMP host: %s", err) + return self._version = entry.data.get(ATTR_VERSION) if self._version == SnmpVersion.V1: @@ -90,27 +102,33 @@ def construct_object_types(list_of_oids): async def get(self, oids) -> dict: """Get data for given OIDs in a single call.""" - _LOGGER.debug("Get OID(s) %s", oids) - result = [] - error_indication, error_status, error_index, var_binds = await hlapi.getCmd( - self._snmpEngine, - self._credentials, - self._target, - hlapi.ContextData(), - *__class__.construct_object_types(oids), - ) + while len(oids): + _LOGGER.debug("Get OID(s) %s", oids) + + error_indication, error_status, error_index, var_binds = await hlapi.getCmd( + self._snmpEngine, + self._credentials, + self._target, + hlapi.ContextData(), + *__class__.construct_object_types(oids), + ) + + if error_index: + _LOGGER.debug("Remove error index %d", error_index - 1) + oids.pop(error_index - 1) + continue + + if error_indication or error_status: + raise RuntimeError( + f"Got SNMP error: {error_indication} {error_status} {error_index}" + ) - if not error_indication and not error_status: items = {} for var_bind in var_binds: items[str(var_bind[0])] = __class__.cast(var_bind[1]) - result.append(items) - else: - raise RuntimeError( - "Got SNMP error: {error_indication} {error_status} {error_index}" - ) + return items - return result[0] + return [] async def get_bulk( self, diff --git a/custom_components/eaton_epdu/entity.py b/custom_components/eaton_epdu/entity.py index d986a6d..2abc487 100644 --- a/custom_components/eaton_epdu/entity.py +++ b/custom_components/eaton_epdu/entity.py @@ -1,4 +1,5 @@ """Definition of base Eaton ePDU Entity.""" + from __future__ import annotations from homeassistant.helpers.entity import DeviceInfo @@ -28,6 +29,20 @@ def get_unit_data(self, oid: str, default=None): """Fetch data from coordinator for current unit.""" return self.coordinator.data.get(oid.replace("unit", self._unit), default) + @property + def identifier(self): + """Return the device identifier.""" + return self.get_unit_data( + SNMP_OID_UNITS_SERIAL_NUMBER, + self.get_unit_data( + SNMP_OID_UNITS_DEVICE_NAME, + self.get_unit_data( + SNMP_OID_UNITS_PART_NUMBER, + self.get_unit_data(SNMP_OID_UNITS_PRODUCT_NAME), + ), + ), + ) + @property def device_info(self): """Return the device_info of the device.""" @@ -39,14 +54,10 @@ def device_info(self): name = self.get_unit_data(SNMP_OID_UNITS_PART_NUMBER) return DeviceInfo( - identifiers={ - ( - DOMAIN, - self.get_unit_data(SNMP_OID_UNITS_SERIAL_NUMBER), - ) - }, + identifiers={(DOMAIN, self.identifier)}, manufacturer=MANUFACTURER, model=model, name=name, + serial_number=self.get_unit_data(SNMP_OID_UNITS_SERIAL_NUMBER), sw_version=self.get_unit_data(SNMP_OID_UNITS_FIRMWARE_VERSION), ) diff --git a/custom_components/eaton_epdu/manifest.json b/custom_components/eaton_epdu/manifest.json index a4bdd53..877ed49 100644 --- a/custom_components/eaton_epdu/manifest.json +++ b/custom_components/eaton_epdu/manifest.json @@ -1,6 +1,7 @@ { "domain": "eaton_epdu", "name": "Eaton ePDU", + "after_dependencies": ["snmp"], "codeowners": ["@jaroschek"], "config_flow": true, "dependencies": [], @@ -10,6 +11,6 @@ "issue_tracker": "https://github.com/jaroschek/home-assistant-eaton-epdu/issues", "requirements": ["pysnmp==6.2.5"], "ssdp": [], - "version": "1.1.0", + "version": "1.1.1", "zeroconf": [] } diff --git a/custom_components/eaton_epdu/sensor.py b/custom_components/eaton_epdu/sensor.py index ba9b759..8e70f59 100644 --- a/custom_components/eaton_epdu/sensor.py +++ b/custom_components/eaton_epdu/sensor.py @@ -1,4 +1,5 @@ """Support for Eaton ePDU sensors.""" + from __future__ import annotations from datetime import timedelta @@ -31,7 +32,6 @@ SNMP_OID_OUTLETS_WATTS, SNMP_OID_UNITS_INPUT_COUNT, SNMP_OID_UNITS_OUTLET_COUNT, - SNMP_OID_UNITS_SERIAL_NUMBER, ) from .coordinator import SnmpCoordinator from .entity import SnmpEntity @@ -100,7 +100,7 @@ def __init__(self, coordinator: SnmpCoordinator, unit: str, index: str) -> None: self._attr_name = ( f"{device_name} {self._name_prefix} {sensor_name} {self._name_suffix}" ) - self._attr_unique_id = f"{DOMAIN}_{self.get_unit_data(SNMP_OID_UNITS_SERIAL_NUMBER)}_{self._value_oid}" + self._attr_unique_id = f"{DOMAIN}_{self.identifier}_{self._value_oid}" self._attr_native_value = self.coordinator.data.get( self._value_oid, self._default_value )