New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sensor.snmp added decoding of "Opaque" types. #11239
Conversation
Also added test cases for String, Integer and Real types.
tests/components/sensor/test_snmp.py
Outdated
udp.UdpTransport().openServerMode(('127.0.0.1', _PORT)) | ||
) | ||
config.addV1System(self.snmpEngine, 'my-area', 'public') | ||
config.addVacmUser(self.snmpEngine, 2, 'my-area', 'noAuthNoPriv', _BASE_OID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (84 > 79 characters)
tests/components/sensor/test_snmp.py
Outdated
if self.snmpEngine is not None: | ||
self.snmpEngine.transportDispatcher.jobFinished(1) | ||
self.snmpEngine.transportDispatcher.unregisterRecvCbFun(recvId=None) | ||
self.snmpEngine.transportDispatcher.unregisterTransport(udp.domainName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (83 > 79 characters)
tests/components/sensor/test_snmp.py
Outdated
def tearDown(self): | ||
if self.snmpEngine is not None: | ||
self.snmpEngine.transportDispatcher.jobFinished(1) | ||
self.snmpEngine.transportDispatcher.unregisterRecvCbFun(recvId=None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (80 > 79 characters)
tests/components/sensor/test_snmp.py
Outdated
@@ -0,0 +1,144 @@ | |||
import unittest | |||
import os |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'os' imported but unused
First of all, I would like to say thank you for giving this a go. Quite honestly, I had lost all hope that someone would ever look into this. However, I replaced the snmp.py file on my system with your version and after restarting Home Assistant, I got the following error for each of my 4 broken snmp sensors: 2017-12-20 00:42:21 ERROR (MainThread) [homeassistant.components.sensor] Error on device update!
Traceback (most recent call last):
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/helpers/entity_component.py", line 217, in async_add_entity
yield from entity.async_device_update(warning=False)
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 306, in async_device_update
yield from self.hass.async_add_job(self.update)
File "/usr/local/lib/python3.6/asyncio/futures.py", line 332, in __iter__
yield self # This tells Task to wait for completion.
File "/usr/local/lib/python3.6/asyncio/tasks.py", line 250, in _wakeup
future.result()
File "/usr/local/lib/python3.6/asyncio/futures.py", line 245, in result
raise self._exception
File "/usr/local/lib/python3.6/concurrent/futures/thread.py", line 56, in run
result = self.fn(*self.args, **self.kwargs)
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/components/sensor/snmp.py", line 122, in update
self.data.update()
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/components/sensor/snmp.py", line 183, in update
value, _ = decoder.decode(bytes(resrow[-1]))
File "/srv/hass_venv/lib/python3.6/site-packages/pyasn1/codec/ber/decoder.py", line 1183, in __call__
'%s not in asn1Spec: %r' % (tagSet, asn1Spec)
pyasn1.error.PyAsn1Error: [128:0:120] not in asn1Spec: None |
self.value = str(resrow[-1]) | ||
self.value = self._decode_value(resrow[-1]) | ||
|
||
def _decode_value(self, value): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstring is missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Getting closer:
sensor:
- platform: snmp
host: 127.0.0.1
baseoid: "1.3.6.1.4.1.2021.10.1.6.1"
name: stringvar
community: public
port: 161
logger:
default: info
logs:
homeassistant.components.sensor.snmp: debug
So right now I can successfully decode a Float. Not sure if that is the most elegant solution... |
_OID3 = _BASE_OID + (3, 1, 1) | ||
|
||
|
||
class TestSnmp(unittest.TestCase): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add docstrings to the whole file. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
tests/components/sensor/test_snmp.py
Outdated
from pysnmp.carrier.asyncore.dgram import udp | ||
from pysnmp.proto.api import v2c | ||
from pyasn1.codec.ber import encoder | ||
from pyasn1.type import univ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'pyasn1.type.univ' imported but unused
tests/components/sensor/test_snmp.py
Outdated
from pysnmp.entity.rfc3413 import cmdrsp, context | ||
from pysnmp.carrier.asyncore.dgram import udp | ||
from pysnmp.proto.api import v2c | ||
from pyasn1.codec.ber import encoder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'pyasn1.codec.ber.encoder' imported but unused
I also added a feature request here: etingof/pysnmp#118 |
Just a quick note that you may also use pyasn1 infrastructure for reliably decoding whatever ASN.1 structures should occur inside the |
I just replaced my snmp.py file once more and I can't believe it...it's working! 😮 Thank you so much, @ChristianKuehnel ! I know this can still be improved when @etingof implements your FR into pysnmp, but this is awesome already in my book! 👍 |
Hey @etingof if I understand it correctly, the pyasn1 infrastructure does depend on some C/C++ libraries? |
Hey, no! pyasn1 is a pure-Python tool and has no external dependencies. pysnmp is totally dependent on pyasn1. |
OK good. |
Right, there is no What I meant though is that the wrapping BER encoding could be handled by pyasn1 like this:
|
Hmmm I can't seem to apply a I have the following SNMP sensors (the bottom 4 are Opaque types): I wish to change the shown state for the battery voltage sensor (for obvious reasons) using the following: 2018-01-01 04:34:02 ERROR (MainThread) [homeassistant.components.sensor] Error on device update!
Traceback (most recent call last):
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/helpers/entity_component.py", line 217, in async_add_entity
yield from entity.async_device_update(warning=False)
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 306, in async_device_update
yield from self.hass.async_add_job(self.update)
File "/usr/local/lib/python3.6/asyncio/futures.py", line 332, in __iter__
yield self # This tells Task to wait for completion.
File "/usr/local/lib/python3.6/asyncio/tasks.py", line 250, in _wakeup
future.result()
File "/usr/local/lib/python3.6/asyncio/futures.py", line 245, in result
raise self._exception
File "/usr/local/lib/python3.6/concurrent/futures/thread.py", line 56, in run
result = self.fn(*self.args, **self.kwargs)
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/components/sensor/snmp.py", line 131, in update
value, STATE_UNKNOWN)
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/helpers/template.py", line 129, in render_with_possible_json_value
error_value).result()
File "/usr/local/lib/python3.6/concurrent/futures/_base.py", line 432, in result
return self.__get_result()
File "/usr/local/lib/python3.6/concurrent/futures/_base.py", line 384, in __get_result
raise self._exception
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/util/async.py", line 167, in run_callback
future.set_result(callback(*args))
File "/srv/hass_venv/lib/python3.6/site-packages/homeassistant/helpers/template.py", line 147, in async_render_with_possible_json_value
variables['value_json'] = json.loads(value)
File "/usr/local/lib/python3.6/json/__init__.py", line 348, in loads
'not {!r}'.format(s.__class__.__name__))
TypeError: the JSON object must be str, bytes or bytearray, not 'float' I know my template is fine as I'm able to use it with other sensors (e.g. InfluxDB sensors). |
Hmm, OK. I added a type cast to string before returning the float. Can you give it another shot? |
@ChristianKuehnel Just tested your change, it works! 😀 |
weird test results --> closing/opening to trigger new build. |
This is weird. The error on Travis is:
|
problem resolved with a merge of Dev and a re-run of gen_requirements_all |
Should this have been included in 0.61.0? I've been using the code in this PR for the past 2 weeks without issues 😁 |
As the change was not yet merged, it's not contained in the 0.61 release. It seems with my latest merge of the Dev branch I broke some tests. I'll have a investigate this on the weekend... |
decoded_value, _ = decoder.decode(bytes(value)) | ||
return str(decoded_value) | ||
# pylint: disable=broad-except | ||
except Exception as decode_exception: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it possible to catch a more specific error, like ValueError
?
# pylint: disable=broad-except | ||
except Exception as decode_exception: | ||
_LOGGER.error('SNMP error in decoding opaque type: %s', | ||
str(decode_exception)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy to string shouldn't be necessary.
"""Test basic functionality of sensor.snmp.""" | ||
|
||
def setUp(self): | ||
"""Setup a hoss instance for testing.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo.
config.addTransport( | ||
self.snmpEngine, | ||
udp.domainName, | ||
udp.UdpTransport().openServerMode(('127.0.0.1', _PORT)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the engine should be mocked. Why do we need a real engine? We only need to test the home assistant side of the logic.
what's the status? |
Please reopen the PR after all comments are addressed |
Description:
SNMP does not support floating point data as native data types. It rather uses asn1 encoding and transfers them as bytes. The sensor.snmp uses pysnmp and that in turn uses pyasn1 to encode/decode these things. So as long as the encoding is supported by pyasn1, this change should be able to decode it.
Related issue (if applicable): fixes #2767
Pull request in home-assistant.github.io with documentation (if applicable): N/A
Example entry for
configuration.yaml
(if applicable):see #2767
Checklist:
If the code does not interact with devices:
tox
run successfully. Your PR cannot be merged unless tests pass