diff options
| author | Michael Auchter <michael.auchter@ni.com> | 2020-01-29 14:02:59 -0600 | 
|---|---|---|
| committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-03 09:34:55 -0500 | 
| commit | 15b94c53edc86031c74b27cd3d824d2291c0c91f (patch) | |
| tree | 1e57c3e87262b8d156336d8c28b6f90e86eda1d5 | |
| parent | 60a147a6a16ced611ab1a7dfdb233d3bd01a0d65 (diff) | |
| download | uhd-15b94c53edc86031c74b27cd3d824d2291c0c91f.tar.gz uhd-15b94c53edc86031c74b27cd3d824d2291c0c91f.tar.bz2 uhd-15b94c53edc86031c74b27cd3d824d2291c0c91f.zip | |
mpm: sys_utils: add libgpiod-based Gpio helper
This adds a new Gpio helper class, which uses libgpiod under the
hood instead of the deprecated sysfs GPIO access. This class provides
the ability to get/set a specific GPIO, which is looked up by name.
| -rwxr-xr-x | mpm/python/tests/base_tests.py | 9 | ||||
| -rwxr-xr-x | mpm/python/tests/sys_utils_tests.py | 1 | ||||
| -rwxr-xr-x | mpm/python/tests/test_utilities.py | 79 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/sys_utils/gpio.py | 60 | 
4 files changed, 143 insertions, 6 deletions
| diff --git a/mpm/python/tests/base_tests.py b/mpm/python/tests/base_tests.py index ec4b8328a..cb57b3481 100755 --- a/mpm/python/tests/base_tests.py +++ b/mpm/python/tests/base_tests.py @@ -7,7 +7,7 @@  Base Test Case classes  """ -import platform +from test_utilities import on_linux, on_usrp  import unittest  class TestBase(unittest.TestCase): @@ -19,7 +19,7 @@ class TestBase(unittest.TestCase):          Test function decorator which skips tests unless the current          execution environment is a linux OS.          """ -        if 'linux' in platform.system().lower(): +        if on_linux():              return lambda func: func          return unittest.skip("This test is only valid when run on a Linux system.") @@ -27,11 +27,8 @@ class TestBase(unittest.TestCase):          """          Test function decorator which skips tests unless the current          execution environment is a USRP. - -        Assumes that 'arm' in the machine name constitutes an ARM -        processor, aka a USRP.          """ -        if 'arm' in platform.machine().lower(): +        if on_usrp():              return lambda func: func          return unittest.skip("This test is only valid when run on the USRP.") diff --git a/mpm/python/tests/sys_utils_tests.py b/mpm/python/tests/sys_utils_tests.py index c21cfea07..50a10e1a1 100755 --- a/mpm/python/tests/sys_utils_tests.py +++ b/mpm/python/tests/sys_utils_tests.py @@ -9,6 +9,7 @@ Tests related to usrp_mpm.sys_utils  from base_tests import TestBase  import unittest +import test_utilities  from usrp_mpm.sys_utils import net  import platform diff --git a/mpm/python/tests/test_utilities.py b/mpm/python/tests/test_utilities.py index 210e76580..198eda5e8 100755 --- a/mpm/python/tests/test_utilities.py +++ b/mpm/python/tests/test_utilities.py @@ -6,6 +6,80 @@  """ Utility classes to facilitate unit testing """  import queue +import platform +import os + +def on_linux(): +    """ +    Returns True if this is being executed on a Linux system +    """ +    return 'linux' in platform.system().lower() + +def on_usrp(): +    """ +    Returns True if this is being executed on an USRP +    """ +    # Check device tree standard property for manufacturer info +    path = '/sys/firmware/devicetree/base/compatible' + +    if not os.path.exists(path): +        return False +    else: +        with open(path, 'r') as f: +            s = f.read() +            # String returned is actually a list of null-terminated strings, +            # replace the null-terminations with a separator +            s.replace('\x00', ';') +            return 'ettus' in s + +def _mock_gpiod_pkg(): +    """ +    Replace the gpiod python package with a mock version if import +    of the gpiod package fails. This package is not available on all +    OS versions that we would like to test in. +    """ +    try: +        import gpiod +    except Exception as ex: +        # The gpiod package should be available if testing on a USRP +        if on_usrp(): +            raise ex +        import sys +        sys.modules["gpiod"] = MockGpiod + +class MockGpiod(object): +    """ +    Mocks a portion of the gpiod python package without actually +    accessing GPIO hardware. +    """ +    LINE_REQ_DIR_IN = 0 +    LINE_REQ_DIR_OUT = 1 + +    _DEFAULT_LINE_VAL = 0 + +    class MockLine(object): +        def __init__(self, val): +            self.val = val + +        def request(self): +            pass + +        def release(self): +            pass + +        def get_value(self): +            return self.val + +        def set_value(self, val): +            self.val = val + +    def __init__(self): +        self.lines = dict() + +    def find_line(self, name): +        if name not in self.lines.keys(): +            self.lines[name] = self.MockLine(self._DEFAULT_LINE_VAL) +        return self.lines[name]  class MockRegsIface(object):      """ @@ -111,3 +185,8 @@ class MockLog(object):              return ''          else:              return log_messages.get_nowait() + +# importing this utilities package should mock out the gpiod package +# if necessary so that usrp_mpm can be imported on devices without +# gpiod support for the OS. +_mock_gpiod_pkg() diff --git a/mpm/python/usrp_mpm/sys_utils/gpio.py b/mpm/python/usrp_mpm/sys_utils/gpio.py new file mode 100644 index 000000000..b609479f1 --- /dev/null +++ b/mpm/python/usrp_mpm/sys_utils/gpio.py @@ -0,0 +1,60 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Access to GPIOs via libgpiod +""" + +import contextlib +import gpiod + + +@contextlib.contextmanager +def request_gpio(line, direction): +    """ +    Context manager for a GPIO line +    """ +    line.request(consumer='mpm', type=direction) +    try: +        yield line +    finally: +        line.release() + + +class Gpio: +    """ +    Class for accessing a named GPIO line via libgpiod +    """ + +    INPUT = gpiod.LINE_REQ_DIR_IN +    OUTPUT = gpiod.LINE_REQ_DIR_OUT + +    def __init__(self, name, direction=INPUT, default_val=None): +        self._direction = direction +        self._line = gpiod.find_line(name) +        self._out_value = False +        if self._line is None: +            raise RuntimeError('failed to find gpio with name %s' % name) + +        if default_val is not None and direction == Gpio.OUTPUT: +            self.set(default_val) + +    def get(self): +        """ +        Read the value of this GPIO +        """ +        if self._direction == self.OUTPUT: +            return self._out_value + +        with request_gpio(self._line, self._direction) as gpio: +            return bool(gpio.get_value()) + +    def set(self, value): +        """ +        Set the value of this GPIO +        """ +        with request_gpio(self._line, self._direction) as gpio: +            gpio.set_value(int(value)) +            self._out_value = bool(value) | 
