diff --git a/src/System.Device.Gpio.Tests/GpioControllerTestBase.cs b/src/System.Device.Gpio.Tests/GpioControllerTestBase.cs index a11c8a3e32..f45d662e27 100644 --- a/src/System.Device.Gpio.Tests/GpioControllerTestBase.cs +++ b/src/System.Device.Gpio.Tests/GpioControllerTestBase.cs @@ -91,6 +91,30 @@ public void IsPinOpenOnInputTest() } } + [Fact] + public void SharedControllerManagesItsOwnPinsTest() + { + using (GpioController controller = new GpioController(GetTestNumberingScheme(), GetTestDriver())) + { + // Open pin in input mode (default) + Assert.False(controller.IsPinOpen(LedPin)); + controller.OpenPin(LedPin, PinMode.Input); + Assert.True(controller.IsPinOpen(LedPin)); + + var sharedController = new SharedGpioController(controller); + sharedController.OpenPin(10, PinMode.Input); + Assert.True(sharedController.IsPinOpen(10)); + Assert.True(controller.IsPinOpen(10)); + + sharedController.ClosePin(10); + Assert.False(sharedController.IsPinOpen(10)); + Assert.False(controller.IsPinOpen(10)); + + controller.ClosePin(LedPin); + Assert.False(controller.IsPinOpen(LedPin)); + } + } + [Fact] public void IsPinOpenOnOutputTest() { @@ -112,6 +136,21 @@ public void IsPinOpenOnOutputTest() } } + [Fact] + public void CanClosePinsViaDisposableTest() + { + using (GpioController controller = new GpioController(GetTestNumberingScheme(), GetTestDriver())) + { + Assert.False(controller.IsPinOpen(LedPin)); + + var openPin = controller.OpenPinAsDisposable(LedPin, PinMode.Output); + Assert.True(controller.IsPinOpen(LedPin)); + + openPin.Dispose(); + Assert.False(controller.IsPinOpen(LedPin)); + } + } + [Fact] public void ThrowsIfWritingOnInputPin() { @@ -270,6 +309,52 @@ void Callback(object sender, PinValueChangedEventArgs e) } } + [Fact] + public void AddCallbackDisposeCallbackTest() + { + int risingEventOccurredCount = 0, fallingEventOccurredCount = 0; + using (GpioController controller = new GpioController(GetTestNumberingScheme(), GetTestDriver())) + { + controller.OpenPin(InputPin, PinMode.Input); + controller.OpenPin(OutputPin, PinMode.Output); + controller.Write(OutputPin, PinValue.Low); + + controller.RegisterCallbackForPinValueChangedEvent(InputPin, PinEventTypes.Rising, (o, e) => + { + risingEventOccurredCount++; + }); + var callbackToDispose = controller.RegisterCallbackForPinValueChangedEventAsDisposable(InputPin, PinEventTypes.Rising, Callback); + controller.RegisterCallbackForPinValueChangedEvent(InputPin, PinEventTypes.Rising, (o, e) => + { + risingEventOccurredCount++; + if (fallingEventOccurredCount == 4) + { + callbackToDispose.Dispose(); + } + }); + controller.RegisterCallbackForPinValueChangedEvent(InputPin, PinEventTypes.Falling, (o, e) => + { + fallingEventOccurredCount++; + }); + + for (int i = 0; i < 10; i++) + { + controller.Write(OutputPin, PinValue.High); + Thread.Sleep(WaitMilliseconds); + controller.Write(OutputPin, PinValue.Low); + Thread.Sleep(WaitMilliseconds); + } + + Assert.Equal(25, risingEventOccurredCount); + Assert.Equal(10, fallingEventOccurredCount); + + void Callback(object sender, PinValueChangedEventArgs e) + { + risingEventOccurredCount++; + } + } + } + [Fact] public void AddCallbackRemoveAllCallbackTest() { diff --git a/src/System.Device.Gpio/System/Device/Gpio/GpioController.cs b/src/System.Device.Gpio/System/Device/Gpio/GpioController.cs index 190bbee0a1..5dba50e798 100644 --- a/src/System.Device.Gpio/System/Device/Gpio/GpioController.cs +++ b/src/System.Device.Gpio/System/Device/Gpio/GpioController.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Device.Gpio.Drivers; +using System.Disposables; using System.IO; using System.Text.RegularExpressions; using System.Threading; @@ -15,7 +16,7 @@ namespace System.Device.Gpio /// /// Represents a general-purpose I/O (GPIO) controller. /// - public sealed class GpioController : IDisposable + public sealed class GpioController : IGpioController { // Constants used to check the hardware on linux private const string CpuInfoPath = "/proc/cpuinfo"; @@ -52,14 +53,10 @@ public GpioController(PinNumberingScheme numberingScheme, GpioDriver driver) _openPins = new HashSet(); } - /// - /// The numbering scheme used to represent pins provided by the controller. - /// + /// public PinNumberingScheme NumberingScheme { get; } - /// - /// The number of pins provided by the controller. - /// + /// public int PinCount => _driver.PinCount; /// @@ -72,10 +69,7 @@ private int GetLogicalPinNumber(int pinNumber) return (NumberingScheme == PinNumberingScheme.Logical) ? pinNumber : _driver.ConvertPinNumberToLogicalNumberingScheme(pinNumber); } - /// - /// Opens a pin in order for it to be ready to use. - /// - /// The pin number in the controller's numbering scheme. + /// public void OpenPin(int pinNumber) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -88,21 +82,14 @@ public void OpenPin(int pinNumber) _openPins.Add(logicalPinNumber); } - /// - /// Opens a pin and sets it to a specific mode. - /// - /// The pin number in the controller's numbering scheme. - /// The mode to be set. + /// public void OpenPin(int pinNumber, PinMode mode) { OpenPin(pinNumber); SetPinMode(pinNumber, mode); } - /// - /// Closes an open pin. - /// - /// The pin number in the controller's numbering scheme. + /// public void ClosePin(int pinNumber) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -115,11 +102,7 @@ public void ClosePin(int pinNumber) _openPins.Remove(logicalPinNumber); } - /// - /// Sets the mode to a pin. - /// - /// The pin number in the controller's numbering scheme. - /// The mode to be set. + /// public void SetPinMode(int pinNumber, PinMode mode) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -136,11 +119,7 @@ public void SetPinMode(int pinNumber, PinMode mode) _driver.SetPinMode(logicalPinNumber, mode); } - /// - /// Gets the mode of a pin. - /// - /// The pin number in the controller's numbering scheme. - /// The mode of the pin. + /// public PinMode GetPinMode(int pinNumber) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -152,34 +131,21 @@ public PinMode GetPinMode(int pinNumber) return _driver.GetPinMode(logicalPinNumber); } - /// - /// Checks if a specific pin is open. - /// - /// The pin number in the controller's numbering scheme. - /// The status if the pin is open or closed. + /// public bool IsPinOpen(int pinNumber) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); return _openPins.Contains(logicalPinNumber); } - /// - /// Checks if a pin supports a specific mode. - /// - /// The pin number in the controller's numbering scheme. - /// The mode to check. - /// The status if the pin supports the mode. + /// public bool IsPinModeSupported(int pinNumber, PinMode mode) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); return _driver.IsPinModeSupported(logicalPinNumber, mode); } - /// - /// Reads the current value of a pin. - /// - /// The pin number in the controller's numbering scheme. - /// The value of the pin. + /// public PinValue Read(int pinNumber) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -191,11 +157,7 @@ public PinValue Read(int pinNumber) return _driver.Read(logicalPinNumber); } - /// - /// Writes a value to a pin. - /// - /// The pin number in the controller's numbering scheme. - /// The value to be written to the pin. + /// public void Write(int pinNumber, PinValue value) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -212,13 +174,7 @@ public void Write(int pinNumber, PinValue value) _driver.Write(logicalPinNumber, value); } - /// - /// Blocks execution until an event of type eventType is received or a period of time has expired. - /// - /// The pin number in the controller's numbering scheme. - /// The event types to wait for. - /// The time to wait for the event. - /// A structure that contains the result of the waiting operation. + /// public WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, TimeSpan timeout) { using (CancellationTokenSource tokenSource = new CancellationTokenSource(timeout)) @@ -227,13 +183,7 @@ public WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, } } - /// - /// Blocks execution until an event of type eventType is received or a cancellation is requested. - /// - /// The pin number in the controller's numbering scheme. - /// The event types to wait for. - /// The cancellation token of when the operation should stop waiting for an event. - /// A structure that contains the result of the waiting operation. + /// public WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -245,13 +195,7 @@ public WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, return _driver.WaitForEvent(logicalPinNumber, eventTypes, cancellationToken); } - /// - /// Async call to wait until an event of type eventType is received or a period of time has expired. - /// - /// The pin number in the controller's numbering scheme. - /// The event types to wait for. - /// The time to wait for the event. - /// A task representing the operation of getting the structure that contains the result of the waiting operation. + /// public async ValueTask WaitForEventAsync(int pinNumber, PinEventTypes eventTypes, TimeSpan timeout) { using (CancellationTokenSource tokenSource = new CancellationTokenSource(timeout)) @@ -260,13 +204,7 @@ public async ValueTask WaitForEventAsync(int pinNumber, PinE } } - /// - /// Async call until an event of type eventType is received or a cancellation is requested. - /// - /// The pin number in the controller's numbering scheme. - /// The event types to wait for. - /// The cancellation token of when the operation should stop waiting for an event. - /// A task representing the operation of getting the structure that contains the result of the waiting operation + /// public ValueTask WaitForEventAsync(int pinNumber, PinEventTypes eventTypes, CancellationToken token) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -278,12 +216,7 @@ public ValueTask WaitForEventAsync(int pinNumber, PinEventTy return _driver.WaitForEventAsync(logicalPinNumber, eventTypes, token); } - /// - /// Adds a callback that will be invoked when pinNumber has an event of type eventType. - /// - /// The pin number in the controller's numbering scheme. - /// The event types to wait for. - /// The callback method that will be invoked. + /// public void RegisterCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes eventTypes, PinChangeEventHandler callback) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -295,11 +228,7 @@ public void RegisterCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes _driver.AddCallbackForPinValueChangedEvent(logicalPinNumber, eventTypes, callback); } - /// - /// Removes a callback that was being invoked for pin at pinNumber. - /// - /// The pin number in the controller's numbering scheme. - /// The callback method that will be invoked. + /// public void UnregisterCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback) { int logicalPinNumber = GetLogicalPinNumber(pinNumber); @@ -328,10 +257,7 @@ public void Dispose() Dispose(true); } - /// - /// Write the given pins with the given values. - /// - /// The pin/value pairs to write. + /// public void Write(ReadOnlySpan pinValuePairs) { for (int i = 0; i < pinValuePairs.Length; i++) @@ -340,10 +266,7 @@ public void Write(ReadOnlySpan pinValuePairs) } } - /// - /// Read the given pins with the given pin numbers. - /// - /// The pin/value pairs to read. + /// public void Read(Span pinValuePairs) { for (int i = 0; i < pinValuePairs.Length; i++) diff --git a/src/System.Device.Gpio/System/Device/Gpio/IGpioController.cs b/src/System.Device.Gpio/System/Device/Gpio/IGpioController.cs new file mode 100644 index 0000000000..7dd2d2f131 --- /dev/null +++ b/src/System.Device.Gpio/System/Device/Gpio/IGpioController.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; + +namespace System.Device.Gpio +{ + /// + /// Represents the interface of a general-purpose I/O (GPIO) controller. + /// + public interface IGpioController : IDisposable + { + /// + /// The numbering scheme used to represent pins provided by the controller. + /// + PinNumberingScheme NumberingScheme { get; } + + /// + /// The number of pins provided by the controller. + /// + int PinCount { get; } + + /// + /// Opens a pin in order for it to be ready to use. + /// + /// The pin number in the controller's numbering scheme. + void OpenPin(int pinNumber); + + /// + /// Opens a pin and sets it to a specific mode. + /// + /// The pin number in the controller's numbering scheme. + /// The mode to be set. + void OpenPin(int pinNumber, PinMode mode); + + /// + /// Closes an open pin. + /// + /// The pin number in the controller's numbering scheme. + void ClosePin(int pinNumber); + + /// + /// Sets the mode to a pin. + /// + /// The pin number in the controller's numbering scheme. + /// The mode to be set. + void SetPinMode(int pinNumber, PinMode mode); + + /// + /// Gets the mode of a pin. + /// + /// The pin number in the controller's numbering scheme. + /// The mode of the pin. + PinMode GetPinMode(int pinNumber); + + /// + /// Checks if a specific pin is open. + /// + /// The pin number in the controller's numbering scheme. + /// The status if the pin is open or closed. + bool IsPinOpen(int pinNumber); + + /// + /// Checks if a pin supports a specific mode. + /// + /// The pin number in the controller's numbering scheme. + /// The mode to check. + /// The status if the pin supports the mode. + bool IsPinModeSupported(int pinNumber, PinMode mode); + + /// + /// Reads the current value of a pin. + /// + /// The pin number in the controller's numbering scheme. + /// The value of the pin. + PinValue Read(int pinNumber); + + /// + /// Writes a value to a pin. + /// + /// The pin number in the controller's numbering scheme. + /// The value to be written to the pin. + void Write(int pinNumber, PinValue value); + + /// + /// Blocks execution until an event of type eventType is received or a period of time has expired. + /// + /// The pin number in the controller's numbering scheme. + /// The event types to wait for. + /// The time to wait for the event. + /// A structure that contains the result of the waiting operation. + WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, TimeSpan timeout); + + /// + /// Blocks execution until an event of type eventType is received or a cancellation is requested. + /// + /// The pin number in the controller's numbering scheme. + /// The event types to wait for. + /// The cancellation token of when the operation should stop waiting for an event. + /// A structure that contains the result of the waiting operation. + WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken); + + /// + /// Async call to wait until an event of type eventType is received or a period of time has expired. + /// + /// The pin number in the controller's numbering scheme. + /// The event types to wait for. + /// The time to wait for the event. + /// A task representing the operation of getting the structure that contains the result of the waiting operation. + ValueTask WaitForEventAsync(int pinNumber, PinEventTypes eventTypes, TimeSpan timeout); + + /// + /// Async call until an event of type eventType is received or a cancellation is requested. + /// + /// The pin number in the controller's numbering scheme. + /// The event types to wait for. + /// The cancellation token of when the operation should stop waiting for an event. + /// A task representing the operation of getting the structure that contains the result of the waiting operation + ValueTask WaitForEventAsync(int pinNumber, PinEventTypes eventTypes, CancellationToken token); + + /// + /// Adds a callback that will be invoked when pinNumber has an event of type eventType. + /// + /// The pin number in the controller's numbering scheme. + /// The event types to wait for. + /// The callback method that will be invoked. + void RegisterCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes eventTypes, PinChangeEventHandler callback); + + /// + /// Removes a callback that was being invoked for pin at pinNumber. + /// + /// The pin number in the controller's numbering scheme. + /// The callback method that will be invoked. + void UnregisterCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback); + + /// + /// Write the given pins with the given values. + /// + /// The pin/value pairs to write. + void Write(ReadOnlySpan pinValuePairs); + + /// + /// Read the given pins with the given pin numbers. + /// + /// The pin/value pairs to read. + void Read(Span pinValuePairs); + } +} diff --git a/src/System.Device.Gpio/System/Device/Gpio/IGpioControllerExtensions.cs b/src/System.Device.Gpio/System/Device/Gpio/IGpioControllerExtensions.cs new file mode 100644 index 0000000000..682e92ccca --- /dev/null +++ b/src/System.Device.Gpio/System/Device/Gpio/IGpioControllerExtensions.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Disposables; + +namespace System.Device.Gpio +{ + /// + /// Extensions for interface. + /// + public static class IGpioControllerExtensions + { + /// + /// Opens a pin in order for it to be ready to use. + /// + /// The GPioController. + /// The pin number in the controller's numbering scheme. + /// A disposable that will close the pin if disposed. + public static IDisposable OpenPinAsDisposable(this IGpioController controller, int pinNumber) + { + controller.OpenPin(pinNumber); + return Disposable.Create(() => controller.ClosePin(pinNumber)); + } + + /// + /// Opens a pin and sets it to a specific mode. + /// + /// The GPioController. + /// The pin number in the controller's numbering scheme. + /// The mode to be set. + /// A disposable that will close the pin if disposed. + public static IDisposable OpenPinAsDisposable(this IGpioController controller, int pinNumber, PinMode mode) + { + controller.OpenPin(pinNumber, mode); + return Disposable.Create(() => controller.ClosePin(pinNumber)); + } + + /// + /// Adds a callback that will be invoked when pinNumber has an event of type eventType. + /// + /// The GPioController. + /// The pin number in the controller's numbering scheme. + /// The event types to wait for. + /// The callback method that will be invoked. + /// A disposable object that will remove the added callback + public static IDisposable RegisterCallbackForPinValueChangedEventAsDisposable(this IGpioController controller, int pinNumber, + PinEventTypes eventTypes, PinChangeEventHandler callback) + { + controller.RegisterCallbackForPinValueChangedEvent(pinNumber, eventTypes, callback); + return Disposable.Create(() => controller.UnregisterCallbackForPinValueChangedEvent(pinNumber, callback)); + } + } +} diff --git a/src/System.Device.Gpio/System/Device/Gpio/SharedGpioController.cs b/src/System.Device.Gpio/System/Device/Gpio/SharedGpioController.cs new file mode 100644 index 0000000000..cbb921166f --- /dev/null +++ b/src/System.Device.Gpio/System/Device/Gpio/SharedGpioController.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Disposables; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Device.Gpio +{ + /// + /// Represents a shared view of general-purpose I/O (GPIO) controller. + /// + public sealed class SharedGpioController : IGpioController + { + private readonly List _openPins = new List(); + private readonly IGpioController _controller; + + /// + /// Initializes a new instance of the class that will use the logical pin numbering scheme as default. + /// + public SharedGpioController(IGpioController controller) + { + _controller = controller; + } + + /// + public void Dispose() + { + foreach (var openPin in _openPins) + { + _controller.ClosePin(openPin); + } + + _openPins.Clear(); + } + + /// + public PinNumberingScheme NumberingScheme => _controller.NumberingScheme; + + /// + public int PinCount => _controller.PinCount; + + /// + public void OpenPin(int pinNumber) + { + _controller.OpenPin(pinNumber); + _openPins.Add(pinNumber); + } + + /// + public void OpenPin(int pinNumber, PinMode mode) + { + _controller.OpenPin(pinNumber, mode); + _openPins.Add(pinNumber); + } + + /// + public void ClosePin(int pinNumber) + { + if (_openPins.Remove(pinNumber)) + { + _controller.ClosePin(pinNumber); + } + } + + /// + public void SetPinMode(int pinNumber, PinMode mode) + { + _controller.SetPinMode(pinNumber, mode); + } + + /// + public PinMode GetPinMode(int pinNumber) + { + return _controller.GetPinMode(pinNumber); + } + + /// + public bool IsPinOpen(int pinNumber) + { + return _openPins.Contains(pinNumber); + } + + /// + public bool IsPinModeSupported(int pinNumber, PinMode mode) + { + return _controller.IsPinModeSupported(pinNumber, mode); + } + + /// + public PinValue Read(int pinNumber) + { + return _controller.Read(pinNumber); + } + + /// + public void Write(int pinNumber, PinValue value) + { + _controller.Write(pinNumber, value); + } + + /// + public WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, TimeSpan timeout) + { + return _controller.WaitForEvent(pinNumber, eventTypes, timeout); + } + + /// + public WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken) + { + return _controller.WaitForEvent(pinNumber, eventTypes, cancellationToken); + } + + /// + public ValueTask WaitForEventAsync(int pinNumber, PinEventTypes eventTypes, TimeSpan timeout) + { + return _controller.WaitForEventAsync(pinNumber, eventTypes, timeout); + } + + /// + public ValueTask WaitForEventAsync(int pinNumber, PinEventTypes eventTypes, CancellationToken token) + { + return _controller.WaitForEventAsync(pinNumber, eventTypes, token); + } + + /// + public void RegisterCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes eventTypes, PinChangeEventHandler callback) + { + _controller.RegisterCallbackForPinValueChangedEvent(pinNumber, eventTypes, callback); + } + + /// + public void UnregisterCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback) + { + _controller.UnregisterCallbackForPinValueChangedEvent(pinNumber, callback); + } + + /// + public void Write(ReadOnlySpan pinValuePairs) + { + _controller.Write(pinValuePairs); + } + + /// + public void Read(Span pinValuePairs) + { + _controller.Read(pinValuePairs); + } + } +} diff --git a/src/System.Device.Gpio/System/Disposables/Disposable.cs b/src/System.Device.Gpio/System/Disposables/Disposable.cs new file mode 100644 index 0000000000..dcd503b1f6 --- /dev/null +++ b/src/System.Device.Gpio/System/Disposables/Disposable.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Disposables +{ + /// + /// Utility to create disposable objects + /// + public static class Disposable + { + private class AnonymousDisposable : IDisposable + { + private readonly Action _onDispose; + + public AnonymousDisposable(Action onDispose) + { + _onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); + } + + public void Dispose() + { + _onDispose(); + } + } + + /// + /// Creates a disposable object. + /// + /// The action to perform on disposal + /// The disposable object + public static IDisposable Create(Action onDispose) + { + return new AnonymousDisposable(onDispose); + } + } +}