using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using Swan; using Unosquare.RaspberryIO.Abstractions; namespace Unosquare.WiringPi { /// /// 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() { Dictionary 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(this._pins != null) { return; } if(IsInitialized == false) { Boolean initResult = this.Initialize(ControllerMode.DirectWithBcmPins); if(initResult == false) { throw new Exception("Unable to initialize the GPIO controller."); } } this._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, }; Dictionary headerP1 = new Dictionary(this._pins.Count); Dictionary headerP5 = new Dictionary(this._pins.Count); foreach(GpioPin pin in this._pins) { if(pin.PhysicalPinNumber < 0) { continue; } Dictionary header = pin.Header == GpioHeader.P1 ? headerP1 : headerP5; header[pin.PhysicalPinNumber] = 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 wiring pi edge detection mapping. /// internal static ReadOnlyDictionary WiringPiEdgeDetectionMapping { get; } /// /// /// Gets the number of registered pins in the controller. /// public Int32 Count => this.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 Int32 PwmBaseFrequency => 19200000; /// /// Gets a red-only collection of all pins. /// public ReadOnlyCollection Pins => new ReadOnlyCollection(this._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] => this.Pins[(Int32)bcmPin]; /// public IGpioPin this[Int32 bcmPinNumber] { get { if(!Enum.IsDefined(typeof(BcmPin), bcmPinNumber)) { throw new IndexOutOfRangeException($"Pin {bcmPinNumber} is not registered in the GPIO controller."); } return this.Pins[bcmPinNumber]; } } /// public IGpioPin this[P1 pinNumber] => this.HeaderP1[(Int32)pinNumber]; /// public IGpioPin this[P5 pinNumber] => this.HeaderP5[(Int32)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 this.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(Int32 group, Int32 value) { lock(SyncRoot) { _ = Native.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 first 8 pins (0 to 7) need their {nameof(GpioPin.PinMode)} to be set to {GpioPinDriveMode.Output}"); } Native.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 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)Native.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(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(); /// IEnumerator IEnumerable.GetEnumerator() => this.Pins.GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => this.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 Int32 WiringPiToBcmPinNumber(Int32 wiringPiPinNumber) { lock(SyncRoot) { return Native.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 Native.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 Boolean 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"); Int32 setupResult; switch(mode) { case ControllerMode.DirectWithWiringPiPins: { setupResult = Native.WiringPi.WiringPiSetup(); break; } case ControllerMode.DirectWithBcmPins: { setupResult = Native.WiringPi.WiringPiSetupGpio(); break; } case ControllerMode.DirectWithHeaderPins: { setupResult = Native.WiringPi.WiringPiSetupPhys(); break; } case ControllerMode.FileStreamWithHardwarePins: { setupResult = Native.WiringPi.WiringPiSetupSys(); break; } default: { throw new ArgumentException($"'{mode}' is not a valid initialization mode."); } } Mode = setupResult == 0 ? mode : ControllerMode.NotInitialized; return IsInitialized; } } #endregion } }