namespace Unosquare.WiringPi { using Native; using RaspberryIO.Abstractions; using Swan; using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; /// /// Represents the Raspberry Pi GPIO controller /// as an IReadOnlyCollection of GpioPins. /// /// Low level operations are accomplished by using the Wiring Pi library. /// public sealed class GpioController : IGpioController { #region Private Declarations private const string WiringPiCodesEnvironmentVariable = "WIRINGPI_CODES"; private static readonly object SyncRoot = new object(); private readonly List _pins; #endregion #region Constructors and Initialization /// /// Initializes static members of the class. /// static GpioController() { var wiringPiEdgeDetection = new Dictionary { {EdgeDetection.FallingEdge, 21}, {EdgeDetection.RisingEdge, 1}, {EdgeDetection.FallingAndRisingEdge, 3}, }; WiringPiEdgeDetectionMapping = new ReadOnlyDictionary(wiringPiEdgeDetection); } /// /// Initializes a new instance of the class. /// /// Unable to initialize the GPIO controller. internal GpioController() { if (_pins != null) return; if (IsInitialized == false) { var initResult = Initialize(ControllerMode.DirectWithBcmPins); if (initResult == false) throw new Exception("Unable to initialize the GPIO controller."); } _pins = new List { GpioPin.Pin00.Value, GpioPin.Pin01.Value, GpioPin.Pin02.Value, GpioPin.Pin03.Value, GpioPin.Pin04.Value, GpioPin.Pin05.Value, GpioPin.Pin06.Value, GpioPin.Pin07.Value, GpioPin.Pin08.Value, GpioPin.Pin09.Value, GpioPin.Pin10.Value, GpioPin.Pin11.Value, GpioPin.Pin12.Value, GpioPin.Pin13.Value, GpioPin.Pin14.Value, GpioPin.Pin15.Value, GpioPin.Pin16.Value, GpioPin.Pin17.Value, GpioPin.Pin18.Value, GpioPin.Pin19.Value, GpioPin.Pin20.Value, GpioPin.Pin21.Value, GpioPin.Pin22.Value, GpioPin.Pin23.Value, GpioPin.Pin24.Value, GpioPin.Pin25.Value, GpioPin.Pin26.Value, GpioPin.Pin27.Value, GpioPin.Pin28.Value, GpioPin.Pin29.Value, GpioPin.Pin30.Value, GpioPin.Pin31.Value, }; var headerP1 = new Dictionary(_pins.Count); var headerP5 = new Dictionary(_pins.Count); foreach (var pin in _pins) { if (pin.PhysicalPinNumber < 0) continue; var header = pin.Header == GpioHeader.P1 ? headerP1 : headerP5; header[pin.PhysicalPinNumber] = pin; } HeaderP1 = new ReadOnlyDictionary(headerP1); 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 bool IsInitialized { get { lock (SyncRoot) { return Mode != ControllerMode.NotInitialized; } } } /// /// Gets the wiring pi edge detection mapping. /// internal static ReadOnlyDictionary WiringPiEdgeDetectionMapping { get; } /// /// /// Gets the number of registered pins in the controller. /// public int Count => Pins.Count; /// /// Gets or sets the initialization mode. /// private static ControllerMode Mode { get; set; } = ControllerMode.NotInitialized; #endregion #region Pin Addressing /// /// Gets the PWM base frequency (in Hz). /// public int PwmBaseFrequency => 19200000; /// /// Gets a red-only collection of all pins. /// public ReadOnlyCollection Pins => new ReadOnlyCollection(_pins); /// /// 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 as BCM0. /// public GpioPin Pin00 => GpioPin.Pin00.Value; /// /// Provides direct access to Pin known as BCM1. /// public GpioPin Pin01 => GpioPin.Pin01.Value; /// /// Provides direct access to Pin known as BCM2. /// public GpioPin Pin02 => GpioPin.Pin02.Value; /// /// Provides direct access to Pin known as BCM3. /// public GpioPin Pin03 => GpioPin.Pin03.Value; /// /// Provides direct access to Pin known as BCM4. /// public GpioPin Pin04 => GpioPin.Pin04.Value; /// /// Provides direct access to Pin known as BCM5. /// public GpioPin Pin05 => GpioPin.Pin05.Value; /// /// Provides direct access to Pin known as BCM6. /// public GpioPin Pin06 => GpioPin.Pin06.Value; /// /// Provides direct access to Pin known as BCM7. /// public GpioPin Pin07 => GpioPin.Pin07.Value; /// /// Provides direct access to Pin known as BCM8. /// public GpioPin Pin08 => GpioPin.Pin08.Value; /// /// Provides direct access to Pin known as BCM9. /// public GpioPin Pin09 => GpioPin.Pin09.Value; /// /// Provides direct access to Pin known as BCM10. /// public GpioPin Pin10 => GpioPin.Pin10.Value; /// /// Provides direct access to Pin known as BCM11. /// public GpioPin Pin11 => GpioPin.Pin11.Value; /// /// Provides direct access to Pin known as BCM12. /// public GpioPin Pin12 => GpioPin.Pin12.Value; /// /// Provides direct access to Pin known as BCM13. /// public GpioPin Pin13 => GpioPin.Pin13.Value; /// /// Provides direct access to Pin known as BCM14. /// public GpioPin Pin14 => GpioPin.Pin14.Value; /// /// Provides direct access to Pin known as BCM15. /// public GpioPin Pin15 => GpioPin.Pin15.Value; /// /// Provides direct access to Pin known as BCM16. /// public GpioPin Pin16 => GpioPin.Pin16.Value; /// /// Provides direct access to Pin known as BCM17. /// public GpioPin Pin17 => GpioPin.Pin17.Value; /// /// Provides direct access to Pin known as BCM18. /// public GpioPin Pin18 => GpioPin.Pin18.Value; /// /// Provides direct access to Pin known as BCM19. /// public GpioPin Pin19 => GpioPin.Pin19.Value; /// /// Provides direct access to Pin known as BCM20. /// public GpioPin Pin20 => GpioPin.Pin20.Value; /// /// Provides direct access to Pin known as BCM21. /// public GpioPin Pin21 => GpioPin.Pin21.Value; /// /// Provides direct access to Pin known as BCM22. /// public GpioPin Pin22 => GpioPin.Pin22.Value; /// /// Provides direct access to Pin known as BCM23. /// public GpioPin Pin23 => GpioPin.Pin23.Value; /// /// Provides direct access to Pin known as BCM24. /// public GpioPin Pin24 => GpioPin.Pin24.Value; /// /// Provides direct access to Pin known as BCM25. /// public GpioPin Pin25 => GpioPin.Pin25.Value; /// /// Provides direct access to Pin known as BCM26. /// public GpioPin Pin26 => GpioPin.Pin26.Value; /// /// Provides direct access to Pin known as BCM27. /// public GpioPin Pin27 => GpioPin.Pin27.Value; /// /// Provides direct access to Pin known as BCM28 (available on Header P5). /// public GpioPin Pin28 => GpioPin.Pin28.Value; /// /// Provides direct access to Pin known as BCM29 (available on Header P5). /// public GpioPin Pin29 => GpioPin.Pin29.Value; /// /// Provides direct access to Pin known as BCM30 (available on Header P5). /// public GpioPin Pin30 => GpioPin.Pin30.Value; /// /// Provides direct access to Pin known as BCM31 (available on Header P5). /// public GpioPin Pin31 => GpioPin.Pin31.Value; #endregion #region Indexers /// public IGpioPin this[BcmPin bcmPin] => Pins[(int)bcmPin]; /// public IGpioPin this[int bcmPinNumber] { get { if (!Enum.IsDefined(typeof(BcmPin), bcmPinNumber)) throw new IndexOutOfRangeException($"Pin {bcmPinNumber} is not registered in the GPIO controller."); return Pins[bcmPinNumber]; } } /// public IGpioPin this[P1 pinNumber] => HeaderP1[(int)pinNumber]; /// public IGpioPin this[P5 pinNumber] => HeaderP5[(int)pinNumber]; /// /// Gets the with the specified Wiring Pi pin number. /// /// /// The . /// /// The pin number. /// A reference to the GPIO pin. public GpioPin this[WiringPiPin pinNumber] { get { if (pinNumber == WiringPiPin.Unknown) throw new InvalidOperationException("You can not get an unknown WiringPi pin."); return Pins.First(p => p.WiringPiPinNumber == pinNumber); } } #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(int group, int 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(int group, int value) => Task.Run(() => 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 first 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(() => 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 unsupported. /// /// 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 first 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 unsupported. /// /// A byte from the GPIO. public Task ReadByteAsync() => Task.Run(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() => Pins.GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => Pins.GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => Pins.GetEnumerator(); #endregion #region Helper and Init Methods /// /// Converts the Wirings Pi pin number to the BCM pin number. /// /// The wiring pi pin number. /// The converted pin. internal static int WiringPiToBcmPinNumber(int 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 int HaderToBcmPinNumber(int headerPinNumber) { lock (SyncRoot) { return WiringPi.PhysPinToGpio(headerPinNumber); } } /// /// 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 bool Initialize(ControllerMode mode) { if (SwanRuntime.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"); int setupResult; switch (mode) { case ControllerMode.DirectWithWiringPiPins: { setupResult = WiringPi.WiringPiSetup(); break; } case ControllerMode.DirectWithBcmPins: { setupResult = WiringPi.WiringPiSetupGpio(); break; } case ControllerMode.DirectWithHeaderPins: { setupResult = WiringPi.WiringPiSetupPhys(); break; } case ControllerMode.FileStreamWithHardwarePins: { setupResult = WiringPi.WiringPiSetupSys(); break; } default: { throw new ArgumentException($"'{mode}' is not a valid initialization mode."); } } Mode = setupResult == 0 ? mode : ControllerMode.NotInitialized; return IsInitialized; } } #endregion } }