dotnet / iot

This repo includes .NET Core implementations for various IoT boards, chips, displays and PCBs.
MIT License
2.13k stars 574 forks source link

Add libgpiodv2 support #2184

Closed huesla closed 5 months ago

huesla commented 7 months ago

Fixes: https://github.com/dotnet/iot/issues/2179 Hi there, This PR aims to integrate version 2 of libgpiod

C library and tools for interacting with the linux GPIO character device (gpiod stands for GPIO device)

Structure Basically, my strategy was to extend as much functionality as possible while touching as little existing code as possible. A lot has changed in the API between v1 and v2 of libgpiod. For example, the control of GPIOs is purely via so-called request objects. If you want to learn more about the concepts, see:

  1. overview https://www.youtube.com/watch?v=EcUULioruj0&t=1s
  2. docs https://libgpiod.readthedocs.io/en/latest/modules.html

As the new functions are very different from the existing ones, I have decided to differentiate between V1 and V2 in the namespace or class name. (What changes together should go together). This concerns the Interop class, as well as the driver and event handler.

The new structure from the lower to the higher level is shown as follows:

Binding Interop.libgpiod Maps all methods of the current libgpiod API, structured in the same way as in the docs.

Proxies libgpiod offers so-called "opaque data classes". These are objects whose fields are not visible and can only be changed via methods. libgpiod divides the API logically according to objects (or modules) see docs. To represent these in C#, there are now so-called proxy classes. Such a class offers the same methods as the controlled gpiod object, but in a more C#-friendly way. LibGpiodProxyBase is the base class and provides methods for thread security. (more information on the threading contract of libgpiod) and wraps any exception from the native call in GpiodException (easier for clients to handle). I think when you know the concept of a libgpiod module, looking at the corresponding proxy class should give a clear picutre in what it does (Basically it only forwards stuff).

Handles Each proxy class works with a SafeHandle, the object that holds the pointer to the gpiod object. SafeHandle comes from the framework and makes sure to call ReleaseHandle() once, which frees the gpiod object.

Enums Constants see docs

LibGpiodV2Driver This class is the "bridge" between GpioController and the binding. Each driver instance manages one gpiochip. It implements the usual methods and manages proxies, mainly the LineRequest to control GPIOs. The methods of the driver were implemented to have as little traffic as possible, e.g. reuse existing LineRequest objects etc.

LibGpiodV2EventObserver Is closely coupled with LibGpiodV2Driver and manages EdgeEvent subscriptions / callbacks.

GpioController Existing class that instantiates the driver. LibGpiodDriverFactory helps to load the correct version suitable for the system. If libgpiodv2 is available, v2 is loaded, otherwise v1 or v0.

Testing Tested with manual tests and GpioControllerTestBase tests on Raspberry Pi 3/4/5 with latest RPI OS (lite). I had to adapt the gpiochip number with Pi5 to 4, instead of 0 on Pi3/4. I am not sure how you treat different boards in unit tests, so I left 0 as default, because Pi5 is not yet as popular.

Testing (continued) The current tests of GpioControllerTestBase use delays, which provokes flakyness and makes the tests slow. I have an improvement branch that uses reset events instead of delays. Again I tested it on the Pi 3/4/5... the time got improved alot and I did not encounter flakyness anymore:

Pi3: 50.2 sec -> 32.2 sec Pi4: 42.8s -> 19.3s Pi5: 40.3s -> 13.2s (measured only once)

I could make a seperate PR for this after this one, depending on how the integration of this will go. The real question is whether other drivers can keep up... would probably have to be tested with your CI pipeline.

Commits The commits are chronologically logically ordered.

Advanced use It should be noted that the current GpioController/Driver architecture imposes restrictions on the libgpiodv2 functionality. For example, information such as ChipInfo, LineInfo, EventInfo, Event sequence numbers etc. are not available. Also functions like setting Debounce Period, EventBuffer size etc. are not available. (One of my use cases is, for example, the time-accurate detection of edges, and the time info is in the edge event as Ns timestamp). In order not to change the existing architecture, I have stuck to it and made proxies etc. internal. However, this also means that this functionality cannot be used externally. My suggestion is therefore to make it public. This allows advanced users extended functionality if they need it. Normal users can continue to use the GpioController API. Since it would probably be a bit tedious (even for advanced users) to manage proxy objects directly, and to use every fine grained function, it would make sense to create an abstraction that would simplify this and cover the most relevant use cases. The code could look much nicer then, for example:

var request = new GpioChip(4).PrepareRequest()
    .SetConsumer("me")
    .AddLineSettings(lineSettings => 
    {
        lineSettings.Lines = new { 12, 18 };
        lineSettings.Direction = LineDirection.Input;
        lineSettings.EdgeDetection = EdgeDetection.Both;
    })
    .DoReqeust();

Task.Run(() =>
{
    foreach (var event in request.GetNextEvent())
    {
        ...
    }
};

request.SetLine(12, High);
request.SetLine(12, Low);

(By the way, this example is inspired by the Python wrapper for libgpiodv2)

I would of course also make documentation for a few examples.

I look forward to comments and opinions :)

Microsoft Reviewers: Open in CodeFlow

EDIT (@krwq): adding link to the issue

pgrawehr commented 5 months ago

@huesla We had a call on Thursday and decided to merge this now and then address some of the remaining issues separately. The two points that were still disputed was the library search algorithm and the best way to expose this new functionality.

For this, I have created #2271 and #2272.

In no way, this should be criticism on your work, this is just trying to allign it with large scale standards.

pgrawehr commented 5 months ago

/azp run dotnet.iot

azure-pipelines[bot] commented 5 months ago
Azure Pipelines successfully started running 1 pipeline(s).