// 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.Threading;
using System.Threading.Tasks;
namespace System.Device.Gpio
{
///
/// Represents a general-purpose I/O (GPIO) controller.
///
public sealed partial class GpioController : IDisposable
{
private readonly GpioDriver _driver;
private readonly HashSet _openPins;
///
/// Initializes a new instance of the class that will use the logical pin numbering scheme as default.
///
public GpioController()
: this(PinNumberingScheme.Logical)
{
}
///
/// Initializes a new instance of the class that will use the specified numbering scheme and driver.
///
/// The numbering scheme used to represent pins provided by the controller.
/// The driver that manages all of the pin operations for the controller.
public GpioController(PinNumberingScheme numberingScheme, GpioDriver driver)
{
_driver = driver;
NumberingScheme = numberingScheme;
_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;
///
/// Gets the logical pin number in the controller's numbering scheme.
///
/// The pin number in the controller's numbering scheme.
/// The logical pin number in the controller's numbering scheme.
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);
if (_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("The selected pin is already open.");
}
_driver.OpenPin(logicalPinNumber);
_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);
if (!_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("Can not close a pin that is not open.");
}
_driver.ClosePin(logicalPinNumber);
_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);
if (!_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("Can not set a mode to a pin that is not open.");
}
if (!_driver.IsPinModeSupported(logicalPinNumber, mode))
{
throw new InvalidOperationException("The pin does not support the selected 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);
if (!_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("Can not get the mode of a pin that is not open.");
}
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);
if (!_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("Can not read from a pin that is not open.");
}
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);
if (!_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("Can not write to a pin that is not open.");
}
if (_driver.GetPinMode(logicalPinNumber) != PinMode.Output)
{
throw new InvalidOperationException("Can not write to a pin that is not set to Output mode.");
}
_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))
{
return WaitForEvent(pinNumber, eventTypes, tokenSource.Token);
}
}
///
/// 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);
if (!_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("Can not wait for events from a pin that is not open.");
}
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))
{
return await WaitForEventAsync(pinNumber, eventTypes, tokenSource.Token).ConfigureAwait(false);
}
}
///
/// 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);
if (!_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("Can not wait for events from a pin that is not open.");
}
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);
if (!_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("Can not add callback for a pin that is not open.");
}
_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);
if (!_openPins.Contains(logicalPinNumber))
{
throw new InvalidOperationException("Can not add callback for a pin that is not open.");
}
_driver.RemoveCallbackForPinValueChangedEvent(logicalPinNumber, callback);
}
private void Dispose(bool disposing)
{
foreach (int pin in _openPins)
{
_driver.ClosePin(pin);
}
_openPins.Clear();
_driver.Dispose();
}
///
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++)
{
Write(pinValuePairs[i].PinNumber, pinValuePairs[i].PinValue);
}
}
///
/// 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++)
{
int pin = pinValuePairs[i].PinNumber;
pinValuePairs[i] = new PinValuePair(pin, Read(pin));
}
}
}
}