namespace Unosquare.RaspberryIO.Gpio { using Native; using Swan; using Swan.Abstractions; using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; /// /// 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 ReadOnlyCollection _pinCollection; private readonly ReadOnlyDictionary _headerP1Pins; private readonly ReadOnlyDictionary _headerP5Pins; 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 (_pinCollection != null) return; if (IsInitialized == false) { var initResult = Initialize(ControllerMode.DirectWithWiringPiPins); if (initResult == false) throw new Exception("Unable to initialize the GPIO controller."); } #region Pin Registration (32 WiringPi Pins) RegisterPin(GpioPin.Pin00.Value); RegisterPin(GpioPin.Pin01.Value); RegisterPin(GpioPin.Pin02.Value); RegisterPin(GpioPin.Pin03.Value); RegisterPin(GpioPin.Pin04.Value); RegisterPin(GpioPin.Pin05.Value); RegisterPin(GpioPin.Pin06.Value); RegisterPin(GpioPin.Pin07.Value); RegisterPin(GpioPin.Pin08.Value); RegisterPin(GpioPin.Pin09.Value); RegisterPin(GpioPin.Pin10.Value); RegisterPin(GpioPin.Pin11.Value); RegisterPin(GpioPin.Pin12.Value); RegisterPin(GpioPin.Pin13.Value); RegisterPin(GpioPin.Pin14.Value); RegisterPin(GpioPin.Pin15.Value); RegisterPin(GpioPin.Pin16.Value); RegisterPin(GpioPin.Pin17.Value); RegisterPin(GpioPin.Pin18.Value); RegisterPin(GpioPin.Pin19.Value); RegisterPin(GpioPin.Pin20.Value); RegisterPin(GpioPin.Pin21.Value); RegisterPin(GpioPin.Pin22.Value); RegisterPin(GpioPin.Pin23.Value); RegisterPin(GpioPin.Pin24.Value); RegisterPin(GpioPin.Pin25.Value); RegisterPin(GpioPin.Pin26.Value); RegisterPin(GpioPin.Pin27.Value); RegisterPin(GpioPin.Pin28.Value); RegisterPin(GpioPin.Pin29.Value); RegisterPin(GpioPin.Pin30.Value); RegisterPin(GpioPin.Pin31.Value); #endregion _pinCollection = new ReadOnlyCollection(_pinsByWiringPiPinNumber.Values.ToArray()); var headerP1 = new Dictionary(_pinCollection.Count); var headerP5 = new Dictionary(_pinCollection.Count); foreach (var pin in _pinCollection) { var target = pin.Header == GpioHeader.P1 ? headerP1 : headerP5; target[pin.HeaderPinNumber] = pin; } _headerP1Pins = new ReadOnlyDictionary(headerP1); _headerP5Pins = 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 number of registered pins in the controller. /// public int Count => _pinCollection.Count; #endregion #region Pin Addressing /// /// Gets the PWM base frequency (in Hz). /// public int PwmBaseFrequency => 19200000; /// /// Gets a red-only collection of all registered pins. /// public ReadOnlyCollection Pins => _pinCollection; /// /// 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 => _headerP1Pins; /// /// 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 => _headerP5Pins; #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] => _pinsByWiringPiPinNumber[pinNumber]; /// /// Gets the with the specified pin number. /// /// /// The . /// /// The pin number. /// A reference to the GPIO pin public GpioPin this[P1 pinNumber] => HeaderP1[(int)pinNumber]; /// /// Gets the with the specified pin number. /// /// /// The . /// /// The pin number. /// A reference to the GPIO pin public GpioPin this[P5 pinNumber] => HeaderP5[(int)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[int wiringPiPinNumber] { get { if (Enum.IsDefined(typeof(WiringPiPin), wiringPiPinNumber) == false) throw new IndexOutOfRangeException($"Pin {wiringPiPinNumber} is not registered in the GPIO controller."); return _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(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 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(() => { 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(() => 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() => _pinCollection.GetEnumerator(); /// /// Returns an enumerator that iterates through the collection. /// /// /// An object that can be used to iterate through the collection. /// IEnumerator IEnumerable.GetEnumerator() => _pinCollection.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(int 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 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); } } /// /// Short-hand method of registering pins /// /// The pin. private void RegisterPin(GpioPin pin) { if (_pinsByWiringPiPinNumber.ContainsKey(pin.WiringPiPinNumber) == false) _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 bool 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); int 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 } }