question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[API Proposal]: Adding GpioPin to GpioController in .NET IoT

See original GitHub issue

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: https://github.com/dotnet/iot/issues/1671

API Proposal

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 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() { }
+}

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();

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:

    // Very simplified version, just to show the inconvenient of the solution
    public class MyGpioController : GpioController
    {
        private GpioController _controller1;
        private GpioController _controller2;
        private readonly ConcurrentDictionary<int, PinValue?> _openPins = new();

        public MyGpioController(GpioController driver1, GpioController driver2)
        {
            _controller1 = driver1;
            _controller2 = driver2;
        }

        // We say each driver has 4 pins
        public new int PinCount => 8;

        public new void OpenPin(int pinNumber)
        {
            if ((pinNumber >= 0) && (pinNumber < 4))
            {
                _controller1.OpenPin(pinNumber);
            }
            else if ((pinNumber >= 4) && (pinNumber < 8))
            {
                _controller2.OpenPin(pinNumber - 4);
            }
        }

        public new void Write(int pinNumber, PinValue pinValue)
        {
            if ((pinNumber >= 0) && (pinNumber < 4))
            {
                _controller1.Write(pinNumber, pinValue);
            }
            else if ((pinNumber >= 4) && (pinNumber < 8))
            {
                _controller2.Write(pinNumber - 4, pinValue);
            }
        }

        // All the rest of the controller will have to be implemented that way!
    }

// Then you will have to instantiate your own GpioController with the 2 drivers:
GpioController ctrl = new(
  new GpioController(new GpioDriver1()),
  new GpioController(new GpioDriver2()));

// You usually need a list of pins
List<int> _pins = new() { 0, 1, 2, 3, 4, 5, 6, 7 };

// And then pass it to your device
var myDevice = new MyDevice(_pins, _ctrl);

With GpioPin, everything you need is just a list of GpioPins, regardless of the GpioController they are coming from:

// One controller will come from one driver
GpioController ctrl1 = new(new GpioDrive1());
GpioController ctrl2 = new(new GpioDrive2());
List<GpioPin> _lotsOfPins = new();
for (int i = 0; i< 4; i++)
{
  _lotsOfPins.Add(ctrl1.Open(1));
  _lotsOfPins.Add(ctrl1.Open(2));
}

// The device will take GpioPin list:
var myDevice = new MyDevice(_lotsOfPins);

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.

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:18 (16 by maintainers)

github_iconTop GitHub Comments

2reactions
Ellerbachcommented, Dec 14, 2022

Closing this issue as the implementation has been done and merged.

2reactions
bartonjscommented, Nov 1, 2022
  • The binary breaking change seems to be mitigated by a fairly tight coupling to higher level types that ship out of the same repository, and low usage of these primitive types directly.
  • Based on discussion it seems like the controller needs a Toggle method as well (or TogglePin, whatever you think is best)
  • During discussion it was stated that GpioPin was going to reach through straight to the driver, potentially bypassing any overridden methods in the controller class. This may be too much of a surprise.
    • It was mentioned that the straight-to-the-driver behavior could be conditional on whether this.GetType() == typeof(GpioController).
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() { }
+}
Read more comments on GitHub >

github_iconTop Results From Across the Web

System.Device.Gpio API Proposal
The goal of this API is to allow .Net Core developers to access General Purpose IO (GPIO) pins of IoT devices like Raspberry...
Read more >
IoT with .NET core – Mohamed Ashiq Faleel
Enter the following command on the terminal window to provide root permission for Pin no 22: /usr/bin/gpio export 22 out. · Now run...
Read more >
Use GPIO for binary input
In this tutorial, you'll use .NET and your Raspberry Pi's GPIO pins to detect the opening and closing of a circuit.
Read more >
how to read value from GPIO5 pin of Raspberry PI 3 using ...
Here is a code snippet you can reference: using Windows.Devices.Gpio; private const int GPIO_PIN_NUM = 5; //Initialize gpio pin ...
Read more >
GpioController Class (System.Device.Gpio)
Initializes a new instance of the GpioController class that will use the logical pin numbering scheme as default. GpioController(PinNumberingScheme).
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found