-
Notifications
You must be signed in to change notification settings - Fork 574
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
[API Proposal]: Adding GpioPin to GpioController in .NET IoT #1920
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
@Ellerbach @krwq do we track API reviews on IoT API here? I wonder whether https://apireview.Net should look at that repo also. @terrajobst |
I'm not sure if API review does it (guessing no), we only do occasional API reviews in IoT so I don't think it makes sense to set it up permanently. This one was explicitly requested because we had some more discussion on this particular set of APIs and need advice from API review folks. I'd prefer we make this as one time thing since for most of the APIs we'd do API review during our weekly triage meeting rather than official one. |
Hmm I missed the fact this was filed on dotnet/runtime and not dotnet/iot. I think that's fine in that case, this is a one time request for API review, we normally don't want that. I'm not strongly opinionated where issue should live - if there is easier way we can request one time API review then we can go ahead with that |
Tagging subscribers to this area: @dotnet/area-system-runtime Issue DetailsBackground and motivationIn .NET IoT (https://github.com/dotnet/iot/) we do have a GpioController allowing operations on GPIO (referred as pin later). To open, write, read or close a pin, it is necessary to use the GpioController. This GpioPin is already present and used in .NET nanoFramework (https://github.com/nanoframework/). We've been working to align as best as possible all hardware related API between .NET IoT and .NET nanoFramework. So GPIO, SPI, I2C, PWM, Serial API are aligned. Due to the platform differences, there are minor differences. The GpioPin in .NET nanoFramework can be found here: https://github.com/nanoframework/System.Device.Gpio/blob/main/System.Device.Gpio/GpioPin.cs Adding a GpioPin makes it easy for simple operations and a simpler concept as well for beginners. It is important to keep in mind that only the GpioController is allow to open pins and is responsible ultimately for the life of the GpioPin. You cannot open a GpioPin without the GpioController. You can dispose your GpioPin which will close it in the GpioController. And if the GpioController is disposed, the GpioPin won't be able to operate the pin. This deisgn exist in .NET nanoFramework and is very sucessfull. It was existing before the GpioController API aligned and based on developers' feedback has been kept. Adding this GpioPin to the GpioController will result in a breaking change in the OpenPin function. This is not a code breaking change, it is a binary breaking change. See the risk section on this. When using multiple GpioControllers or with specific controllers like the FT family, this can be an advantage to simplify the management of the pins. It could allow some better performance as well in those scenarios. An active issue is open in .NET IoT which also reference other closed issues with more detailed discussions: #1671 API ProposalSee the PR in .NET IoT with the proposed implementation: #1895 API Usage// As always get a GpioController
const int PinNumber = 42;
GpioController ctrl = new GpioController();
// Then open a pin and get the GpioPin
GpioPin pin = ctrl.OpenPin(PinNumber, PinMode.Input);
// Then do directly the operations on the pin:
// Write operation, equivalentin to ctrl.Write(PinNumber, PinValue.High)
pin.Write(PinValue.High)
// Read operation, equivalent to ctrl.Read(PinNumber);
var val = pin.Read();
// Togle the pin value, no equivalent using the GpioController except reading and writing the opposite value
pin.Toggle(); Alternative DesignsThe alternative design is to not introduce the GpioPin and stay as it is. RisksMain risk is the binary breaking change introduce by the change of the OpenPin function, now returning a GpioPin rather than being void. This change is not a code breaking change, it's only in the case of updating the .NET IoT nuget in an application without recompiling it. We think, this scenario is very minimal, and most people are rebuilding the solution when updating nugets. When recompiling the solution, no change is necessary, just to recompile the code as everything is source compatible.
|
@krwq, I think its probably better to live in It's worth noting to @Ellerbach that API review would appreciate if the following could be fixed up to actually contain the API surface:
Doing so will greatly help with API review and ensure that its short/clear/immediately available. We don't need (and prefer not to have) the implementation as part of the proposed API surface. A link after the proposal to the implementation is, however, welcome 😄 |
Oh, I also missed the fact that this is in the wrong repo. Will transfer to dotnet/iot. I already sent an internal email to API Review board to get it on the books. @danmoseley I don't think we need to have API Review monitor dotnet/iot as most of the API changes we make don't go through API Review. This particular one is a rather impacting one that could use expert's advice, which is why we are asking for consultation, but it is more of a one-off kind of thing. |
One observation that just jumped on me: If |
After the above comments, I think the proposal should be cleaned up as follows: namespace System.Device.Gpio;
public class GpioController : IDisposable
{
// Changing current OpenPin overloads to return a GpioPin instead of void. Binary breaking, non source breaking.
- public void OpenPin(int pinNumber) { }
+ public GpioPin OpenPin(int pinNumber) { }
- public void OpenPin(int pinNumber, PinMode mode) { }
+ public GpioPin OpenPin(int pinNumber, PinMode mode) { }
- public void OpenPin(int pinNumber, PinMode mode, PinValue initialValue) { }
+ public GpioPin OpenPin(int pinNumber, PinMode mode, PinValue initialValue) { }
}
+public class GpioPin : IDisposable
+{
+ public GpioPin(int pinNumber, GpioController controller) {};
+ public virtual int PinNumber { get; }
+ public virtual PinMode GetPinMode() { }
+ public virtual bool IsPinModeSupported(PinMode pinMode) { }
+ public virtual void SetPinMode(PinMode value) { }
+ public virtual PinValue Read() { }
+ public virtual void Write(PinValue value) { }
+ public event PinChangeEventHandler ValueChanged;
+ protected virtual void Dispose(bool disposing) {};
+ public void Dispose() {};
+} Open Questions: Should
|
I agree with @pgrawehr. Let me add a couple of notes:
Not sure if I will be able to join the call, sorry. |
For 1, 100% agree - for 2/3 I'd only consider that if there is a hardware support - for Toggle I believe we can support it at least on some drivers I'm not sure about pulses |
@krwq I understand your point but given the library is an abstraction over many vendors, it is not likely to see the same support for all of them. |
Here are my few notes on the open questions from above:
I could be convinced otherwise, but I wouldn't mark it as disposable. Disposable types either hold native resources, or have members that do which need to be disposed of. GpioPin's case doesn't apply to any of those, as the native resources are actually hold by the driver, and GpioPin doesn't have any other members that need to be disposed of. As for the comment regarding cleaning up after use, device bindings should just call Close() instead.
My answer here would be: no, the constructor for Gpio Pin should only be internal, and the only way to get a GpioPin back should be via calling OpenPin in a controller. If you want a GpioPin dervied type back, then the way to do it is via creating a controller which has a specific custom driver passed in, which returns this custom gpiopin back. For that, this proposal is also missing changing the driver's open pin to also return GpioPin so that derived drivers can return derived GpioPins.
Unless we have a very specific use in mind, I'd start with making it sealed, and then in the future we can unseal it if we need to. We can't go the other way around.
I would prefer not to. We have a Write method, and Toggle is a method that can be very easily added on the consuming code as a helper. In any case, I think this decision doesn't need to be made now, so I'd rather not to include it as part of this proposal, and then add it later as a subsequent proposal if we see it provides enough value.
Risk is pretty high. The main issue will be that this is a binary breaking change and it will very likely break anyone in the library case. By this I mean: Imagine a own a robotic company which has a robot, and I wanted to ship a nuget package with a binding for my robot that internally uses System.Device.Gpio. I will depend on one version of that, for example, in version 2.2. If a consuming app then wants to use my robot nuget package, they won't be able to use our 3.0 System.Device.Gpio package as well, since if they do, it would break the robot package. In other words, any code that depends on System.Device.Gpio today would be binary roken after this change, and would be forced to recompile. |
I'm always more in favor of not sealing. But we can for sure.
Toggle is actually available natively on many hardware. Most extenders like the FT ones have this capability. Also in MCU it's always the case making the change of a pin really fast. Now, that's as well a reason we should not seal the implementation to allow faster usage.
Like @krwq, I'm not super in favor of the pulse one. It will always be very dependent of the driver to get a maximum performance. Something that can be added later. Also the pulse one is most of the time a PWM, so I would recommend then to go for a PWM pin instead at least it makes things clear. For the rest aligned with the comments from @joperezr. |
My take on this:
|
@joperezr got it. If the breaking change is unavoidable, I would rather prefer to introduce a new, let's say, @Ellerbach : About With regards to |
namespace System.Device.Gpio;
public class GpioController : IDisposable
{
// Changing current OpenPin overloads to return a GpioPin instead of void. Binary breaking, non source breaking.
- public void OpenPin(int pinNumber) { }
+ public GpioPin OpenPin(int pinNumber) { }
- public void OpenPin(int pinNumber, PinMode mode) { }
+ public GpioPin OpenPin(int pinNumber, PinMode mode) { }
- public void OpenPin(int pinNumber, PinMode mode, PinValue initialValue) { }
+ public GpioPin OpenPin(int pinNumber, PinMode mode, PinValue initialValue) { }
+ public void Toggle(int pinNumber) { }
}
+public sealed class GpioPin
+{
+ public int PinNumber { get; }
+ public PinMode GetPinMode() { }
+ public bool IsPinModeSupported(PinMode pinMode) { }
+ public void SetPinMode(PinMode value) { }
+ public PinValue Read() { }
+ public void Write(PinValue value) { }
+ public event PinChangeEventHandler ValueChanged;
+ public void Toggle() { }
+} |
Closing this issue as the implementation has been done and merged. |
Background and motivation
In .NET IoT (https://github.com/dotnet/iot/) we do have a GpioController allowing operations on GPIO (referred as pin later). To open, write, read or close a pin, it is necessary to use the GpioController.
We want to propose adding a GpioPin class that will make it easier to access directly a pin.
This GpioPin is already present and used in .NET nanoFramework (https://github.com/nanoframework/). We've been working to align as best as possible all hardware related API between .NET IoT and .NET nanoFramework. So GPIO, SPI, I2C, PWM, Serial API are aligned. Due to the platform differences, there are minor differences.
The GpioPin in .NET nanoFramework can be found here: https://github.com/nanoframework/System.Device.Gpio/blob/main/System.Device.Gpio/GpioPin.cs
Adding a GpioPin makes it easy for simple operations and a simpler concept as well for beginners. It is important to keep in mind that only the GpioController is allow to open pins and is responsible ultimately for the life of the GpioPin. You cannot open a GpioPin without the GpioController. You can dispose your GpioPin which will close it in the GpioController. And if the GpioController is disposed, the GpioPin won't be able to operate the pin. This deisgn exist in .NET nanoFramework and is very sucessfull. It was existing before the GpioController API aligned and based on developers' feedback has been kept.
Adding this GpioPin to the GpioController will result in a breaking change in the OpenPin function. This is not a code breaking change, it is a binary breaking change. See the risk section on this.
When using multiple GpioControllers or with specific controllers like the FT family, this can be an advantage to simplify the management of the pins. It could allow some better performance as well in those scenarios.
An active issue is open in .NET IoT which also reference other closed issues with more detailed discussions: #1671
API Proposal
API Usage
Something we are trying to solve is when we do have extender and need a lot of pins. In the current situation, the only way to do this is to create your own controller taking the other controllers you need. So you basically have to do something like this:
With GpioPin, everything you need is just a list of GpioPins, regardless of the GpioController they are coming from:
Alternative Designs
The alternative design is to not introduce the GpioPin and stay as it is.
Risks
Main risk is the binary breaking change introduce by the change of the OpenPin function, now returning a GpioPin rather than being void. This change is not a code breaking change, it's only in the case of updating the .NET IoT nuget in an application without recompiling it. We think, this scenario is very minimal, and most people are rebuilding the solution when updating nugets.
When recompiling the solution, no change is necessary, just to recompile the code as everything is source compatible.
The text was updated successfully, but these errors were encountered: