From 54a33be504d9082c296bdd726ee1598eadaa79b9 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sun, 11 Apr 2021 16:09:28 +0200 Subject: [PATCH] Add Board as base class of existing ArduinoBoard --- src/devices/Arduino/Arduino.csproj | 1 + src/devices/Arduino/Arduino.sln | 6 + src/devices/Arduino/ArduinoBoard.cs | 150 +++++++++++++++++++----- src/devices/Arduino/ArduinoI2cBus.cs | 41 +++++++ src/devices/Arduino/ArduinoI2cDevice.cs | 18 ++- src/devices/Board/Board.cs | 11 +- src/devices/Board/I2cBusManager.cs | 5 + src/devices/Board/RaspberryPiBoard.cs | 35 ++++-- 8 files changed, 225 insertions(+), 42 deletions(-) create mode 100644 src/devices/Arduino/ArduinoI2cBus.cs diff --git a/src/devices/Arduino/Arduino.csproj b/src/devices/Arduino/Arduino.csproj index 81332014dc..848cd39c67 100644 --- a/src/devices/Arduino/Arduino.csproj +++ b/src/devices/Arduino/Arduino.csproj @@ -12,6 +12,7 @@ + diff --git a/src/devices/Arduino/Arduino.sln b/src/devices/Arduino/Arduino.sln index fd1eefa76f..4355288e20 100644 --- a/src/devices/Arduino/Arduino.sln +++ b/src/devices/Arduino/Arduino.sln @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Tests", "tests\Ardu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HardwareMonitor", "..\HardwareMonitor\HardwareMonitor.csproj", "{D1C467D8-E6E3-4811-826B-216DCB80A16E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Board", "..\Board\Board.csproj", "{0413C8F5-20E0-48B2-8C35-AED67219B7C6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -73,6 +75,10 @@ Global {D1C467D8-E6E3-4811-826B-216DCB80A16E}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1C467D8-E6E3-4811-826B-216DCB80A16E}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1C467D8-E6E3-4811-826B-216DCB80A16E}.Release|Any CPU.Build.0 = Release|Any CPU + {0413C8F5-20E0-48B2-8C35-AED67219B7C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0413C8F5-20E0-48B2-8C35-AED67219B7C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0413C8F5-20E0-48B2-8C35-AED67219B7C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0413C8F5-20E0-48B2-8C35-AED67219B7C6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index fc4126e5db..cd5c3b5633 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -18,6 +18,7 @@ using System.Threading; using System.Linq; using Iot.Device.Common; +using Iot.Device.Board; using Microsoft.Extensions.Logging; using UnitsNet; @@ -29,7 +30,7 @@ namespace Iot.Device.Arduino /// Note that the program will run on the PC, so you cannot disconnect the /// Arduino while this driver is connected. /// - public class ArduinoBoard : IDisposable + public class ArduinoBoard : Board.Board, IDisposable { private SerialPort? _serialPort; private Stream? _dataStream; @@ -56,6 +57,7 @@ public class ArduinoBoard : IDisposable /// /// A stream to an Arduino/Firmata device public ArduinoBoard(Stream serialPortStream) + : base(PinNumberingScheme.Logical) { _dataStream = serialPortStream ?? throw new ArgumentNullException(nameof(serialPortStream)); _logger = this.GetCurrentClassLogger(); @@ -69,6 +71,7 @@ public ArduinoBoard(Stream serialPortStream) /// On Linux, possible values include "/dev/ttyAMA0", "/dev/serial0", "/dev/ttyUSB1", etc. /// Baudrate to use. It is recommended to use at least 115200 Baud. public ArduinoBoard(string portName, int baudRate) + : base(PinNumberingScheme.Logical) { _dataStream = null; _serialPort = new SerialPort(portName, baudRate); @@ -158,13 +161,73 @@ public static List CommonBaudRates() }; } + /// + public override int ConvertPinNumber(int pinNumber, PinNumberingScheme inputScheme, PinNumberingScheme outputScheme) + { + if (inputScheme == outputScheme && inputScheme == PinNumberingScheme.Logical) + { + return pinNumber; + } + + throw new NotSupportedException("The Arduino uses logical pin numbering only"); + } + + /// + /// Returns the current assignment of the given pin + /// + /// Pin number to query + /// A value of the enumeration + public override PinUsage DetermineCurrentPinUsage(int pinNumber) + { + SupportedMode mode = Firmata.GetPinMode(pinNumber); + switch (mode) + { + case SupportedMode.AnalogInput: + return PinUsage.AnalogIn; + case SupportedMode.DigitalInput: + return PinUsage.Gpio; + case SupportedMode.DigitalOutput: + return PinUsage.Gpio; + case SupportedMode.Pwm: + return PinUsage.Pwm; + case SupportedMode.Servo: + break; + case SupportedMode.Shift: + break; + case SupportedMode.I2c: + return PinUsage.I2c; + case SupportedMode.OneWire: + break; + case SupportedMode.Stepper: + break; + case SupportedMode.Encoder: + break; + case SupportedMode.Serial: + return PinUsage.Uart; + case SupportedMode.InputPullup: + return PinUsage.Gpio; + case SupportedMode.Spi: + return PinUsage.Spi; + case SupportedMode.Sonar: + break; + case SupportedMode.Tone: + break; + case SupportedMode.Dht: + return PinUsage.Gpio; + } + + return PinUsage.Unknown; + } + /// /// Initialize the board connection. This must be called before any other methods of this class. /// /// The Firmata firmware on the connected board is too old. /// There was no answer from the board - protected virtual void Initialize() + protected override void Initialize() { + base.Initialize(); + // Shortcut, so we do not need to take the lock if (_initialized) { @@ -313,35 +376,29 @@ private void FirmataOnError(string message, Exception? exception) /// Creates a GPIO Controller instance for the board. This allows working with digital input/output pins. /// /// An instance of GpioController, using an Arduino-Enabled driver - public GpioController CreateGpioController() + public override GpioController CreateGpioController() { Initialize(); return new GpioController(PinNumberingScheme.Logical, new ArduinoGpioControllerDriver(this, _supportedPinConfigurations)); } - /// - /// Creates an I2c device with the given connection settings. - /// - /// I2c connection settings. Only Bus 0 is supported. - /// An instance - /// The firmware reports that no pins are available for I2C. Check whether the I2C module is enabled in Firmata. - /// Or: An invalid Bus Id or device Id was specified - public virtual I2cDevice CreateI2cDevice(I2cConnectionSettings connectionSettings) + /// + protected override I2cBusManager CreateI2cBusCore(int busNumber, int[]? pins) { Initialize(); - - if (connectionSettings == null) - { - throw new ArgumentNullException(nameof(connectionSettings)); - } - if (!SupportedPinConfigurations.Any(x => x.PinModes.Contains(SupportedMode.I2c))) { throw new NotSupportedException("No Pins with I2c support found. Is the I2c module loaded?"); } - return new ArduinoI2cDevice(this, connectionSettings); + return new I2cBusManager(this, busNumber, pins, new ArduinoI2cBus(this, busNumber)); + } + + /// + public override int GetDefaultI2cBusNumber() + { + return 0; } /// @@ -349,9 +406,10 @@ public virtual I2cDevice CreateI2cDevice(I2cConnectionSettings connectionSetting /// Firmata's default implementation has no SPI support, so this first checks whether it's available at all. /// /// Spi Connection settings + /// The pins to use. /// An instance. /// The Bus number is not 0, or the SPI component has not been enabled in the firmware. - public virtual SpiDevice CreateSpiDevice(SpiConnectionSettings settings) + protected override SpiDevice CreateSimpleSpiDevice(SpiConnectionSettings settings, int[] pins) { Initialize(); @@ -381,17 +439,54 @@ public virtual SpiDevice CreateSpiDevice(SpiConnectionSettings settings) /// This value is ignored /// The duty cycle as a fraction. /// - public virtual PwmChannel CreatePwmChannel( + protected override PwmChannel CreateSimplePwmChannel( int chip, int channel, - int frequency = 400, - double dutyCyclePercentage = 0.5) + int frequency, + double dutyCyclePercentage) { Initialize(); return new ArduinoPwmChannel(this, chip, channel, frequency, dutyCyclePercentage); } + /// + public override int GetDefaultPinAssignmentForPwm(int chip, int channel) + { + if (chip == 0) + { + return channel; + } + + throw new NotSupportedException($"Unknown chip numbe {chip}"); + } + + /// + public override int[] GetDefaultPinAssignmentForI2c(int busId) + { + if (busId != 0) + { + throw new NotSupportedException("Only bus number 0 is currently supported"); + } + + var pins = _supportedPinConfigurations.Where(x => x.PinModes.Contains(SupportedMode.I2c)).Select(y => y.Pin); + + return pins.ToArray(); + } + + /// + public override int[] GetDefaultPinAssignmentForSpi(SpiConnectionSettings connectionSettings) + { + if (connectionSettings.BusId != 0) + { + throw new NotSupportedException("Only bus number 0 is currently supported"); + } + + var pins = _supportedPinConfigurations.Where(x => x.PinModes.Contains(SupportedMode.Spi)).Select(y => y.Pin); + + return pins.ToArray(); + } + /// /// Creates an anlog controller for this board. /// @@ -440,7 +535,7 @@ public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, /// /// Standard dispose pattern /// - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { _isDisposed = true; // Do this first, to force any blocking read operations to end @@ -465,15 +560,6 @@ protected virtual void Dispose(bool disposing) _initialized = false; } - /// - /// Dispose this instance - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - internal void EnableSpi() { _spiEnabled++; diff --git a/src/devices/Arduino/ArduinoI2cBus.cs b/src/devices/Arduino/ArduinoI2cBus.cs new file mode 100644 index 0000000000..fd11a0d3cd --- /dev/null +++ b/src/devices/Arduino/ArduinoI2cBus.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Device.I2c; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Iot.Device.Arduino +{ + internal class ArduinoI2cBus : I2cBus + { + private readonly ArduinoBoard _board; + private readonly int _busId; + private readonly HashSet _usedAddresses; + + public ArduinoI2cBus(ArduinoBoard board, int busId) + { + _board = board; + _busId = busId; + _usedAddresses = new HashSet(); + } + + /// + public override I2cDevice CreateDevice(int deviceAddress) + { + if (_usedAddresses.Contains(deviceAddress)) + { + throw new InvalidOperationException($"Device number {deviceAddress} is already in use"); + } + + var device = new ArduinoI2cDevice(_board, this, new I2cConnectionSettings(_busId, deviceAddress)); + _usedAddresses.Add(deviceAddress); + return device; + } + + public override void RemoveDevice(int deviceAddress) + { + _usedAddresses.Remove(deviceAddress); + } + } +} diff --git a/src/devices/Arduino/ArduinoI2cDevice.cs b/src/devices/Arduino/ArduinoI2cDevice.cs index 9965b5fd70..d95d17428c 100644 --- a/src/devices/Arduino/ArduinoI2cDevice.cs +++ b/src/devices/Arduino/ArduinoI2cDevice.cs @@ -16,10 +16,12 @@ namespace Iot.Device.Arduino internal class ArduinoI2cDevice : I2cDevice { private readonly ArduinoBoard _board; + private ArduinoI2cBus? _bus; - public ArduinoI2cDevice(ArduinoBoard board, I2cConnectionSettings connectionSettings) + public ArduinoI2cDevice(ArduinoBoard board, ArduinoI2cBus bus, I2cConnectionSettings connectionSettings) { _board = board ?? throw new ArgumentNullException(nameof(board)); + _bus = bus ?? throw new ArgumentNullException(nameof(bus)); if (connectionSettings == null) { @@ -98,5 +100,19 @@ public override void WriteRead(ReadOnlySpan writeBuffer, Span readBu { _board.Firmata.WriteReadI2cData(ConnectionSettings.DeviceAddress, writeBuffer, readBuffer); } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_bus != null) + { + _bus.RemoveDevice(ConnectionSettings.DeviceAddress); + _bus = null; + } + } + + base.Dispose(disposing); + } } } diff --git a/src/devices/Board/Board.cs b/src/devices/Board/Board.cs index 592c63c2fc..a61abbaa6b 100644 --- a/src/devices/Board/Board.cs +++ b/src/devices/Board/Board.cs @@ -226,7 +226,7 @@ protected virtual void ActivatePinMode(int pinNumber, PinUsage usage) /// Initialize the board and test whether it works on the current hardware. /// /// The required hardware cannot be found - public virtual void Initialize() + protected virtual void Initialize() { if (_disposed) { @@ -290,6 +290,8 @@ public virtual GpioController CreateGpioController() /// (or for purposes for which it is not suitable) public virtual GpioController CreateGpioController(PinNumberingScheme pinNumberingScheme) { + Initialize(); + GpioDriver? driver = TryCreateBestGpioDriver(); if (driver == null) { @@ -411,6 +413,7 @@ private static GpioDriver GetBestDriverForBoardOnWindows() /// An I2C bus instance public virtual I2cBus CreateOrGetI2cBus(int busNumber, int[]? pinAssignment) { + Initialize(); if (_i2cBuses.TryGetValue(busNumber, out var bus)) { return bus; @@ -433,6 +436,7 @@ public virtual I2cBus CreateOrGetI2cBus(int busNumber, int[]? pinAssignment) /// An I2C bus instance public I2cBus CreateOrGetI2cBus(int busNumber) { + Initialize(); if (_i2cBuses.TryGetValue(busNumber, out var bus)) { return bus; @@ -467,6 +471,7 @@ public I2cBus CreateOrGetI2cBus(int busNumber) /// (i.e. bus 0 and 1 on the Raspi always use pins 0/1 and 2/3) public I2cDevice CreateI2cDevice(I2cConnectionSettings connectionSettings) { + Initialize(); // Returns logical pin numbers for the selected bus (or an exception if using a bus number > 1, because that // requires specifying the pins) if (_i2cBuses.TryGetValue(connectionSettings.BusId, out var bus)) @@ -498,6 +503,7 @@ internal void RemoveBus(I2cBusManager bus) /// An SPI device instance public SpiDevice CreateSpiDevice(SpiConnectionSettings connectionSettings, int[] pinAssignment, PinNumberingScheme pinNumberingScheme) { + Initialize(); if (pinAssignment == null) { throw new ArgumentNullException(nameof(pinAssignment)); @@ -519,6 +525,7 @@ public SpiDevice CreateSpiDevice(SpiConnectionSettings connectionSettings, int[] /// This method can only be used for bus numbers where the corresponding pins are hardwired public SpiDevice CreateSpiDevice(SpiConnectionSettings connectionSettings) { + Initialize(); // Returns logical pin numbers for the selected bus (or an exception if using a bus number > 1, because that // requires specifying the pins) int[] pinAssignment = GetDefaultPinAssignmentForSpi(connectionSettings); @@ -548,6 +555,7 @@ public SpiDevice CreateSpiDevice(SpiConnectionSettings connectionSettings) public PwmChannel CreatePwmChannel(int chip, int channel, int frequency, double dutyCyclePercentage, int pin, PinNumberingScheme pinNumberingScheme) { + Initialize(); return new PwmChannelManager(this, pin, chip, channel, frequency, dutyCyclePercentage, CreateSimplePwmChannel); } @@ -565,6 +573,7 @@ public PwmChannel CreatePwmChannel( int frequency = 400, double dutyCyclePercentage = 0.5) { + Initialize(); int pin = GetDefaultPinAssignmentForPwm(chip, channel); return CreatePwmChannel(chip, channel, frequency, dutyCyclePercentage, RemapPin(pin, DefaultPinNumberingScheme), PinNumberingScheme.Logical); } diff --git a/src/devices/Board/I2cBusManager.cs b/src/devices/Board/I2cBusManager.cs index 3b474c08d6..05bfabdc99 100644 --- a/src/devices/Board/I2cBusManager.cs +++ b/src/devices/Board/I2cBusManager.cs @@ -68,6 +68,11 @@ public I2cBusManager(Board board, int bus, int[]? pins, I2cBus busInstance) /// No test is performed whether the given device exists and is usable public override I2cDevice CreateDevice(int deviceAddress) { + if (_devices.TryGetValue(deviceAddress, out var device)) + { + return device; + } + var newDevice = _busInstance.CreateDevice(deviceAddress); _devices.Add(deviceAddress, newDevice); return newDevice; diff --git a/src/devices/Board/RaspberryPiBoard.cs b/src/devices/Board/RaspberryPiBoard.cs index 7d947c8e84..33080050bd 100644 --- a/src/devices/Board/RaspberryPiBoard.cs +++ b/src/devices/Board/RaspberryPiBoard.cs @@ -18,8 +18,11 @@ namespace Iot.Device.Board /// public class RaspberryPiBoard : GenericBoard { + private readonly object _initLock = new object(); + private ManagedGpioController? _managedGpioController; private RaspberryPi3Driver? _raspberryPi3Driver; + private bool _initialized; /// /// Creates an instance of a Rasperry Pi board. @@ -29,6 +32,7 @@ public RaspberryPiBoard(PinNumberingScheme defaultNumberingScheme) { // TODO: Ideally detect board type, so that invalid combinations can be prevented (i.e. I2C bus 2 on Raspi 3) PinCount = 28; + _initialized = false; } /// @@ -50,19 +54,34 @@ public int PinCount /// Initializes this instance /// /// The current hardware could not be identified as a valid Raspberry Pi type - public override void Initialize() + protected override void Initialize() { - // Needs to be a raspi 3 driver here (either unix or windows) - GpioDriver? driver = TryCreateBestGpioDriver(); - if (driver == null) + if (_initialized) { - throw new NotSupportedException("Could not initialize the RaspberryPi GPIO driver"); + return; } - _managedGpioController = new ManagedGpioController(this, DefaultPinNumberingScheme, driver); - _raspberryPi3Driver = driver as RaspberryPi3Driver; + lock (_initLock) + { + if (_initialized) + { + return; + } + + // Needs to be a raspi 3 driver here (either unix or windows) + GpioDriver? driver = TryCreateBestGpioDriver(); + if (driver == null) + { + throw new NotSupportedException("Could not initialize the RaspberryPi GPIO driver"); + } + + _managedGpioController = new ManagedGpioController(this, DefaultPinNumberingScheme, driver); + _raspberryPi3Driver = driver as RaspberryPi3Driver; + + PinCount = _managedGpioController.PinCount; + _initialized = true; + } - PinCount = _managedGpioController.PinCount; base.Initialize(); }