using Unosquare.RaspberryIO.Native;
using Unosquare.Swan;
using Unosquare.Swan.Abstractions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace Unosquare.RaspberryIO.Gpio {
///
/// Represents a singleton of the Raspberry Pi GPIO controller
/// as an IReadOnlyCollection of GpioPins
/// Low level operations are accomplished by using the Wiring Pi library.
/// Use the Instance property to access the singleton's instance
///
public sealed class GpioController : SingletonBase, IReadOnlyCollection {
#region Private Declarations
private const String WiringPiCodesEnvironmentVariable = "WIRINGPI_CODES";
private static readonly Object SyncRoot = new Object();
private readonly Dictionary _pinsByWiringPiPinNumber = new Dictionary();
#endregion
#region Constructors and Initialization
///
/// Prevents a default instance of the class from being created.
/// It in turn initializes the controller and registers the pin -- in that order.
///
/// Unable to initialize the GPIO controller.
private GpioController() {
if(this.Pins != null) {
return;
}
if(IsInitialized == false) {
Boolean initResult = this.Initialize(ControllerMode.DirectWithWiringPiPins);
if(initResult == false) {
throw new Exception("Unable to initialize the GPIO controller.");
}
}
#region Pin Registration (32 WiringPi Pins)
this.RegisterPin(GpioPin.Pin00.Value);
this.RegisterPin(GpioPin.Pin01.Value);
this.RegisterPin(GpioPin.Pin02.Value);
this.RegisterPin(GpioPin.Pin03.Value);
this.RegisterPin(GpioPin.Pin04.Value);
this.RegisterPin(GpioPin.Pin05.Value);
this.RegisterPin(GpioPin.Pin06.Value);
this.RegisterPin(GpioPin.Pin07.Value);
this.RegisterPin(GpioPin.Pin08.Value);
this.RegisterPin(GpioPin.Pin09.Value);
this.RegisterPin(GpioPin.Pin10.Value);
this.RegisterPin(GpioPin.Pin11.Value);
this.RegisterPin(GpioPin.Pin12.Value);
this.RegisterPin(GpioPin.Pin13.Value);
this.RegisterPin(GpioPin.Pin14.Value);
this.RegisterPin(GpioPin.Pin15.Value);
this.RegisterPin(GpioPin.Pin16.Value);
this.RegisterPin(GpioPin.Pin17.Value);
this.RegisterPin(GpioPin.Pin18.Value);
this.RegisterPin(GpioPin.Pin19.Value);
this.RegisterPin(GpioPin.Pin20.Value);
this.RegisterPin(GpioPin.Pin21.Value);
this.RegisterPin(GpioPin.Pin22.Value);
this.RegisterPin(GpioPin.Pin23.Value);
this.RegisterPin(GpioPin.Pin24.Value);
this.RegisterPin(GpioPin.Pin25.Value);
this.RegisterPin(GpioPin.Pin26.Value);
this.RegisterPin(GpioPin.Pin27.Value);
this.RegisterPin(GpioPin.Pin28.Value);
this.RegisterPin(GpioPin.Pin29.Value);
this.RegisterPin(GpioPin.Pin30.Value);
this.RegisterPin(GpioPin.Pin31.Value);
#endregion
this.Pins = new ReadOnlyCollection(this._pinsByWiringPiPinNumber.Values.ToArray());
Dictionary headerP1 = new Dictionary(this.Pins.Count);
Dictionary headerP5 = new Dictionary(this.Pins.Count);
foreach(GpioPin pin in this.Pins) {
Dictionary target = pin.Header == GpioHeader.P1 ? headerP1 : headerP5;
target[pin.HeaderPinNumber] = pin;
}
this.HeaderP1 = new ReadOnlyDictionary(headerP1);
this.HeaderP5 = new ReadOnlyDictionary(headerP5);
}
///
/// Determines if the underlying GPIO controller has been initialized properly.
///
///
/// true if the controller is properly initialized; otherwise, false.
///
public static Boolean IsInitialized {
get {
lock(SyncRoot) {
return Mode != ControllerMode.NotInitialized;
}
}
}
///
/// Gets the number of registered pins in the controller.
///
public Int32 Count => this.Pins.Count;
#endregion
#region Pin Addressing
///
/// Gets the PWM base frequency (in Hz).
///
public Int32 PwmBaseFrequency => 19200000;
///
/// Gets a red-only collection of all registered pins.
///
public ReadOnlyCollection Pins {
get;
}
///
/// Provides all the pins on Header P1 of the Pi as a lookup by physical header pin number.
/// This header is the main header and it is the one commonly used.
///
public ReadOnlyDictionary HeaderP1 {
get;
}
///
/// Provides all the pins on Header P5 of the Pi as a lookup by physical header pin number.
/// This header is the secondary header and it is rarely used.
///
public ReadOnlyDictionary HeaderP5 {
get;
}
#endregion
#region Individual Pin Properties
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 00.
///
public GpioPin Pin00 => GpioPin.Pin00.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 01.
///
public GpioPin Pin01 => GpioPin.Pin01.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 02.
///
public GpioPin Pin02 => GpioPin.Pin02.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 03.
///
public GpioPin Pin03 => GpioPin.Pin03.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 04.
///
public GpioPin Pin04 => GpioPin.Pin04.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 05.
///
public GpioPin Pin05 => GpioPin.Pin05.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 06.
///
public GpioPin Pin06 => GpioPin.Pin06.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 07.
///
public GpioPin Pin07 => GpioPin.Pin07.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 08.
///
public GpioPin Pin08 => GpioPin.Pin08.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 09.
///
public GpioPin Pin09 => GpioPin.Pin09.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 10.
///
public GpioPin Pin10 => GpioPin.Pin10.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 11.
///
public GpioPin Pin11 => GpioPin.Pin11.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 12.
///
public GpioPin Pin12 => GpioPin.Pin12.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 13.
///
public GpioPin Pin13 => GpioPin.Pin13.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 14.
///
public GpioPin Pin14 => GpioPin.Pin14.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 15.
///
public GpioPin Pin15 => GpioPin.Pin15.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 16.
///
public GpioPin Pin16 => GpioPin.Pin16.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 17.
///
public GpioPin Pin17 => GpioPin.Pin17.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 18.
///
public GpioPin Pin18 => GpioPin.Pin18.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 19.
///
public GpioPin Pin19 => GpioPin.Pin19.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 20.
///
public GpioPin Pin20 => GpioPin.Pin20.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 21.
///
public GpioPin Pin21 => GpioPin.Pin21.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 22.
///
public GpioPin Pin22 => GpioPin.Pin22.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 23.
///
public GpioPin Pin23 => GpioPin.Pin23.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 24.
///
public GpioPin Pin24 => GpioPin.Pin24.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 25.
///
public GpioPin Pin25 => GpioPin.Pin25.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 26.
///
public GpioPin Pin26 => GpioPin.Pin26.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 27.
///
public GpioPin Pin27 => GpioPin.Pin27.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 28.
///
public GpioPin Pin28 => GpioPin.Pin28.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 29.
///
public GpioPin Pin29 => GpioPin.Pin29.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 30.
///
public GpioPin Pin30 => GpioPin.Pin30.Value;
///
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 31.
///
public GpioPin Pin31 => GpioPin.Pin31.Value;
#endregion
#region Indexers
///
/// Gets or sets the initialization mode.
///
private static ControllerMode Mode { get; set; } = ControllerMode.NotInitialized;
///
/// Gets the with the specified Wiring Pi pin number.
///
///
/// The .
///
/// The pin number.
/// A reference to the GPIO pin
public GpioPin this[WiringPiPin pinNumber] => this._pinsByWiringPiPinNumber[pinNumber];
///
/// Gets the with the specified pin number.
///
///
/// The .
///
/// The pin number.
/// A reference to the GPIO pin
public GpioPin this[P1 pinNumber] => this.HeaderP1[(Int32)pinNumber];
///
/// Gets the with the specified pin number.
///
///
/// The .
///
/// The pin number.
/// A reference to the GPIO pin
public GpioPin this[P5 pinNumber] => this.HeaderP5[(Int32)pinNumber];
///
/// Gets the with the specified Wiring Pi pin number.
/// Use the HeaderP1 and HeaderP5 lookups if you would like to retrieve pins by physical pin number.
///
///
/// The .
///
/// The pin number as defined by Wiring Pi. This is not the header pin number as pin number in headers are obvoisly repeating.
/// A reference to the GPIO pin
/// When the pin index is not found
public GpioPin this[Int32 wiringPiPinNumber] {
get {
if(Enum.IsDefined(typeof(WiringPiPin), wiringPiPinNumber) == false) {
throw new IndexOutOfRangeException($"Pin {wiringPiPinNumber} is not registered in the GPIO controller.");
}
return this._pinsByWiringPiPinNumber[(WiringPiPin)wiringPiPinNumber];
}
}
#endregion
#region Pin Group Methods (Read, Write, Pad Drive)
///
/// This sets the “strength” of the pad drivers for a particular group of pins.
/// There are 3 groups of pins and the drive strength is from 0 to 7.
/// Do not use this unless you know what you are doing.
///
/// The group.
/// The value.
public void SetPadDrive(Int32 group, Int32 value) {
lock(SyncRoot) {
_ = WiringPi.SetPadDrive(group, value);
}
}
///
/// This sets the “strength” of the pad drivers for a particular group of pins.
/// There are 3 groups of pins and the drive strength is from 0 to 7.
/// Do not use this unless you know what you are doing.
///
/// The group.
/// The value.
/// The awaitable task
public Task SetPadDriveAsync(Int32 group, Int32 value) => Task.Run(() => this.SetPadDrive(group, value));
///
/// This writes the 8-bit byte supplied to the first 8 GPIO pins.
/// It’s the fastest way to set all 8 bits at once to a particular value,
/// although it still takes two write operations to the Pi’s GPIO hardware.
///
/// The value.
/// PinMode
public void WriteByte(Byte value) {
lock(SyncRoot) {
if(this.Skip(0).Take(8).Any(p => p.PinMode != GpioPinDriveMode.Output)) {
throw new InvalidOperationException(
$"All firts 8 pins (0 to 7) need their {nameof(GpioPin.PinMode)} to be set to {GpioPinDriveMode.Output}");
}
WiringPi.DigitalWriteByte(value);
}
}
///
/// This writes the 8-bit byte supplied to the first 8 GPIO pins.
/// It’s the fastest way to set all 8 bits at once to a particular value,
/// although it still takes two write operations to the Pi’s GPIO hardware.
///
/// The value.
/// The awaitable task
public Task WriteByteAsync(Byte value) => Task.Run(() => this.WriteByte(value));
///
/// This reads the 8-bit byte supplied to the first 8 GPIO pins.
/// It’s the fastest way to get all 8 bits at once to a particular value.
/// Please note this function is undocumented and unsopported
///
/// A byte from the GPIO
/// PinMode
public Byte ReadByte() {
lock(SyncRoot) {
if(this.Skip(0).Take(8).Any(p =>
p.PinMode != GpioPinDriveMode.Input && p.PinMode != GpioPinDriveMode.Output)) {
throw new InvalidOperationException(
$"All firts 8 pins (0 to 7) need their {nameof(GpioPin.PinMode)} to be set to {GpioPinDriveMode.Input} or {GpioPinDriveMode.Output}");
}
return (Byte)WiringPi.DigitalReadByte();
}
}
///
/// This reads the 8-bit byte supplied to the first 8 GPIO pins.
/// It’s the fastest way to get all 8 bits at once to a particular value.
/// Please note this function is undocumented and unsopported
///
/// A byte from the GPIO
public Task ReadByteAsync() => Task.Run(() => this.ReadByte());
#endregion
#region IReadOnlyCollection Implementation
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through the collection.
///
public IEnumerator GetEnumerator() => this.Pins.GetEnumerator();
///
/// Returns an enumerator that iterates through the collection.
///
///
/// An object that can be used to iterate through the collection.
///
IEnumerator IEnumerable.GetEnumerator() => this.Pins.GetEnumerator();
#endregion
#region Helper and Init Methods
///
/// Gets the GPIO pin by BCM pin number.
///
/// The BCM pin number.
/// The GPIO pin
public GpioPin GetGpioPinByBcmPinNumber(Int32 bcmPinNumber) => this.First(pin => pin.BcmPinNumber == bcmPinNumber);
///
/// Converts the Wirings Pi pin number to the BCM pin number.
///
/// The wiring pi pin number.
/// The converted pin
internal static Int32 WiringPiToBcmPinNumber(Int32 wiringPiPinNumber) {
lock(SyncRoot) {
return WiringPi.WpiPinToGpio(wiringPiPinNumber);
}
}
///
/// Converts the Physical (Header) pin number to BCM pin number.
///
/// The header pin number.
/// The converted pin
internal static Int32 HaderToBcmPinNumber(Int32 headerPinNumber) {
lock(SyncRoot) {
return WiringPi.PhysPinToGpio(headerPinNumber);
}
}
///
/// Short-hand method of registering pins
///
/// The pin.
private void RegisterPin(GpioPin pin) {
if(this._pinsByWiringPiPinNumber.ContainsKey(pin.WiringPiPinNumber) == false) {
this._pinsByWiringPiPinNumber[pin.WiringPiPinNumber] = pin;
} else {
throw new InvalidOperationException($"Pin {pin.WiringPiPinNumber} has been registered");
}
}
///
/// Initializes the controller given the initialization mode and pin numbering scheme
///
/// The mode.
/// True when successful.
///
/// This library does not support the platform
///
/// Library was already Initialized
/// The init mode is invalid
private Boolean Initialize(ControllerMode mode) {
if(Runtime.OS != Swan.OperatingSystem.Unix) {
throw new PlatformNotSupportedException("This library does not support the platform");
}
lock(SyncRoot) {
if(IsInitialized) {
throw new InvalidOperationException($"Cannot call {nameof(Initialize)} more than once.");
}
Environment.SetEnvironmentVariable(WiringPiCodesEnvironmentVariable, "1", EnvironmentVariableTarget.Process);
Int32 setpuResult;
switch(mode) {
case ControllerMode.DirectWithWiringPiPins: {
setpuResult = WiringPi.WiringPiSetup();
break;
}
case ControllerMode.DirectWithBcmPins: {
setpuResult = WiringPi.WiringPiSetupGpio();
break;
}
case ControllerMode.DirectWithHeaderPins: {
setpuResult = WiringPi.WiringPiSetupPhys();
break;
}
case ControllerMode.FileStreamWithHardwarePins: {
setpuResult = WiringPi.WiringPiSetupSys();
break;
}
default: {
throw new ArgumentException($"'{mode}' is not a valid initialization mode.");
}
}
Mode = setpuResult == 0 ? mode : ControllerMode.NotInitialized;
return IsInitialized;
}
}
#endregion
}
}