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 } }