1 """Support for displaying collected data over SNMP."""
3 from __future__
import annotations
5 from datetime
import timedelta
7 from struct
import unpack
9 from pyasn1.codec.ber
import decoder
10 from pysnmp.error
import PySnmpError
11 import pysnmp.hlapi.asyncio
as hlapi
12 from pysnmp.hlapi.asyncio
import (
19 from pysnmp.proto.rfc1902
import Opaque
20 from pysnmp.proto.rfc1905
import NoSuchObject
21 import voluptuous
as vol
25 PLATFORM_SCHEMA
as SENSOR_PLATFORM_SCHEMA,
34 CONF_UNIT_OF_MEASUREMENT,
46 TEMPLATE_SENSOR_BASE_SCHEMA,
47 ManualTriggerSensorEntity,
61 DEFAULT_AUTH_PROTOCOL,
66 DEFAULT_PRIV_PROTOCOL,
73 from .util
import async_create_request_cmd_args
75 _LOGGER = logging.getLogger(__name__)
79 TRIGGER_ENTITY_OPTIONS = (
86 CONF_UNIT_OF_MEASUREMENT,
89 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
91 vol.Required(CONF_BASEOID): cv.string,
92 vol.Optional(CONF_ACCEPT_ERRORS, default=
False): cv.boolean,
93 vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string,
94 vol.Optional(CONF_DEFAULT_VALUE): cv.string,
95 vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
96 vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
97 vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
98 vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): vol.In(SNMP_VERSIONS),
99 vol.Optional(CONF_USERNAME): cv.string,
100 vol.Optional(CONF_AUTH_KEY): cv.string,
101 vol.Optional(CONF_AUTH_PROTOCOL, default=DEFAULT_AUTH_PROTOCOL): vol.In(
104 vol.Optional(CONF_PRIV_KEY): cv.string,
105 vol.Optional(CONF_PRIV_PROTOCOL, default=DEFAULT_PRIV_PROTOCOL): vol.In(
109 ).extend(TEMPLATE_SENSOR_BASE_SCHEMA.schema)
115 async_add_entities: AddEntitiesCallback,
116 discovery_info: DiscoveryInfoType |
None =
None,
118 """Set up the SNMP sensor."""
119 host = config.get(CONF_HOST)
120 port = config.get(CONF_PORT)
121 community = config.get(CONF_COMMUNITY)
122 baseoid: str = config[CONF_BASEOID]
123 version = config[CONF_VERSION]
124 username = config.get(CONF_USERNAME)
125 authkey = config.get(CONF_AUTH_KEY)
126 authproto = config[CONF_AUTH_PROTOCOL]
127 privkey = config.get(CONF_PRIV_KEY)
128 privproto = config[CONF_PRIV_PROTOCOL]
129 accept_errors = config.get(CONF_ACCEPT_ERRORS)
130 default_value = config.get(CONF_DEFAULT_VALUE)
134 target = UdpTransportTarget((host, port), timeout=DEFAULT_TIMEOUT)
138 target = Udp6TransportTarget((host, port), timeout=DEFAULT_TIMEOUT)
139 except PySnmpError
as err:
140 _LOGGER.error(
"Invalid SNMP host: %s", err)
148 auth_data = UsmUserData(
150 authKey=authkey
or None,
151 privKey=privkey
or None,
152 authProtocol=getattr(hlapi, MAP_AUTH_PROTOCOLS[authproto]),
153 privProtocol=getattr(hlapi, MAP_PRIV_PROTOCOLS[privproto]),
156 auth_data = CommunityData(community, mpModel=SNMP_VERSIONS[version])
159 get_result = await getCmd(*request_args)
160 errindication, _, _, _ = get_result
162 if errindication
and not accept_errors:
164 "Please check the details in the configuration file: %s",
169 name = config.get(CONF_NAME,
Template(DEFAULT_NAME, hass))
170 trigger_entity_config = {CONF_NAME: name}
171 for key
in TRIGGER_ENTITY_OPTIONS:
172 if key
not in config:
174 trigger_entity_config[key] = config[key]
176 value_template: Template |
None = config.get(CONF_VALUE_TEMPLATE)
178 data =
SnmpData(request_args, baseoid, accept_errors, default_value)
183 """Representation of a SNMP sensor."""
185 _attr_should_poll =
True
192 value_template: Template |
None,
194 """Initialize the sensor."""
201 """Handle adding to Home Assistant."""
206 """Get the latest data and updates the states."""
209 raw_value = self.
datadata.value
211 if (value := self.
datadata.value)
is None:
212 value = STATE_UNKNOWN
214 value = self.
_value_template_value_template.async_render_with_possible_json_value(
223 """Get the latest data and update the states."""
225 def __init__(self, request_args, baseoid, accept_errors, default_value) -> None:
226 """Initialize the data object."""
234 """Get the latest data from the remote SNMP capable host."""
237 errindication, errstatus, errindex, restable = get_result
240 _LOGGER.error(
"SNMP error: %s", errindication)
243 "SNMP error: %s at %s",
244 errstatus.prettyPrint(),
245 restable[-1][
int(errindex) - 1]
if errindex
else "?",
247 elif (errindication
or errstatus)
and self.
_accept_errors_accept_errors:
250 for resrow
in restable:
254 """Decode the different results we could get into strings."""
257 "SNMP OID %s received type=%s and data %s",
262 if isinstance(value, NoSuchObject):
264 "SNMP error for OID %s: No Such Object currently exists at this OID",
269 if isinstance(value, Opaque):
273 if bytes(value).startswith(b
"\x9f\x78"):
274 return str(unpack(
"!f", bytes(value)[3:])[0])
277 decoded_value, _ = decoder.decode(bytes(value))
278 return str(decoded_value)
279 except Exception
as decode_exception:
281 "SNMP error in decoding opaque type: %s", decode_exception
def _decode_value(self, value)
None __init__(self, request_args, baseoid, accept_errors, default_value)
None async_added_to_hass(self)
None __init__(self, HomeAssistant hass, SnmpData data, ConfigType config, Template|None value_template)
None _process_manual_data(self, Any|None value=None)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
RequestArgsType async_create_request_cmd_args(HomeAssistant hass, UsmUserData|CommunityData auth_data, UdpTransportTarget|Udp6TransportTarget target, str object_id)