-
Notifications
You must be signed in to change notification settings - Fork 588
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
base: main
Are you sure you want to change the base?
Conversation
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.
Good start. I've found some possible pitfalls you might need to look into.
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:
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? |
@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. |
@@ -416,7 +416,7 @@ protected virtual void Dispose(bool disposing) | |||
} | |||
|
|||
/// <inheritdoc/> | |||
public void Dispose() | |||
public virtual void Dispose() |
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.
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) |
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.
[Triage] Let's try to reduce number of virtual OpenPin methods, if possible only OpenPinCore
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'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!
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.
Fine for me. I mean if there's an use if this is virtual, I don't bother changing it.
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 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 GpioPin | |||
{ | |||
private readonly int _pinNumber; | |||
private readonly GpioDriver _driver; | |||
private readonly int _externalPinNumber; |
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.
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?
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.
Some comments and questions still.
Some unit tests would be nice.
/// <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) |
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.
Implementation or description is missleading, as you don't actually close the underlying pin.
/// <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) |
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.
Update documentation to better match the behavior
/// <inheritdoc/> | ||
public override WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken) | ||
{ | ||
throw new NotImplementedException(); |
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.
This should be straight-forward, shouldn't it?
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.
no because I don't have access to the Driver neither. If we want to expose it, then yes, it can indeed be done
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.
Oh, got it. Would need the Driver property to be at least protected.
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'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.
/// <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); | ||
|
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.
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.
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'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)
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.
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?
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.
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 GpioPin(int pinNumber, GpioDriver driver) |
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.
These two APIs should also be Experimental.
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