Skip to content
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

Adding virtual GpioController and virtual GpioPin #2180

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

Ellerbach
Copy link
Member

@Ellerbach Ellerbach commented Nov 25, 2023

Adding virtual GpioController and virtual GpioPin. This allows to recreate a GpioController from different GpioPin created form potentially very different GpioController.

Microsoft Reviewers: Open in CodeFlow

Copy link
Contributor

@pgrawehr pgrawehr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start. I've found some possible pitfalls you might need to look into.

src/System.Device.Gpio/System/Device/Gpio/GpioPin.cs Outdated Show resolved Hide resolved
src/devices/Gpio/Drivers/VirtualGpioController.cs Outdated Show resolved Hide resolved
src/devices/Gpio/Drivers/VirtualGpioController.cs Outdated Show resolved Hide resolved
src/devices/Gpio/Drivers/VirtualGpioController.cs Outdated Show resolved Hide resolved
src/devices/Gpio/Drivers/VirtualGpioController.cs Outdated Show resolved Hide resolved
src/devices/Gpio/README.md Outdated Show resolved Hide resolved
@joperezr
Copy link
Member

Not sure if this proposal was discussed in a meeting I wasn't part of, but I have some questions about the reason why this would be needed. I thought in general we agreed on two models:

  • Cases were all of the pins for a specific device come from the same controller: These cases should just be solved by having the device take in a gpioController in one of its constructors, that has access to all of those pins.
  • Cases were pins used by a device come from different controlelrs: These cases should just be sulved by having the device also have a constructor that takes in individual GpioPins.

The whole reason why we added GpioPin was so that we wouldn't need something like this (which seems to be an approach that uses an emmulation to mix the two scenarios above) since devices can just take individual pins, and we also agreed all devices would get constructors that took Gpio Pins in favor of this. Am I missing something here?

@Ellerbach
Copy link
Member Author

@joperezr yes, we discussed in one of the call. As we have a lot of bindings to adjust, idea is to have a transparent way to use a virtual GPIO controller! So having a virtual controller will solve this issue of having to add all those new constructors and adjust all the internal codes to support GpioPin. Because, it's not just adding ability to have a GpioPin in the constructor, it's about adjusting all the logic in all the bondings to replace GpioController.Write, Read, etc by the GpioPin.Write, Read, etc.

@Ellerbach Ellerbach marked this pull request as ready for review April 8, 2024 09:11
@@ -416,7 +416,7 @@ protected virtual void Dispose(bool disposing)
}

/// <inheritdoc/>
public void Dispose()
public virtual void Dispose()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't be virtual (only the one with bool flag should)

@@ -131,7 +131,7 @@ protected virtual void OpenPinCore(int pinNumber)
/// </summary>
/// <param name="pinNumber">The pin number in the controller's numbering scheme.</param>
/// <param name="mode">The mode to be set.</param>
public GpioPin OpenPin(int pinNumber, PinMode mode)
public virtual GpioPin OpenPin(int pinNumber, PinMode mode)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Triage] Let's try to reduce number of virtual OpenPin methods, if possible only OpenPinCore

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been looking at overwriting only the OpenPinCore but the issue is that the core logic with adding the pin into the lopen list and adding the gpio pin as well is done in the OpenPin. And that is what I want to override. And as the function returns a pin that won't be store properly otherwise, I don't really have a nice option for that!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine for me. I mean if there's an use if this is virtual, I don't bother changing it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I found a good compromise and reviewed a bit the internal elements. So, I only made one of the OpenPin virtual, removed the others, reused some of the logic from the GpioPin dictionary. So it's cleaner.

@@ -8,13 +8,40 @@ namespace System.Device.Gpio
/// </summary>
public class Gpio​Pin
{
private readonly int _pinNumber;
private readonly GpioDriver _driver;
private readonly int _externalPinNumber;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment explaining how's this different to DriverPinNumber and why we need this here and not just i.e. on the virtual pin?

Copy link
Contributor

@pgrawehr pgrawehr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments and questions still.

Some unit tests would be nice.

Comment on lines 91 to 96
/// <summary>
/// Closes an open pin.
/// If allowed by the driver, the state of the pin is not changed.
/// </summary>
/// <param name="pinNumber">The pin number in the controller's numbering scheme.</param>
public override void ClosePin(int pinNumber)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation or description is missleading, as you don't actually close the underlying pin.

Comment on lines 106 to 110
/// <summary>
/// Disposes this instance and closes all open pins associated with this controller.
/// </summary>
/// <param name="disposing">True to dispose all instances, false to dispose only unmanaged resources</param>
protected override void Dispose(bool disposing)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update documentation to better match the behavior

src/devices/Gpio/Drivers/VirtualGpioController.cs Outdated Show resolved Hide resolved
src/devices/Gpio/Drivers/VirtualGpioController.cs Outdated Show resolved Hide resolved
/// <inheritdoc/>
public override WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken)
{
throw new NotImplementedException();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be straight-forward, shouldn't it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no because I don't have access to the Driver neither. If we want to expose it, then yes, it can indeed be done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, got it. Would need the Driver property to be at least protected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been playing with this as well and it's not that straight forward. I may continue a bit but it's not easy and requires to create a virtual driver as well, things like this.

src/devices/Gpio/Drivers/VirtualGpioPin.cs Outdated Show resolved Hide resolved
src/devices/Gpio/Drivers/VirtualGpioPin.cs Show resolved Hide resolved
/// <param name="pinNumber">The pin number.</param>
/// <param name="gpioPin">The <see cref="GpioPin"/> to add.</param>
public void Add(int pinNumber, GpioPin gpioPin) => _pins.TryAdd(pinNumber, gpioPin);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering the complex numbering of the Beaglebone black discussed recently, I would consider two alternatives to the use of integer for the virtual pin numbering.

  • Option 1: A string
  • Option 2: An enum. This could be done by making the controller generic and using Enum as constraint for the generic type (which is new in C# 7.3)

In both cases this could help identifying the pins with any friendly name, which is in use on any board.
Going beyond, you can also define a json map loaded in the project with all the pins getting customized names, similar to this example.

I love the idea of virtualizing pins, but IMO it should/can be more versatile.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've checked all this and if we want to do it we have to keep in mind few things if we want to have done all to the bottom:

  • changing int to string will have an impact on performances as comparing strings and int is slower
  • adjustments can be done and it will require to change the GpioPin itself to store the string instead of the pin number otherwise it will complicated much more everything. And indeed will complicated the internal manipulation

Alternative:

  • if we don't want to use string at the lower level, the possibility is to generate a random, positive, non already existing pin number from the list
  • For this implementation, I will then have to add a new constructor, a new list and new methods using the string on top of the int (getting the int will also be possible anyway)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With regards to the "alternative" solution, one possibility is to provide the GpioPin an optional IDictionary<string, int> to decode the string into the number that makes sense for the board.
As it is optional, if you want pure performance, you can just skip it.
If you want to use it, the GpioPin can use the overload that takes a string and try resolving the pin through the dictionary.

Does this make sense to you?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core issue with this is that it can then be used only when in your own code you are calling this. As all bindings takes a GpioController and pin numbers.
So the solution with alternative is the one compatible with everything in that case. It will indeed require a dictionary and few more functions but ultimately, what is going to be manipulated are just in numbers. So you'll still be able to keep the string in the virtual controller but under the hook and for compatibility reasons, it's going to be pure uint.
As an example on how it can looks like:

// Note that we can add couple of other constructors
var virtualGpio = new VirtualGpioController();
virtualGpio.Add("A42", gpioPinA);
virtualGpio.Add("B26", gpioPinB);
var myButtonPin = virtualGpio.GetPinNumber("A42");
var button = New Button(buttonPin: myButtonPin, gpio = virtualGpio);

In all cases, I would make those adjustments in a separate PR.

/// </summary>
/// <param name="pinNumber">The GPIO pin number.</param>
/// <param name="driver">The GpioDriver.</param>
protected internal Gpio​Pin(int pinNumber, GpioDriver driver)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two APIs should also be Experimental.

@krwq krwq added the Priority:0 Work that we can't release without label Nov 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority:0 Work that we can't release without
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants