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

Add virtual gpio controller, attempt 2 #2368

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
32 changes: 27 additions & 5 deletions src/System.Device.Gpio.Tests/GpioPinTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ public void TestOpenPin()
Assert.Equal(PinMode.Input, pin.GetPinMode());
}

/// <summary>
/// Closes the pin via the controller first
/// </summary>
[Fact]
public void TestClosePin()
public void TestClosePin1()
{
// Arrange
_mockedGpioDriver.Setup(x => x.OpenPinEx(PinNumber));
Expand All @@ -52,10 +55,29 @@ public void TestClosePin()
// Act
GpioPin pin = ctrl.OpenPin(PinNumber, PinMode.Input);
ctrl.ClosePin(PinNumber);
// Assert
// This should work even if the pin is closed in the controller as the driver has no idea
// Of the controller behavior.
var ret = pin.Read();
// Closing the pin makes its usage invalid
Assert.Throws<InvalidOperationException>(() => pin.Read());
pin.Dispose(); // Shouldn't throw
}

/// <summary>
/// Closes the pin via Dispose first
/// </summary>
[Fact]
public void TestClosePin2()
{
// Arrange
_mockedGpioDriver.Setup(x => x.OpenPinEx(PinNumber));
_mockedGpioDriver.Setup(x => x.IsPinModeSupportedEx(PinNumber, It.IsAny<PinMode>())).Returns(true);
_mockedGpioDriver.Setup(x => x.SetPinModeEx(PinNumber, It.IsAny<PinMode>()));
var ctrl = new GpioController(_mockedGpioDriver.Object);
// Act
GpioPin pin = ctrl.OpenPin(PinNumber, PinMode.Input);
pin.Dispose();
// Closing the pin makes its usage invalid
Assert.Throws<InvalidOperationException>(() => ctrl.Read(PinNumber));
// That is not valid now
Assert.Throws<InvalidOperationException>(() => ctrl.ClosePin(PinNumber));
}

[Fact]
Expand Down
21 changes: 21 additions & 0 deletions src/System.Device.Gpio/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,27 @@
<Left>lib/netstandard2.0/System.Device.Gpio.dll</Left>
<Right>lib/net6.0-windows10.0.17763/System.Device.Gpio.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0007</DiagnosticId>
<Target>T:System.Device.Gpio.GpioPin</Target>
<Left>lib/net6.0-windows10.0.17763/System.Device.Gpio.dll</Left>
<Right>lib/net6.0-windows10.0.17763/System.Device.Gpio.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0007</DiagnosticId>
<Target>T:System.Device.Gpio.GpioPin</Target>
<Left>lib/net6.0/System.Device.Gpio.dll</Left>
<Right>lib/net6.0/System.Device.Gpio.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0007</DiagnosticId>
<Target>T:System.Device.Gpio.GpioPin</Target>
<Left>lib/netstandard2.0/System.Device.Gpio.dll</Left>
<Right>lib/netstandard2.0/System.Device.Gpio.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:System.Device.Gpio.Drivers.GpiodException</Target>
Expand Down
2 changes: 1 addition & 1 deletion src/System.Device.Gpio/System.Device.Gpio.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
</PackageReference>
<Content Include="$(RepoRoot)README-nuget.md" Pack="true" Visible="false" PackagePath="\README.md" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Win32.Registry" Version="$(MicrosoftWin32RegistryPackageVersion)" /> <!-- This is Windows specific -->
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
Expand Down
4 changes: 2 additions & 2 deletions src/System.Device.Gpio/System/Device/Gpio/GpioController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public GpioPin OpenPin(int pinNumber)

OpenPinCore(pinNumber);
_openPins.TryAdd(pinNumber, null);
_gpioPins[pinNumber] = new GpioPin(pinNumber, _driver);
_gpioPins[pinNumber] = new GpioPin(pinNumber, this);
return _gpioPins[pinNumber];
}

Expand Down Expand Up @@ -261,7 +261,7 @@ public virtual PinMode GetPinMode(int pinNumber)
/// </summary>
/// <param name="pinNumber">The pin number in the controller's numbering scheme.</param>
/// <returns>The status if the pin is open or closed.</returns>
public bool IsPinOpen(int pinNumber)
public virtual bool IsPinOpen(int pinNumber)
{
CheckDriverValid();
return _openPins.ContainsKey(pinNumber);
Expand Down
109 changes: 96 additions & 13 deletions src/System.Device.Gpio/System/Device/Gpio/GpioPin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ namespace System.Device.Gpio
/// <summary>
/// Represents a general-purpose I/O (GPIO) pin.
/// </summary>
public class Gpio​Pin
public class Gpio​Pin : MarshalByRefObject, IEquatable<GpioPin>, IDisposable
{
private readonly int _pinNumber;
private readonly GpioDriver _driver;
private readonly GpioController _controller;

internal Gpio​Pin(int pinNumber, GpioDriver driver)
/// <summary>
/// Create an instance of a GpioPin
/// </summary>
/// <param name="pinNumber">The pin number</param>
/// <param name="controller">The matching controller</param>
/// <remarks>Instances of this class are usually only generated by an instance of <see cref="GpioController"/></remarks>
protected internal Gpio​Pin(int pinNumber, GpioController controller)
{
_driver = driver;
_controller = controller;
_pinNumber = pinNumber;
}

Expand All @@ -25,12 +31,17 @@ public class Gpio​Pin
/// </value>
public virtual int PinNumber => _pinNumber;

/// <summary>
/// The GPIO Controller this pin is assigned to
/// </summary>
public GpioController Controller => _controller;

/// <summary>
/// Gets the current pin mode for the general-purpose I/O (GPIO) pin. The pin mode specifies whether the pin is configured as an input or an output, and determines how values are driven onto the pin.
/// </summary>
/// <returns>An enumeration value that indicates the current pin mode for the GPIO pin.
/// The pin mode specifies whether the pin is configured as an input or an output, and determines how values are driven onto the pin.</returns>
public virtual PinMode GetPinMode() => _driver.GetPinMode(_pinNumber);
public virtual PinMode GetPinMode() => _controller.GetPinMode(_pinNumber);

/// <summary>
/// Gets whether the general-purpose I/O (GPIO) pin supports the specified pin mode.
Expand All @@ -40,7 +51,7 @@ public class Gpio​Pin
/// <see langword="true"/> if the GPIO pin supports the pin mode that pinMode specifies; otherwise false.
/// If you specify a pin mode for which this method returns <see langword="false"/> when you call <see cref="SetPinMode"/>, <see cref="SetPinMode"/> generates an exception.
/// </returns>
public virtual bool IsPinModeSupported(PinMode pinMode) => _driver.IsPinModeSupported(_pinNumber, pinMode);
public virtual bool IsPinModeSupported(PinMode pinMode) => _controller.IsPinModeSupported(_pinNumber, pinMode);

/// <summary>
/// Sets the pin mode of the general-purpose I/O (GPIO) pin.
Expand All @@ -49,13 +60,13 @@ public class Gpio​Pin
/// <param name="value">An enumeration value that specifies pin mode to use for the GPIO pin.
/// The pin mode specifies whether the pin is configured as an input or an output, and determines how values are driven onto the pin.</param>
/// <exception cref="ArgumentException">The GPIO pin does not support the specified pin mode.</exception>
public virtual void SetPinMode(PinMode value) => _driver.SetPinMode(_pinNumber, value);
public virtual void SetPinMode(PinMode value) => _controller.SetPinMode(_pinNumber, value);

/// <summary>
/// Reads the current value of the general-purpose I/O (GPIO) pin.
/// </summary>
/// <returns>The current value of the GPIO pin. If the pin is configured as an output, this value is the last value written to the pin.</returns>
public virtual PinValue Read() => _driver.Read(_pinNumber);
public virtual PinValue Read() => _controller.Read(_pinNumber);

/// <summary>
/// Drives the specified value onto the general purpose I/O (GPIO) pin according to the current pin mode for the pin
Expand All @@ -66,27 +77,99 @@ public class Gpio​Pin
/// <para>If the GPIO pin is configured as an input, the method updates the latched output value for the pin. The latched output value is driven onto the pin when the configuration for the pin changes to output.</para>
/// </param>
/// <exception cref="InvalidOperationException">This exception will be thrown on an attempt to write to a pin that hasn't been opened or is not configured as output.</exception>
public virtual void Write(PinValue value) => _driver.Write(_pinNumber, value);
public virtual void Write(PinValue value) => _controller.Write(_pinNumber, value);

/// <summary>
/// Occurs when the value of the general-purpose I/O (GPIO) pin changes, either because of an external stimulus when the pin is configured as an input, or when a value is written to the pin when the pin in configured as an output.
/// Occurs when the value of the general-purpose I/O (GPIO) pin changes, either because of an external stimulus when the pin is configured as an input, or when a value is written
/// to the pin when the pin in configured as an output.
/// </summary>
public virtual event PinChangeEventHandler ValueChanged
{
add
{
_driver.AddCallbackForPinValueChangedEvent(_pinNumber, PinEventTypes.Falling | PinEventTypes.Rising, value);
_controller.RegisterCallbackForPinValueChangedEvent(_pinNumber, PinEventTypes.Falling | PinEventTypes.Rising, value);
}

remove
{
_driver.RemoveCallbackForPinValueChangedEvent(_pinNumber, value);
_controller.UnregisterCallbackForPinValueChangedEvent(_pinNumber, value);
}
}

/// <summary>
/// Toggles the output of the general purpose I/O (GPIO) pin if the pin is configured as an output.
/// </summary>
public virtual void Toggle() => _driver.Toggle(_pinNumber);
public virtual void Toggle() => _controller.Toggle(_pinNumber);

/// <inheritdoc />
public virtual bool Equals(GpioPin? other)
{
if (other == null)
{
return false;
}

return PinNumber == other.PinNumber && ReferenceEquals(_controller, other._controller);
}

/// <inheritdoc />
public override bool Equals(object? obj)
{
if (obj == null)
{
return false;
}

if (ReferenceEquals(this, obj))
{
return true;
}

if (obj is GpioPin other)
{
return Equals(other);
}

return false;
}

/// <inheritdoc />
public override int GetHashCode()
{
return _controller.GetHashCode() ^ _pinNumber;
}

/// <summary>
/// Closes the pin
/// </summary>
public virtual void Close()
{
// Avoid an exception when called multiple times
if (_controller.IsPinOpen(_pinNumber))
{
_controller.ClosePin(_pinNumber);
}
}

/// <summary>
/// Disposes (closes) the pin
/// </summary>
/// <param name="disposing">Should always be true</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Close();
}
}

/// <summary>
/// Standard Dispose pattern
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Loading