From b7bf82f877abd10e6f642f5a4ece9fa9d8c24ded Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Fri, 30 Oct 2020 11:03:23 +0100 Subject: [PATCH 01/56] Basic firmata features Works with currently released version of ConfigurableFirmata. Should work with StandardFirmata as well. --- src/devices/Arduino/Arduino.csproj | 17 + src/devices/Arduino/Arduino.sln | 155 ++ .../Arduino/ArduinoAnalogControllerDriver.cs | 133 ++ src/devices/Arduino/ArduinoBoard.cs | 255 ++++ .../Arduino/ArduinoGpioControllerDriver.cs | 303 ++++ src/devices/Arduino/ArduinoI2cDevice.cs | 100 ++ src/devices/Arduino/ArduinoMethodDecl.cs | 83 ++ src/devices/Arduino/ArduinoPwmChannel.cs | 132 ++ src/devices/Arduino/ArduinoSpiDevice.cs | 73 + src/devices/Arduino/ArduinoTask.cs | 154 ++ src/devices/Arduino/CommonFirmataFeatures.ino | 102 ++ src/devices/Arduino/DebugLogStream.cs | 83 ++ src/devices/Arduino/FirmataCommand.cs | 59 + src/devices/Arduino/FirmataDevice.cs | 1285 +++++++++++++++++ src/devices/Arduino/FirmataSpiCommand.cs | 13 + src/devices/Arduino/FirmataSysexCommand.cs | 34 + src/devices/Arduino/README.md | 47 + src/devices/Arduino/SupportedMode.cs | 92 ++ .../Arduino/SupportedPinConfiguration.cs | 62 + src/devices/Arduino/category.txt | 6 + .../Arduino/samples/Arduino.Monitor.cs | 301 ++++ .../Arduino/samples/Arduino.Monitor.csproj | 30 + src/devices/Arduino/samples/Arduino.sample.cs | 567 ++++++++ .../Arduino/samples/Arduino.sample.csproj | 27 + .../Arduino/samples/CharacterDisplay.cs | 56 + 25 files changed, 4169 insertions(+) create mode 100644 src/devices/Arduino/Arduino.csproj create mode 100644 src/devices/Arduino/Arduino.sln create mode 100644 src/devices/Arduino/ArduinoAnalogControllerDriver.cs create mode 100644 src/devices/Arduino/ArduinoBoard.cs create mode 100644 src/devices/Arduino/ArduinoGpioControllerDriver.cs create mode 100644 src/devices/Arduino/ArduinoI2cDevice.cs create mode 100644 src/devices/Arduino/ArduinoMethodDecl.cs create mode 100644 src/devices/Arduino/ArduinoPwmChannel.cs create mode 100644 src/devices/Arduino/ArduinoSpiDevice.cs create mode 100644 src/devices/Arduino/ArduinoTask.cs create mode 100644 src/devices/Arduino/CommonFirmataFeatures.ino create mode 100644 src/devices/Arduino/DebugLogStream.cs create mode 100644 src/devices/Arduino/FirmataCommand.cs create mode 100644 src/devices/Arduino/FirmataDevice.cs create mode 100644 src/devices/Arduino/FirmataSpiCommand.cs create mode 100644 src/devices/Arduino/FirmataSysexCommand.cs create mode 100644 src/devices/Arduino/README.md create mode 100644 src/devices/Arduino/SupportedMode.cs create mode 100644 src/devices/Arduino/SupportedPinConfiguration.cs create mode 100644 src/devices/Arduino/category.txt create mode 100644 src/devices/Arduino/samples/Arduino.Monitor.cs create mode 100644 src/devices/Arduino/samples/Arduino.Monitor.csproj create mode 100644 src/devices/Arduino/samples/Arduino.sample.cs create mode 100644 src/devices/Arduino/samples/Arduino.sample.csproj create mode 100644 src/devices/Arduino/samples/CharacterDisplay.cs diff --git a/src/devices/Arduino/Arduino.csproj b/src/devices/Arduino/Arduino.csproj new file mode 100644 index 0000000000..c0da12b58a --- /dev/null +++ b/src/devices/Arduino/Arduino.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + false + Iot.Device.Arduino + + + + + + + + + + + diff --git a/src/devices/Arduino/Arduino.sln b/src/devices/Arduino/Arduino.sln new file mode 100644 index 0000000000..e26436d8c7 --- /dev/null +++ b/src/devices/Arduino/Arduino.sln @@ -0,0 +1,155 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29905.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino", "Arduino.csproj", "{0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Device.Gpio", "..\..\System.Device.Gpio\System.Device.Gpio.csproj", "{0B90F9D4-7353-4172-A317-714471A06781}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.sample", "samples\Arduino.sample.csproj", "{2670F7BF-A7C8-49EB-9A99-1719A90D0C67}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bmxx80", "..\Bmxx80\Bmxx80.csproj", "{EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mcp3xxx", "..\Mcp3xxx\Mcp3xxx.csproj", "{26911D2A-E303-4846-96A1-5ABC92F54445}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CharacterLcd", "..\CharacterLcd\CharacterLcd.csproj", "{D47A6627-4041-4CEA-8903-A6C67C6993CF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "..\Common\Iot\Device\Common\CommonHelpers.csproj", "{CD593083-8E94-4B60-86BF-DF3FB7873893}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CpuTemperature", "..\CpuTemperature\CpuTemperature.csproj", "{F3950513-A564-462F-887B-E00972D20FAD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9E5A25ED-9839-4C1A-9B27-993437D1CB31}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Monitor", "samples\Arduino.Monitor.csproj", "{23B4B60C-9594-42BB-9D25-C54983B0F809}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Linux-Debug|Any CPU = Linux-Debug|Any CPU + Linux-Release|Any CPU = Linux-Release|Any CPU + Release|Any CPU = Release|Any CPU + Windows-Debug|Any CPU = Windows-Debug|Any CPU + Windows-Release|Any CPU = Windows-Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Linux-Release|Any CPU.Build.0 = Release|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Release|Any CPU.Build.0 = Release|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU + {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Linux-Release|Any CPU.Build.0 = Release|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Release|Any CPU.Build.0 = Release|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU + {0B90F9D4-7353-4172-A317-714471A06781}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Linux-Release|Any CPU.Build.0 = Release|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Release|Any CPU.Build.0 = Release|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Linux-Release|Any CPU.Build.0 = Release|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Release|Any CPU.Build.0 = Release|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU + {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Linux-Debug|Any CPU.ActiveCfg = Linux-Debug|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Linux-Debug|Any CPU.Build.0 = Linux-Debug|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Linux-Release|Any CPU.ActiveCfg = Linux-Release|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Linux-Release|Any CPU.Build.0 = Linux-Release|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Release|Any CPU.Build.0 = Release|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Windows-Debug|Any CPU.ActiveCfg = Windows-Debug|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Windows-Debug|Any CPU.Build.0 = Windows-Debug|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Windows-Release|Any CPU.ActiveCfg = Windows-Release|Any CPU + {26911D2A-E303-4846-96A1-5ABC92F54445}.Windows-Release|Any CPU.Build.0 = Windows-Release|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Linux-Release|Any CPU.Build.0 = Release|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Release|Any CPU.Build.0 = Release|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU + {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Linux-Release|Any CPU.Build.0 = Release|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.Build.0 = Release|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU + {CD593083-8E94-4B60-86BF-DF3FB7873893}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Linux-Release|Any CPU.Build.0 = Release|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Release|Any CPU.Build.0 = Release|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU + {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Release|Any CPU.Build.0 = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.Build.0 = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {2670F7BF-A7C8-49EB-9A99-1719A90D0C67} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31} + {23B4B60C-9594-42BB-9D25-C54983B0F809} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {47BF9684-1876-4AC1-8C87-04B8C7FA6C3A} + EndGlobalSection +EndGlobal diff --git a/src/devices/Arduino/ArduinoAnalogControllerDriver.cs b/src/devices/Arduino/ArduinoAnalogControllerDriver.cs new file mode 100644 index 0000000000..6ccca4cbc6 --- /dev/null +++ b/src/devices/Arduino/ArduinoAnalogControllerDriver.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Device.Analog; +using System.Device.Gpio; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Iot.Device.Arduino +{ + internal class ArduinoAnalogControllerDriver : AnalogControllerDriver + { + private readonly ArduinoBoard _board; + private readonly List _supportedPinConfigurations; + private readonly Dictionary _callbacks; + private int _autoReportingReferenceCount; + private int _firstAnalogPin; + + public ArduinoAnalogControllerDriver(ArduinoBoard board, + List supportedPinConfigurations) + { + _board = board ?? throw new ArgumentNullException(nameof(board)); + _supportedPinConfigurations = supportedPinConfigurations ?? throw new ArgumentNullException(nameof(supportedPinConfigurations)); + _callbacks = new Dictionary(); + _autoReportingReferenceCount = 0; + PinCount = _supportedPinConfigurations.Count; + VoltageReference = 5.0; + // Number of the first analog pin. Serves for converting between logical A0-based pin numbers and digital pin numbers. + // The value of this is 14 for most arduinos. + var firstPin = _supportedPinConfigurations.FirstOrDefault(x => x.PinModes.Contains(SupportedMode.ANALOG_INPUT)); + if (firstPin != null) + { + _firstAnalogPin = firstPin.Pin; + } + else + { + _firstAnalogPin = 0; + } + } + + public override int PinCount + { + get; + } + + protected override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber) + { + return pinNumber - _firstAnalogPin; + } + + protected override int ConvertLogicalNumberingSchemeToPinNumber(int logicalPinNumber) + { + return logicalPinNumber + _firstAnalogPin; + } + + public override bool SupportsAnalogInput(int pinNumber) + { + return _supportedPinConfigurations[pinNumber].PinModes.Contains(SupportedMode.ANALOG_INPUT); + } + + public override void EnableAnalogValueChangedEvent(int pinNumber, GpioController masterController, int masterPin) + { + // The pin is already open, so analog reporting is enabled, we just need to forward it. + if (_autoReportingReferenceCount == 0) + { + _board.Firmata.AnalogPinValueUpdated += FirmataOnAnalogPinValueUpdated; + } + + _autoReportingReferenceCount += 1; + } + + public override void DisableAnalogValueChangedEvent(int pinNumber) + { + _autoReportingReferenceCount -= 1; + if (_autoReportingReferenceCount == 0) + { + _board.Firmata.AnalogPinValueUpdated -= FirmataOnAnalogPinValueUpdated; + } + } + + private void FirmataOnAnalogPinValueUpdated(int pin, uint rawvalue) + { + if (_autoReportingReferenceCount > 0) + { + int physicalPin = ConvertLogicalNumberingSchemeToPinNumber(pin); + double voltage = ConvertToVoltage(physicalPin, rawvalue); + var message = new ValueChangedEventArgs(rawvalue, voltage, physicalPin, TriggerReason.Timed); + FireValueChanged(message); + } + } + + public override void OpenPin(int pinNumber) + { + if (!_supportedPinConfigurations[pinNumber].PinModes.Contains(SupportedMode.ANALOG_INPUT)) + { + throw new NotSupportedException($"Pin {pinNumber} does not support Analog input"); + } + + _board.Firmata.SetPinMode(pinNumber, SupportedMode.ANALOG_INPUT); + _board.Firmata.EnableAnalogReporting(ConvertPinNumberToLogicalNumberingScheme(pinNumber)); + } + + public override void ClosePin(int pinNumber) + { + _board.Firmata.DisableAnalogReporting(ConvertPinNumberToLogicalNumberingScheme(pinNumber)); + } + + /// + /// Return the resolution of an analog input pin. + /// + /// The pin number + /// Returns the resolution of the ADC in number of bits, including the sign bit (if applicable) + /// Minimum measurable voltage + /// Maximum measurable voltage + public override void QueryResolution(int pinNumber, out int numberOfBits, out double minVoltage, out double maxVoltage) + { + numberOfBits = _supportedPinConfigurations[pinNumber].AnalogInputResolutionBits; + minVoltage = 0.0; + maxVoltage = VoltageReference; + } + + public override uint ReadRaw(int pinNumber) + { + return _board.Firmata.GetAnalogRawValue(ConvertPinNumberToLogicalNumberingScheme(pinNumber)); + } + + protected override void Dispose(bool disposing) + { + _callbacks.Clear(); + base.Dispose(disposing); + } + } +} diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs new file mode 100644 index 0000000000..06c761a3d2 --- /dev/null +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Device.Analog; +using System.Text; +using System.Device.Gpio; +using System.Device.I2c; +using System.Device.Pwm; +using System.Device.Spi; +using System.Diagnostics; +using System.IO; +using System.Threading; +using UnitsNet; + +#pragma warning disable CS1591 + +namespace Iot.Device.Arduino +{ + /// + /// Implements an interface to an arduino board which is running Firmata. + /// See documentation on how to prepare your arduino board to work with this. + /// Note that the program will run on the PC, so you cannot disconnect the + /// Arduino while this driver is connected. + /// + public class ArduinoBoard : IDisposable + { + private Stream _serialPortStream; + private FirmataDevice _firmata; + private Version _firmwareVersion; + private Version _protocolVersion; + private string _firmwareName; + private List _supportedPinConfigurations; + + // Only a delegate, not an event, because one board can only have one compiler attached at a time + private Action _compilerCallback; + + // Counts how many spi devices are attached, to make sure we enable/disable the bus only when no devices are attached + private int _spiEnabled; + + public ArduinoBoard(Stream serialPortStream) + { + _serialPortStream = serialPortStream; + _spiEnabled = 0; + } + + public event Action LogMessages; + + public virtual void Initialize() + { + _firmata = new FirmataDevice(); + _firmata.Open(_serialPortStream); + _firmata.OnError += FirmataOnError; + _protocolVersion = _firmata.QueryFirmataVersion(); + if (_protocolVersion < _firmata.QuerySupportedFirmataVersion()) + { + throw new NotSupportedException($"Firmata version on board is {_protocolVersion}. Expected {_firmata.QuerySupportedFirmataVersion()}. They must be equal."); + } + + Log($"Firmata version on board is {_protocolVersion}."); + + _firmwareVersion = _firmata.QueryFirmwareVersion(out _firmwareName); + + Log($"Firmware version on board is {_firmwareVersion}"); + + _firmata.QueryCapabilities(); + + _supportedPinConfigurations = _firmata.PinConfigurations; // Clone reference + + Log("Device capabilities: "); + foreach (var pin in _supportedPinConfigurations) + { + Log(pin.ToString()); + } + + // _firmata.SetSamplingInterval(TimeSpan.FromMilliseconds(100)); + _firmata.EnableDigitalReporting(); + + _firmata.OnSchedulerReply += FirmataOnSchedulerReply; + } + + public Version FirmwareVersion + { + get + { + return _firmwareVersion; + } + } + + public string FirmwareName + { + get + { + return _firmwareName; + } + } + + internal FirmataDevice Firmata + { + get + { + return _firmata; + } + } + + internal List SupportedPinConfigurations + { + get + { + return _supportedPinConfigurations; + } + } + + internal void Log(string message) + { + LogMessages?.Invoke(message, null); + } + + private void FirmataOnError(string message, Exception innerException) + { + LogMessages?.Invoke(message, innerException); + } + + private void FirmataOnSchedulerReply(byte method, MethodState schedulerMethodState, int numArgs, IList bytesOfArgs) + { + object[] data = new object[numArgs]; + + for (int i = 0; i < numArgs * 4; i += 4) + { + int retVal = bytesOfArgs[i] | bytesOfArgs[i + 1] << 8 | bytesOfArgs[i + 2] << 16 | bytesOfArgs[i + 3] << 24; + data[i / 4] = retVal; + } + + _compilerCallback?.Invoke(method, schedulerMethodState, data); + } + + public GpioController CreateGpioController(PinNumberingScheme pinNumberingScheme) + { + return new GpioController(pinNumberingScheme, new ArduinoGpioControllerDriver(this, _supportedPinConfigurations)); + } + + public I2cDevice CreateI2cDevice(I2cConnectionSettings connectionSettings) + { + return new ArduinoI2cDevice(this, connectionSettings); + } + + /// + /// Firmata has no support for SPI, even though the Arduino basically has an SPI interface. + /// This therefore returns a Software SPI device for the default Arduino SPI port on pins 11, 12 and 13. + /// + /// Spi Connection settings + /// + public SpiDevice CreateSpiDevice(SpiConnectionSettings settings) + { + if (settings.BusId != 0) + { + throw new NotSupportedException("Only Bus Id 0 is supported"); + } + + return new ArduinoSpiDevice(this, settings); + } + + public PwmChannel CreatePwmChannel( + int chip, + int channel, + int frequency = 400, + double dutyCyclePercentage = 0.5) + { + return new ArduinoPwmChannel(this, chip, channel, frequency, dutyCyclePercentage); + } + + public AnalogController CreateAnalogController(int chip) + { + return new AnalogController(PinNumberingScheme.Logical, new ArduinoAnalogControllerDriver(this, _supportedPinConfigurations)); + } + + /// + /// Special function to read DHT sensor, if supported + /// + /// Pin Number + /// Type of DHT Sensor: 11 = DHT11, 22 = DHT22, etc. + /// Temperature + /// Relative humidity + /// True on success, false otherwise + public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, out Ratio humidity) + { + if (!_supportedPinConfigurations[pinNumber].PinModes.Contains(SupportedMode.DHT)) + { + temperature = default; + humidity = default; + return false; + } + + return Firmata.TryReadDht(pinNumber, dhtType, out temperature, out humidity); + } + + protected virtual void Dispose(bool disposing) + { + // Do this first, to force any blocking read operations to end + if (_serialPortStream != null) + { + _serialPortStream.Close(); + _serialPortStream.Dispose(); + } + + _serialPortStream = null; + if (_firmata != null) + { + _firmata.OnError -= FirmataOnError; + _firmata.Close(); + _firmata.Dispose(); + } + + _firmata = null; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + internal void EnableSpi() + { + _spiEnabled++; + if (_spiEnabled == 1) + { + _firmata.EnableSpi(); + } + } + + internal void DisableSpi() + { + _spiEnabled--; + if (_spiEnabled == 0) + { + _firmata.DisableSpi(); + } + } + + public void SetCompilerCallback(Action onCompilerCallback) + { + if (onCompilerCallback == null) + { + _compilerCallback = null; + return; + } + + if (_compilerCallback != null) + { + throw new InvalidOperationException("Only one compiler can be active for a single board"); + } + + _compilerCallback = onCompilerCallback; + } + } +} diff --git a/src/devices/Arduino/ArduinoGpioControllerDriver.cs b/src/devices/Arduino/ArduinoGpioControllerDriver.cs new file mode 100644 index 0000000000..54b2e46518 --- /dev/null +++ b/src/devices/Arduino/ArduinoGpioControllerDriver.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Device.Gpio; +using System.Linq; +using System.Threading; + +#pragma warning disable CS1591 +namespace Iot.Device.Arduino +{ + internal class ArduinoGpioControllerDriver : GpioDriver + { + private readonly ArduinoBoard _arduinoBoard; + private readonly List _supportedPinConfigurations; + private readonly Dictionary _callbackContainers; + private readonly ConcurrentDictionary _pinModes; + private readonly object _callbackContainersLock; + private readonly AutoResetEvent _waitForEventResetEvent; + + internal ArduinoGpioControllerDriver(ArduinoBoard arduinoBoard, List supportedPinConfigurations) + { + _arduinoBoard = arduinoBoard ?? throw new ArgumentNullException(nameof(arduinoBoard)); + _supportedPinConfigurations = supportedPinConfigurations ?? throw new ArgumentNullException(nameof(supportedPinConfigurations)); + _callbackContainers = new Dictionary(); + _waitForEventResetEvent = new AutoResetEvent(false); + _callbackContainersLock = new object(); + _pinModes = new ConcurrentDictionary(); + + PinCount = _supportedPinConfigurations.Count; + _arduinoBoard.Firmata.DigitalPortValueUpdated += FirmataOnDigitalPortValueUpdated; + } + + protected override int PinCount { get; } + + /// + /// Arduino does not distinguish between logical and physical numbers, so this always returns identity + /// + protected override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber) + { + return pinNumber; + } + + protected override void OpenPin(int pinNumber) + { + } + + protected override void ClosePin(int pinNumber) + { + _pinModes.TryRemove(pinNumber, out _); + } + + protected override void SetPinMode(int pinNumber, PinMode mode) + { + SupportedMode firmataMode; + switch (mode) + { + case PinMode.Output: + firmataMode = SupportedMode.DIGITAL_OUTPUT; + break; + case PinMode.InputPullUp: + firmataMode = SupportedMode.INPUT_PULLUP; + break; + case PinMode.Input: + firmataMode = SupportedMode.DIGITAL_INPUT; + break; + default: + throw new NotSupportedException($"Mode {mode} is not supported for this operation"); + } + + var pinConfig = _supportedPinConfigurations.FirstOrDefault(x => x.Pin == pinNumber); + if (pinConfig == null || !pinConfig.PinModes.Contains(firmataMode)) + { + throw new NotSupportedException($"Mode {mode} is not supported on Pin {pinNumber}."); + } + + _arduinoBoard.Firmata.SetPinMode(pinNumber, firmataMode); + + // Cache this value. Since the GpioController calls GetPinMode when trying to write a pin (to verify whether it is set to output), + // that would be very expensive here. And setting output pins should be cheap. + _pinModes[pinNumber] = mode; + } + + protected override PinMode GetPinMode(int pinNumber) + { + if (_pinModes.TryGetValue(pinNumber, out var existingValue)) + { + return existingValue; + } + + SupportedMode mode = _arduinoBoard.Firmata.GetPinMode(pinNumber); + + PinMode ret; + switch (mode) + { + case SupportedMode.DIGITAL_OUTPUT: + ret = PinMode.Output; + break; + case SupportedMode.INPUT_PULLUP: + ret = PinMode.InputPullUp; + break; + case SupportedMode.DIGITAL_INPUT: + ret = PinMode.Input; + break; + default: + ret = PinMode.Input; // TODO: Return "Unknown" + break; + } + + _pinModes[pinNumber] = ret; + return ret; + } + + protected override bool IsPinModeSupported(int pinNumber, PinMode mode) + { + SupportedMode firmataMode; + switch (mode) + { + case PinMode.Output: + firmataMode = SupportedMode.DIGITAL_OUTPUT; + break; + case PinMode.InputPullUp: + firmataMode = SupportedMode.INPUT_PULLUP; + break; + case PinMode.Input: + firmataMode = SupportedMode.DIGITAL_INPUT; + break; + default: + return false; + } + + var pinConfig = _supportedPinConfigurations.FirstOrDefault(x => x.Pin == pinNumber); + if (pinConfig == null || !pinConfig.PinModes.Contains(firmataMode)) + { + return false; + } + + return true; + } + + protected override PinValue Read(int pinNumber) + { + return _arduinoBoard.Firmata.GetDigitalPinState(pinNumber); + } + + protected override void Write(int pinNumber, PinValue value) + { + _arduinoBoard.Firmata.WriteDigitalPin(pinNumber, value); + } + + protected override WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken) + { + PinEventTypes eventSeen = PinEventTypes.None; + + void WaitForEventPortValueUpdated(int pin, PinValue newvalue) + { + if (pin == pinNumber) + { + if ((eventTypes & PinEventTypes.Rising) == PinEventTypes.Rising && newvalue == PinValue.High) + { + eventSeen = PinEventTypes.Rising; + _waitForEventResetEvent.Set(); + } + else if ((eventTypes & PinEventTypes.Falling) == PinEventTypes.Falling && newvalue == PinValue.Low) + { + eventSeen = PinEventTypes.Falling; + _waitForEventResetEvent.Set(); + } + } + } + + _arduinoBoard.Firmata.DigitalPortValueUpdated += WaitForEventPortValueUpdated; + try + { + WaitHandle.WaitAny(new[] { cancellationToken.WaitHandle, _waitForEventResetEvent }); + if (cancellationToken.IsCancellationRequested) + { + return new WaitForEventResult() + { + EventTypes = PinEventTypes.None, + TimedOut = true + }; + } + } + finally + { + _arduinoBoard.Firmata.DigitalPortValueUpdated -= WaitForEventPortValueUpdated; + } + + return new WaitForEventResult() + { + EventTypes = eventSeen, + TimedOut = false + }; + } + + protected override void AddCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes eventTypes, PinChangeEventHandler callback) + { + lock (_callbackContainersLock) + { + if (_callbackContainers.TryGetValue(pinNumber, out var cb)) + { + cb.EventTypes = cb.EventTypes | eventTypes; + cb.OnPinChanged += callback; + } + else + { + var cb2 = new CallbackContainer(pinNumber, eventTypes); + cb2.OnPinChanged += callback; + _callbackContainers.Add(pinNumber, cb2); + } + } + } + + protected override void RemoveCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback) + { + lock (_callbackContainersLock) + { + if (_callbackContainers.TryGetValue(pinNumber, out var cb)) + { + cb.OnPinChanged -= callback; + if (cb.NoEventsConnected) + { + _callbackContainers.Remove(pinNumber); + } + } + } + } + + private void FirmataOnDigitalPortValueUpdated(int pin, PinValue newvalue) + { + CallbackContainer cb = null; + PinEventTypes eventTypeToFire = PinEventTypes.None; + lock (_callbackContainersLock) + { + if (_callbackContainers.TryGetValue(pin, out cb)) + { + if (newvalue == PinValue.High && cb.EventTypes.HasFlag(PinEventTypes.Rising)) + { + eventTypeToFire = PinEventTypes.Rising; + } + else if (newvalue == PinValue.Low && cb.EventTypes.HasFlag(PinEventTypes.Falling)) + { + eventTypeToFire = PinEventTypes.Falling; + } + } + } + + if (eventTypeToFire != PinEventTypes.None && cb != null) + { + cb.FireOnPinChanged(eventTypeToFire); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + lock (_callbackContainersLock) + { + _callbackContainers.Clear(); + } + + _arduinoBoard.Firmata.DigitalPortValueUpdated -= FirmataOnDigitalPortValueUpdated; + } + + base.Dispose(disposing); + } + + private class CallbackContainer + { + public CallbackContainer(int pinNumber, PinEventTypes eventTypes) + { + PinNumber = pinNumber; + EventTypes = eventTypes; + } + + public event PinChangeEventHandler OnPinChanged; + + public int PinNumber { get; } + + public PinEventTypes EventTypes + { + get; + set; + } + + public bool NoEventsConnected + { + get + { + return OnPinChanged == null; + } + } + + public void FireOnPinChanged(PinEventTypes eventType) + { + // Copy event instance, prevents problems when elements are added or removed at the same time + var threadSafeCopy = OnPinChanged; + threadSafeCopy?.Invoke(PinNumber, new PinValueChangedEventArgs(eventType, PinNumber)); + } + } + } +} diff --git a/src/devices/Arduino/ArduinoI2cDevice.cs b/src/devices/Arduino/ArduinoI2cDevice.cs new file mode 100644 index 0000000000..7e73554afb --- /dev/null +++ b/src/devices/Arduino/ArduinoI2cDevice.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Device.Gpio.I2c; +using System.Device.I2c; +using System.Linq; +using System.Text; + +#pragma warning disable CS1591 +namespace Iot.Device.Arduino +{ + /// + /// Implementation of an I2C device connected to an arduino using the Firmata protocol + /// + internal class ArduinoI2cDevice : I2cDevice + { + private readonly ArduinoBoard _board; + + public ArduinoI2cDevice(ArduinoBoard board, I2cConnectionSettings connectionSettings) + { + _board = board ?? throw new ArgumentNullException(nameof(board)); + + if (connectionSettings == null) + { + throw new ArgumentNullException(nameof(connectionSettings)); + } + + if (connectionSettings.BusId != 0) + { + throw new NotSupportedException("Only I2C Bus 0 is supported"); + } + + if (connectionSettings.DeviceAddress > 127) + { + throw new NotSupportedException("The device address must be less than 128."); + } + + ConnectionSettings = connectionSettings; + + // Ensure the corresponding pins are set to I2C (not strictly necessary, but consistent) + foreach (SupportedPinConfiguration supportedPinConfiguration in _board.SupportedPinConfigurations.Where(x => x.PinModes.Contains(SupportedMode.I2C))) + { + _board.Firmata.SetPinMode(supportedPinConfiguration.Pin, SupportedMode.I2C); + } + + _board.Firmata.SendI2cConfigCommand(); + + // Sometimes, the very first I2C command fails (nothing happens), so try reading a byte + int retries = 3; + while (retries-- > 0) + { + try + { + ReadByte(); + break; + } + catch (Exception x) when (x is TimeoutException || x is I2cCommunicationException) + { + } + } + + // If the above still failed, there's probably no device on the other end. But this shall not throw here but only if + // the client calls ReadByte. + } + + public override I2cConnectionSettings ConnectionSettings + { + get; + } + + public override byte ReadByte() + { + Span bytes = stackalloc byte[1]; + bytes[0] = 0; + _board.Firmata.WriteReadI2cData(ConnectionSettings.DeviceAddress, null, bytes); + return bytes[0]; + } + + public override void Read(Span buffer) + { + _board.Firmata.WriteReadI2cData(ConnectionSettings.DeviceAddress, null, buffer); + } + + public override void WriteByte(byte value) + { + Span bytes = stackalloc byte[1]; + bytes[0] = value; + _board.Firmata.WriteReadI2cData(ConnectionSettings.DeviceAddress, bytes, null); + } + + public override void Write(ReadOnlySpan buffer) + { + _board.Firmata.WriteReadI2cData(ConnectionSettings.DeviceAddress, buffer, null); + } + + public override void WriteRead(ReadOnlySpan writeBuffer, Span readBuffer) + { + _board.Firmata.WriteReadI2cData(ConnectionSettings.DeviceAddress, writeBuffer, readBuffer); + } + } +} diff --git a/src/devices/Arduino/ArduinoMethodDecl.cs b/src/devices/Arduino/ArduinoMethodDecl.cs new file mode 100644 index 0000000000..bc03617714 --- /dev/null +++ b/src/devices/Arduino/ArduinoMethodDecl.cs @@ -0,0 +1,83 @@ +using System; +using System.Reflection; + +#pragma warning disable CS1591 +namespace Iot.Device.Arduino +{ + public sealed class ArduinoMethodDeclaration + { + public ArduinoMethodDeclaration(int index, MethodInfo methodInfo) + { + Index = index; + MethodInfo = methodInfo; + Flags = MethodFlags.None; + var body = methodInfo.GetMethodBody(); + Token = methodInfo.MetadataToken; + MaxLocals = body.LocalVariables.Count; + MaxStack = body.MaxStackSize; + ArgumentCount = methodInfo.GetParameters().Length; + if (methodInfo.CallingConvention.HasFlag(CallingConventions.HasThis)) + { + ArgumentCount += 1; + } + + if (methodInfo.IsStatic) + { + Flags |= MethodFlags.Static; + } + + if (methodInfo.IsVirtual) + { + Flags |= MethodFlags.Virtual; + } + + if (methodInfo.ReturnParameter == null || methodInfo.ReturnParameter.ParameterType == typeof(void)) + { + Flags |= MethodFlags.Void; + } + } + + public ArduinoMethodDeclaration(int index, int token, MethodInfo methodInfo, MethodFlags flags, int maxStack) + { + Index = index; + Token = token; + MethodInfo = methodInfo; + Flags = flags; + MaxLocals = MaxStack = maxStack; + ArgumentCount = methodInfo.GetParameters().Length; + if (methodInfo.CallingConvention.HasFlag(CallingConventions.HasThis)) + { + ArgumentCount += 1; + } + + if (methodInfo.ReturnParameter.ParameterType == typeof(void)) + { + Flags |= MethodFlags.Void; + } + } + + public int Index { get; } + public int Token { get; } + public MethodInfo MethodInfo { get; } + + public MethodFlags Flags + { + get; + } + + public int MaxLocals + { + get; + } + + public int MaxStack + { + get; + } + + public int ArgumentCount + { + get; + } + } +} diff --git a/src/devices/Arduino/ArduinoPwmChannel.cs b/src/devices/Arduino/ArduinoPwmChannel.cs new file mode 100644 index 0000000000..38d454b979 --- /dev/null +++ b/src/devices/Arduino/ArduinoPwmChannel.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Device.Pwm; +using System.Linq; +using System.Text; + +namespace Iot.Device.Arduino +{ + internal class ArduinoPwmChannel : PwmChannel + { + private readonly ArduinoBoard _board; + // The digital pin used + private readonly int _pin; + private double _dutyCycle; + private int _frequency; + private bool _enabled; + + /// + /// Create a PWM Channel on the Arduino. Depending on the board, it has 4-8 pins that support PWM. + /// This expects to take the normal pin number (i.e. D6, D9) as input + /// + /// Reference to Board + /// Always needs to be 0 + /// See above. Valid values depend on the board. + /// This parameter is ignored and exists only for compatibility + /// PWM duty cycle (0 - 1) + public ArduinoPwmChannel(ArduinoBoard board, + int chip, + int channel, + int frequency = 400, + double dutyCyclePercentage = 0.5) + { + Chip = chip; + if (chip != 0) + { + throw new NotSupportedException("No such chip: {0}"); + } + + Channel = channel; + _pin = channel; + var caps = board.SupportedPinConfigurations.FirstOrDefault(x => x.Pin == _pin); + if (caps == null || !caps.PinModes.Contains(SupportedMode.PWM)) + { + throw new NotSupportedException($"Pin {_pin} does not support PWM"); + } + + _frequency = frequency; + _dutyCycle = dutyCyclePercentage; + _board = board; + _enabled = false; + } + + public int Chip + { + get; + } + + public int Channel + { + get; + } + + /// + /// Setting the frequency is not supported on the Arduino. + /// Therefore, this property has no effect. + /// + public override int Frequency + { + get => _frequency; + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, "Value must not be negative."); + } + + _frequency = value; + Update(); + } + } + + /// + public override double DutyCycle + { + get => _dutyCycle; + set + { + if (value < 0.0 || value > 1.0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, "Value must be between 0.0 and 1.0."); + } + + _dutyCycle = value; + Update(); + } + } + + /// + public override void Start() + { + _enabled = true; + _board.Firmata.SetPinMode(_pin, SupportedMode.PWM); + Update(); + } + + /// + public override void Stop() + { + _enabled = false; + _board.Firmata.SetPinMode(_pin, SupportedMode.DIGITAL_INPUT); + Update(); + } + + private void Update() + { + if (_enabled) + { + _board.Firmata.SetPwmChannel(_pin, _dutyCycle); + } + else + { + _board.Firmata.SetPwmChannel(_pin, 0); + } + } + + protected override void Dispose(bool disposing) + { + Stop(); + base.Dispose(disposing); + } + } +} diff --git a/src/devices/Arduino/ArduinoSpiDevice.cs b/src/devices/Arduino/ArduinoSpiDevice.cs new file mode 100644 index 0000000000..b59b98548d --- /dev/null +++ b/src/devices/Arduino/ArduinoSpiDevice.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Device.Spi; +using System.Text; + +namespace Iot.Device.Arduino +{ + internal sealed class ArduinoSpiDevice : SpiDevice + { + public ArduinoSpiDevice(ArduinoBoard board, SpiConnectionSettings connectionSettings) + { + Board = board; + ConnectionSettings = connectionSettings; + board.EnableSpi(); + board.Firmata.ConfigureSpiDevice(connectionSettings); + } + + public ArduinoBoard Board + { + get; + private set; + } + + public override SpiConnectionSettings ConnectionSettings { get; } + public override byte ReadByte() + { + Span dummy = stackalloc byte[1]; + Read(dummy); + return dummy[0]; + } + + public override void Read(Span buffer) + { + ReadOnlySpan dummy = stackalloc byte[buffer.Length]; + Board.Firmata.SpiTransfer(ConnectionSettings.ChipSelectLine, dummy, buffer); + } + + public override void WriteByte(byte value) + { + ReadOnlySpan span = stackalloc byte[1] + { + value + }; + + Write(span); + } + + public override void Write(ReadOnlySpan buffer) + { + Board.Firmata.SpiWrite(ConnectionSettings.ChipSelectLine, buffer); + } + + public override void TransferFullDuplex(ReadOnlySpan writeBuffer, Span readBuffer) + { + Board.Firmata.SpiTransfer(ConnectionSettings.ChipSelectLine, writeBuffer, readBuffer); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (Board != null) + { + Board.DisableSpi(); + // To make sure this is called only once (and any further attempts to use this instance fail) + Board = null; + } + } + + base.Dispose(disposing); + } + } +} diff --git a/src/devices/Arduino/ArduinoTask.cs b/src/devices/Arduino/ArduinoTask.cs new file mode 100644 index 0000000000..72cec915ff --- /dev/null +++ b/src/devices/Arduino/ArduinoTask.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +#pragma warning disable CS1591 +namespace Iot.Device.Arduino +{ + public interface IArduinoTask : IDisposable + { + MethodState State { get; } + ArduinoMethodDeclaration MethodInfo { get; } + void AddData(MethodState state, object[] args); + } + + public sealed class ArduinoTask : IArduinoTask, IDisposable + where T : Delegate + { + private ConcurrentQueue<(MethodState, object[])> _collectedValues; + private AutoResetEvent _dataAdded; + internal ArduinoTask(T function, ArduinoCsCompiler compiler, ArduinoMethodDeclaration methodInfo) + { + Function = function; + Compiler = compiler; + MethodInfo = methodInfo; + State = MethodState.Stopped; + _collectedValues = new ConcurrentQueue<(MethodState, object[])>(); + _dataAdded = new AutoResetEvent(false); + } + + public T Function { get; } + public ArduinoCsCompiler Compiler { get; } + public ArduinoMethodDeclaration MethodInfo { get; } + + /// + /// Returns the current state of the task + /// + public MethodState State + { + get; + private set; + } + + public void AddData(MethodState state, object[] args) + { + _collectedValues.Enqueue((state, args)); + State = state; + _dataAdded.Set(); + } + + public void InvokeAsync(params int[] arguments) + { + if (State == MethodState.Running) + { + throw new InvalidOperationException("Task is already running"); + } + + State = MethodState.Running; + Compiler.Invoke(MethodInfo.MethodInfo, arguments); + } + + public bool Invoke(CancellationToken cancellationToken, params int[] arguments) + { + InvokeAsync(arguments); + Task task = WaitForResult(cancellationToken); + task.Wait(cancellationToken); + return task.Result; + } + + public void Terminate() + { + Compiler.KillTask(MethodInfo.MethodInfo); + } + + /// + /// Returns a data set obtained from the realtime method. + /// If this returns false, no data is available. + /// If this returns true, the next data set is returned, together with the state of the task at that point. + /// If the returned state is , the data returned is the return value of the method. + /// + /// A set of values sent or returned by the task method + /// The state of the method matching the task at that time + /// True if data was available, false otherwise + public bool GetMethodResults(out object[] data, out MethodState state) + { + if (_collectedValues.TryDequeue(out var d)) + { + data = d.Item2; + state = d.Item1; + return true; + } + + data = null; + state = State; + return false; + } + + public void Dispose() + { + Compiler.TaskDone(this); + _collectedValues.Clear(); + _dataAdded?.Dispose(); + _dataAdded = null; + } + + /// + /// Await at least one result data set (or the end of the method) + /// + /// True if there is at least one data set, false otherwise + public async Task WaitForResult(CancellationToken token) + { + if (_dataAdded == null) + { + throw new ObjectDisposedException(nameof(ArduinoTask)); + } + + if (State != MethodState.Running) + { + return false; + } + + var task = Task.Factory.StartNew(() => + { + while (_collectedValues.IsEmpty && State == MethodState.Running) + { + if (token.IsCancellationRequested) + { + return false; + } + + WaitHandle.WaitAny(new WaitHandle[] + { + token.WaitHandle, _dataAdded + }); + } + + return !_collectedValues.IsEmpty; + }); + + await task; + return task.Result; + } + + public bool WaitForResult() + { + var task = WaitForResult(CancellationToken.None); + task.Wait(); + return task.Result; + } + } +} diff --git a/src/devices/Arduino/CommonFirmataFeatures.ino b/src/devices/Arduino/CommonFirmataFeatures.ino new file mode 100644 index 0000000000..3cbe09f9c3 --- /dev/null +++ b/src/devices/Arduino/CommonFirmataFeatures.ino @@ -0,0 +1,102 @@ +/* + * CommonFirmataFeatures.ino generated by FirmataBuilder + * Sun Mar 29 2020 15:10:48 GMT-0400 (EDT) + */ + +#include + +#include +DigitalInputFirmata digitalInput; + +#include +DigitalOutputFirmata digitalOutput; + +#include +AnalogInputFirmata analogInput; + +#include +AnalogOutputFirmata analogOutput; + +#include +#include +I2CFirmata i2c; + +#include +OneWireFirmata oneWire; + +#include +SerialFirmata serial; + +#include +FirmataExt firmataExt; + +#include + +// The scheduler allows to store scripts on the board, however this requires a kind of compiler on the client side. +// The feature only needs 20Bytes of Ram, so it doesn't hurt to have it (There's enough flash left) +#include +FirmataScheduler scheduler; + +#include +FirmataReporting reporting; + +void systemResetCallback() +{ + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_PIN_ANALOG(i)) { + Firmata.setPinMode(i, ANALOG); + } else if (IS_PIN_DIGITAL(i)) { + Firmata.setPinMode(i, OUTPUT); + } + } + firmataExt.reset(); +} + +void initTransport() +{ + // Uncomment to save a couple of seconds by disabling the startup blink sequence. + // Firmata.disableBlinkVersion(); + Firmata.begin(115200); +} + +void initFirmata() +{ + Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION); + + firmataExt.addFeature(digitalInput); + firmataExt.addFeature(digitalOutput); + firmataExt.addFeature(analogInput); + firmataExt.addFeature(analogOutput); + firmataExt.addFeature(i2c); + firmataExt.addFeature(oneWire); + firmataExt.addFeature(serial); + firmataExt.addFeature(scheduler); + firmataExt.addFeature(reporting); + + Firmata.attach(SYSTEM_RESET, systemResetCallback); +} + +void setup() +{ + initFirmata(); + + initTransport(); + + Firmata.parse(SYSTEM_RESET); +} + +void loop() +{ + digitalInput.report(); + + while(Firmata.available()) { + Firmata.processInput(); + } + + if (reporting.elapsed()) { + analogInput.report(); + i2c.report(); + } + + serial.update(); +} diff --git a/src/devices/Arduino/DebugLogStream.cs b/src/devices/Arduino/DebugLogStream.cs new file mode 100644 index 0000000000..01a7668203 --- /dev/null +++ b/src/devices/Arduino/DebugLogStream.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +#pragma warning disable CS1591 +namespace Iot.Device.Arduino +{ + public class DebugLogStream : Stream + { + private readonly Stream _streamImplementation; + private readonly TextWriter _logStream; + private int _bytesWrittenToLog; + + public override bool CanRead => _streamImplementation.CanRead; + + public override bool CanSeek => _streamImplementation.CanSeek; + + public override bool CanWrite => _streamImplementation.CanWrite; + + public override long Length => _streamImplementation.Length; + + public override long Position + { + get => _streamImplementation.Position; + set => _streamImplementation.Position = value; + } + + public DebugLogStream(Stream implementation, TextWriter logStream) + { + _streamImplementation = implementation; + _logStream = logStream; + _bytesWrittenToLog = 0; + } + + public override void Flush() + { + _streamImplementation.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _streamImplementation.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _streamImplementation.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _streamImplementation.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + foreach (byte b in buffer) + { + _logStream.Write(string.Format(CultureInfo.InvariantCulture, "0x{0:X2}, ", b)); + _bytesWrittenToLog++; + if (_bytesWrittenToLog % 16 == 0) + { + _logStream.WriteLine(); + } + } + + _streamImplementation.Write(buffer, offset, count); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _streamImplementation.Dispose(); + _logStream.Dispose(); + } + + base.Dispose(disposing); + } + } +} diff --git a/src/devices/Arduino/FirmataCommand.cs b/src/devices/Arduino/FirmataCommand.cs new file mode 100644 index 0000000000..29380e4460 --- /dev/null +++ b/src/devices/Arduino/FirmataCommand.cs @@ -0,0 +1,59 @@ +namespace Iot.Device.Arduino +{ + /// + /// Primary firmata commands + /// + public enum FirmataCommand : byte + { + /// + /// Digital pins have changed + /// + DIGITAL_MESSAGE = 144, // 0x00000090 + + /// + /// Request a report on a changing analog pin + /// + REPORT_ANALOG_PIN = 192, // 0x000000C0 + + /// + /// Request a report on changing digital ports + /// Note that digital pins are grouped to 8-byte ports + /// + REPORT_DIGITAL_PIN = 208, // 0x000000D0 + + /// + /// Analog pin state + /// + ANALOG_MESSAGE = 224, // 0x000000E0 + + /// + /// Start an extended message + /// + START_SYSEX = 240, // 0x000000F0 + + /// + /// Set the input/output mode of a pin + /// + SET_PIN_MODE = 244, // 0x000000F4 + + /// + /// Set the value of a digital pin + /// + SET_DIGITAL_VALUE = 0xF5, + + /// + /// End an extended message + /// + END_SYSEX = 247, // 0x000000F7 + + /// + /// Protocol version request/reply + /// + PROTOCOL_VERSION = 249, // 0x000000F9 + + /// + /// System was reset + /// + SYSTEM_RESET = 255 // 0x000000FF + } +} diff --git a/src/devices/Arduino/FirmataDevice.cs b/src/devices/Arduino/FirmataDevice.cs new file mode 100644 index 0000000000..211757625b --- /dev/null +++ b/src/devices/Arduino/FirmataDevice.cs @@ -0,0 +1,1285 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Device.Gpio; +using System.Device.Gpio.I2c; +using System.Device.Spi; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UnitsNet; + +#pragma warning disable CS1591 +namespace Iot.Device.Arduino +{ + public delegate void DigitalPinValueChanged(int pin, PinValue newValue); + + internal delegate void AnalogPinValueUpdated(int pin, uint rawValue); + + internal sealed class FirmataDevice : IDisposable + { + private const byte FIRMATA_PROTOCOL_MAJOR_VERSION = 2; + private const byte FIRMATA_PROTOCOL_MINOR_VERSION = 5; // 2.5 works, but 2.6 is recommended + private const int FIRMATA_INIT_TIMEOUT_SECONDS = 4; + private static readonly TimeSpan DefaultReplyTimeout = TimeSpan.FromMilliseconds(500); + + private byte _firmwareVersionMajor; + private byte _firmwareVersionMinor; + private byte _actualFirmataProtocolMajorVersion; + private byte _actualFirmataProtocolMinorVersion; + + private int _lastRequestId; + + private string _firmwareName; + private Stream _firmataStream; + private Thread _inputThread; + private bool _inputThreadShouldExit; + private List _supportedPinConfigurations; + private IList _lastResponse; + private List _lastPinValues; + private Dictionary _lastAnalogValues; + private object _lastPinValueLock; + private object _lastAnalogValueLock; + private object _synchronisationLock; + private Queue _dataQueue; + + private byte _lastIlExecutionError; + + // Event used when waiting for answers (i.e. after requesting firmware version) + private AutoResetEvent _dataReceived; + public event Action> OnSchedulerReply; + + public event DigitalPinValueChanged DigitalPortValueUpdated; + + public event AnalogPinValueUpdated AnalogPinValueUpdated; + + public event Action OnError; + + public FirmataDevice() + { + _firmwareVersionMajor = 0; + _firmwareVersionMinor = 0; + _firmataStream = null; + _inputThreadShouldExit = false; + _dataReceived = new AutoResetEvent(false); + _supportedPinConfigurations = new List(); + _synchronisationLock = new object(); + _lastPinValues = new List(); + _lastPinValueLock = new object(); + _lastAnalogValues = new Dictionary(); + _lastAnalogValueLock = new object(); + _dataQueue = new Queue(); + _lastRequestId = 1; + _lastIlExecutionError = 0; + } + + internal List PinConfigurations + { + get + { + return _supportedPinConfigurations; + } + } + + public void Open(Stream stream) + { + _firmataStream = stream; + if (_firmataStream.CanRead && _firmataStream.CanWrite) + { + StartListening(); + } + else + { + throw new NotSupportedException("Need a read-write stream to the hardware device"); + } + } + + public void Close() + { + StopThread(); + lock (_synchronisationLock) + { + if (_firmataStream != null) + { + _firmataStream.Close(); + } + + _firmataStream = null; + } + + if (_dataReceived != null) + { + _dataReceived.Dispose(); + _dataReceived = null; + } + } + + /// + /// Used where? + /// + private void SendString(byte command, string message) + { + byte[] bytes = Encoding.Unicode.GetBytes(message); + lock (_synchronisationLock) + { + _firmataStream.WriteByte(240); + _firmataStream.WriteByte((byte)(command & (uint)sbyte.MaxValue)); + SendValuesAsTwo7bitBytes(bytes); + _firmataStream.WriteByte(247); + _firmataStream.Flush(); + } + } + + private void StartListening() + { + if (_inputThread != null && _inputThread.IsAlive) + { + return; + } + + _inputThreadShouldExit = false; + + _inputThread = new Thread(InputThread); + _inputThread.Start(); + + // Reset device, in case it is still sending data from an aborted process + _firmataStream.WriteByte((byte)FirmataCommand.SYSTEM_RESET); + } + + private void ProcessInput() + { + if (_dataQueue.Count == 0) + { + FillQueue(); + } + + if (_dataQueue.Count == 0) + { + // Still no data? (End of stream or stream closed) + return; + } + + int data = _dataQueue.Dequeue(); + + // OnError?.Invoke($"0x{data:X}", null); + byte b = (byte)(data & 0x00FF); + byte upper_nibble = (byte)(data & 0xF0); + byte lower_nibble = (byte)(data & 0x0F); + + /* + * the relevant bits in the command depends on the value of the data byte. If it is less than 0xF0 (start sysex), only the upper nibble identifies the command + * while the lower nibble contains additional data + */ + FirmataCommand command = (FirmataCommand)((data < ((ushort)FirmataCommand.START_SYSEX) ? upper_nibble : b)); + + // determine the number of bytes remaining in the message + int bytes_remaining = 0; + bool isMessageSysex = false; + switch (command) + { + default: // command not understood + case FirmataCommand.END_SYSEX: // should never happen + return; + + // commands that require 2 additional bytes + case FirmataCommand.DIGITAL_MESSAGE: + case FirmataCommand.ANALOG_MESSAGE: + case FirmataCommand.SET_PIN_MODE: + case FirmataCommand.PROTOCOL_VERSION: + bytes_remaining = 2; + break; + + // commands that require 1 additional byte + case FirmataCommand.REPORT_ANALOG_PIN: + case FirmataCommand.REPORT_DIGITAL_PIN: + bytes_remaining = 1; + break; + + // commands that do not require additional bytes + case FirmataCommand.SYSTEM_RESET: + // do nothing, as there is nothing to reset + return; + + case FirmataCommand.START_SYSEX: + // this is a special case with no set number of bytes remaining + isMessageSysex = true; + break; + } + + // read the remaining message while keeping track of elapsed time to timeout in case of incomplete message + List message = new List(); + int bytes_read = 0; + Stopwatch timeout_start = Stopwatch.StartNew(); + while (bytes_remaining > 0 || isMessageSysex) + { + if (_dataQueue.Count == 0) + { + int timeout = 10; + while (!FillQueue() && timeout-- > 0) + { + Thread.Sleep(5); + } + + if (timeout == 0) + { + // Synchronisation problem: The remainder of the expected message is missing + return; + } + } + + data = _dataQueue.Dequeue(); + // OnError?.Invoke($"0x{data:X}", null); + // if no data was available, check for timeout + if (data == 0xFFFF) + { + // get elapsed seconds, given as a double with resolution in nanoseconds + var elapsed = timeout_start.Elapsed; + + if (elapsed > DefaultReplyTimeout) + { + return; + } + + continue; + } + + timeout_start.Restart(); + + // if we're parsing sysex and we've just read the END_SYSEX command, we're done. + if (isMessageSysex && (data == (short)FirmataCommand.END_SYSEX)) + { + break; + } + + message.Add((byte)(data & 0xFF)); + ++bytes_read; + --bytes_remaining; + } + + // process the message + switch (command) + { + // ignore these message types (they should not be in a reply) + default: + case FirmataCommand.REPORT_ANALOG_PIN: + case FirmataCommand.REPORT_DIGITAL_PIN: + case FirmataCommand.SET_PIN_MODE: + case FirmataCommand.END_SYSEX: + case FirmataCommand.SYSTEM_RESET: + return; + case FirmataCommand.PROTOCOL_VERSION: + if (_actualFirmataProtocolMajorVersion != 0) + { + // Firmata sends this message automatically after a device reset (if you press the reset button on the arduino) + // If we know the version already, this is unexpected. + OnError?.Invoke("The device was unexpectedly reset. Please restart the communication.", null); + } + + _actualFirmataProtocolMajorVersion = message[0]; + _actualFirmataProtocolMinorVersion = message[1]; + _dataReceived.Set(); + + return; + + case FirmataCommand.ANALOG_MESSAGE: + // report analog commands store the pin number in the lower nibble of the command byte, the value is split over two 7-bit bytes + // AnalogValueUpdated(this, + // new CallbackEventArgs(lower_nibble, (ushort)(message[0] | (message[1] << 7)))); + { + int pin = lower_nibble; + uint value = (uint)(message[0] | (message[1] << 7)); + lock (_lastAnalogValueLock) + { + _lastAnalogValues[pin] = value; + } + + AnalogPinValueUpdated?.Invoke(pin, value); + } + + break; + + case FirmataCommand.DIGITAL_MESSAGE: + // digital messages store the port number in the lower nibble of the command byte, the port value is split over two 7-bit bytes + // Each port corresponds to 8 pins + { + int offset = lower_nibble * 8; + ushort pinValues = (ushort)(message[0] | (message[1] << 7)); + lock (_lastPinValueLock) + { + for (int i = 0; i < 8; i++) + { + PinValue oldValue = _lastPinValues[i + offset]; + int mask = 1 << i; + PinValue newValue = (pinValues & mask) == 0 ? PinValue.Low : PinValue.High; + if (newValue != oldValue) + { + _lastPinValues[i + offset] = newValue; + // TODO: The callback should not be within the lock + DigitalPortValueUpdated?.Invoke(i + offset, newValue); + } + } + } + } + + break; + + case FirmataCommand.START_SYSEX: + // a sysex message must include at least one extended-command byte + if (bytes_read < 1) + { + return; + } + + // retrieve the raw data array & extract the extended-command byte + var raw_data = message.ToArray(); + FirmataSysexCommand sysCommand = (FirmataSysexCommand)(raw_data[0]); + int index = 0; + ++index; + --bytes_read; + + switch (sysCommand) + { + case FirmataSysexCommand.REPORT_FIRMWARE: + // See https://github.com/firmata/protocol/blob/master/protocol.md + // Byte 0 is the command (0x79) and can be skipped here, as we've already interpreted it + { + _firmwareVersionMajor = raw_data[1]; + _firmwareVersionMinor = raw_data[2]; + int stringLength = (raw_data.Length - 3) / 2; + Span bytesReceived = stackalloc byte[stringLength]; + ReassembleByteString(raw_data, 3, stringLength * 2, bytesReceived); + + _firmwareName = Encoding.ASCII.GetString(bytesReceived); + _dataReceived.Set(); + } + + return; + + case FirmataSysexCommand.STRING_DATA: + { + // condense back into 1-byte data + int stringLength = (raw_data.Length - 1) / 2; + Span bytesReceived = stackalloc byte[stringLength]; + ReassembleByteString(raw_data, 1, stringLength * 2, bytesReceived); + + string message1 = Encoding.ASCII.GetString(bytesReceived); + int idxNull = message1.IndexOf('\0'); + if (message1.Contains("%") && idxNull > 0) // C style printf formatters + { + message1 = message1.Substring(0, idxNull); + string message2 = PrintfFromByteStream(message1, bytesReceived, idxNull + 1); + OnError?.Invoke(message2, null); + } + else + { + OnError?.Invoke(message1, null); + } + } + + break; + + case FirmataSysexCommand.CAPABILITY_RESPONSE: + { + _supportedPinConfigurations.Clear(); + int idx = 1; + var currentPin = new SupportedPinConfiguration(0); + int pin = 0; + while (idx < raw_data.Length) + { + int mode = raw_data[idx++]; + if (mode == 0x7F) + { + _supportedPinConfigurations.Add(currentPin); + currentPin = new SupportedPinConfiguration(++pin); + continue; + } + + int resolution = raw_data[idx++]; + switch ((SupportedMode)mode) + { + default: + currentPin.PinModes.Add((SupportedMode)mode); + break; + case SupportedMode.ANALOG_INPUT: + currentPin.PinModes.Add(SupportedMode.ANALOG_INPUT); + currentPin.AnalogInputResolutionBits = resolution; + break; + case SupportedMode.PWM: + currentPin.PinModes.Add(SupportedMode.PWM); + currentPin.PwmResolutionBits = resolution; + break; + } + } + + // Add 8 entries, so that later we do not need to check whether a port (bank) is complete + _lastPinValues = new PinValue[_supportedPinConfigurations.Count + 8].ToList(); + _dataReceived.Set(); + // Do not add the last instance, should also be terminated by 0xF7 + } + + break; + + case FirmataSysexCommand.ANALOG_MAPPING_RESPONSE: + { + // This needs to have been set up previously + if (_supportedPinConfigurations.Count == 0) + { + return; + } + + int idx = 1; + int pin = 0; + while (idx < raw_data.Length) + { + if (raw_data[idx] != 127) + { + _supportedPinConfigurations[pin].AnalogPinNumber = raw_data[idx]; + } + + idx++; + pin++; + } + + _dataReceived.Set(); + } + + break; + case FirmataSysexCommand.I2C_REPLY: + + _lastResponse = raw_data; + _dataReceived.Set(); + break; + + case FirmataSysexCommand.SPI_DATA: + _lastResponse = raw_data; + _dataReceived.Set(); + break; + + case FirmataSysexCommand.DHT_SENSOR_DATA_REQUEST: + case FirmataSysexCommand.PIN_STATE_RESPONSE: + _lastResponse = raw_data; // the instance is constant, so we can just remember the pointer + _dataReceived.Set(); + break; + + case FirmataSysexCommand.SCHEDULER_DATA: + { + if (raw_data.Length == 4 && raw_data[1] == (byte)ExecutorCommand.Ack) + { + // Just an ack for a programming command. + _lastIlExecutionError = 0; + _dataReceived.Set(); + return; + } + + if (raw_data.Length == 4 && raw_data[1] == (byte)ExecutorCommand.Nack) + { + // This is a Nack + _lastIlExecutionError = raw_data[3]; + _dataReceived.Set(); + return; + } + + // Data from real-time methods + if (raw_data.Length < 7) + { + OnError?.Invoke("Code execution returned invalid result or state", null); + break; + } + + int numArgs = raw_data[3]; + Span bytesReceived = stackalloc byte[numArgs * 4]; + ReassembleByteString(raw_data, 4, numArgs * 8, bytesReceived); + + OnSchedulerReply?.Invoke(raw_data[1], (MethodState)raw_data[2], numArgs, bytesReceived.ToArray()); + break; + } + + default: + + // we pass the data forward as-is for any other type of sysex command + break; + } + + break; + } + } + + /// + /// Replaces the first occurrence of search in input with replace. + /// + private static string ReplaceFirst(String input, string search, string replace) + { + int idx = input.IndexOf(search, StringComparison.InvariantCulture); + string output = input.Remove(idx, search.Length); + output = output.Insert(idx, replace); + return output; + } + + /// + /// Simulates a printf C statement. + /// Note that the word size on the arduino is 16 bits, so any argument not specifying an l prefix is considered to + /// be 16 bits only. + /// + /// Format string (with %d, %x, etc) + /// Total bytes received + /// Start of arguments (first byte of formatting parameters) + /// A formatted string + private string PrintfFromByteStream(string fmt, in Span bytesReceived, int startOfArguments) + { + string output = fmt; + while (output.Contains("%")) + { + int idxPercent = output.IndexOf('%'); + string type = output[idxPercent + 1].ToString(); + if (type == "l") + { + type += output[idxPercent + 2]; + } + + switch (type) + { + case "lx": + { + Int32 arg = BitConverter.ToInt32(bytesReceived.ToArray(), startOfArguments); + output = ReplaceFirst(output, "%" + type, arg.ToString("x")); + startOfArguments += 4; + break; + } + + case "x": + { + Int16 arg = BitConverter.ToInt16(bytesReceived.ToArray(), startOfArguments); + output = ReplaceFirst(output, "%" + type, arg.ToString("x")); + startOfArguments += 2; + break; + } + + case "d": + { + Int16 arg = BitConverter.ToInt16(bytesReceived.ToArray(), startOfArguments); + output = ReplaceFirst(output, "%" + type, arg.ToString()); + startOfArguments += 2; + break; + } + + case "ld": + { + Int32 arg = BitConverter.ToInt32(bytesReceived.ToArray(), startOfArguments); + output = ReplaceFirst(output, "%" + type, arg.ToString()); + startOfArguments += 4; + break; + } + } + } + + return output; + } + + private bool FillQueue() + { + Span rawData = stackalloc byte[32]; + int bytesRead = _firmataStream.Read(rawData); + for (int i = 0; i < bytesRead; i++) + { + _dataQueue.Enqueue(rawData[i]); + } + + return _dataQueue.Count > 0; + } + + private void InputThread() + { + while (!_inputThreadShouldExit) + { + try + { + ProcessInput(); + } + catch (Exception ex) + { + OnError?.Invoke($"Firmata protocol error: Parser exception {ex.Message}", ex); + } + } + } + + public Version QueryFirmataVersion() + { + // Try 3 times (because we have to make sure the receiver's input queue is properly synchronized) + for (int i = 0; i < 3; i++) + { + lock (_synchronisationLock) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.PROTOCOL_VERSION); + _firmataStream.Flush(); + bool result = _dataReceived.WaitOne(TimeSpan.FromSeconds(FIRMATA_INIT_TIMEOUT_SECONDS)); + if (result == false) + { + continue; + } + + return new Version(_actualFirmataProtocolMajorVersion, _actualFirmataProtocolMinorVersion); + } + } + + throw new TimeoutException("Timeout waiting for firmata version"); + } + + public Version QuerySupportedFirmataVersion() + { + return new Version(FIRMATA_PROTOCOL_MAJOR_VERSION, FIRMATA_PROTOCOL_MINOR_VERSION); + } + + public Version QueryFirmwareVersion(out string firmwareName) + { + // Try 3 times (because we have to make sure the receiver's input queue is properly synchronized) + for (int i = 0; i < 3; i++) + { + lock (_synchronisationLock) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.REPORT_FIRMWARE); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + bool result = _dataReceived.WaitOne(TimeSpan.FromSeconds(FIRMATA_INIT_TIMEOUT_SECONDS)); + if (result == false) + { + continue; + } + + firmwareName = _firmwareName; + return new Version(_firmwareVersionMajor, _firmwareVersionMinor); + } + } + + throw new TimeoutException("Timeout waiting for firmata firmware version"); + } + + public void QueryCapabilities() + { + lock (_synchronisationLock) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.CAPABILITY_QUERY); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + bool result = _dataReceived.WaitOne(DefaultReplyTimeout); + if (result == false) + { + throw new TimeoutException("Timeout waiting for device capabilities"); + } + + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.ANALOG_MAPPING_QUERY); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + result = _dataReceived.WaitOne(DefaultReplyTimeout); + if (result == false) + { + throw new TimeoutException("Timeout waiting for PWM port mappings"); + } + } + } + + private void StopThread() + { + _inputThreadShouldExit = true; + if (_inputThread != null) + { + _inputThread.Join(); + _inputThread = null; + } + } + + private T PerformRetries(int numberOfRetries, Func operation) + { + Exception lastException = null; + while (numberOfRetries-- > 0) + { + try + { + T result = operation(); + return result; + } + catch (TimeoutException x) + { + lastException = x; + OnError?.Invoke("Timeout waiting for answer. Retries possible.", x); + } + } + + throw new TimeoutException("Timeout waiting for answer. Aborting. ", lastException); + } + + public void SetPinMode(int pin, SupportedMode firmataMode) + { + for (int i = 0; i < 3; i++) + { + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)FirmataCommand.SET_PIN_MODE); + _firmataStream.WriteByte((byte)pin); + _firmataStream.WriteByte((byte)firmataMode); + _firmataStream.Flush(); + } + + if (GetPinMode(pin) == firmataMode) + { + return; + } + } + + throw new TimeoutException($"Unable to set Pin mode to {firmataMode}. Looks like a communication problem."); + } + + public SupportedMode GetPinMode(int pinNumber) + { + return PerformRetries(3, () => + { + lock (_synchronisationLock) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.PIN_STATE_QUERY); + _firmataStream.WriteByte((byte)pinNumber); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + bool result = _dataReceived.WaitOne(DefaultReplyTimeout); + if (result == false) + { + throw new TimeoutException("Timeout waiting for pin mode."); + } + + // The mode is byte 4 + if (_lastResponse.Count < 4) + { + throw new InvalidOperationException("Not enough data in reply"); + } + + if (_lastResponse[1] != pinNumber) + { + throw new InvalidOperationException( + "The reply didn't match the query (another port was indicated)"); + } + + SupportedMode mode = (SupportedMode)(_lastResponse[2]); + return mode; + } + }); + } + + /// + /// Enables digital pin reporting for all ports (one port has 8 pins) + /// + public void EnableDigitalReporting() + { + int numPorts = (int)Math.Ceiling(PinConfigurations.Count / 8.0); + lock (_synchronisationLock) + { + for (byte i = 0; i < numPorts; i++) + { + _firmataStream.WriteByte((byte)(0xD0 + i)); + _firmataStream.WriteByte(1); + _firmataStream.Flush(); + } + } + } + + public PinValue GetDigitalPinState(int pinNumber) + { + lock (_lastPinValueLock) + { + return _lastPinValues[pinNumber]; + } + } + + public void WriteDigitalPin(int pin, PinValue value) + { + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)FirmataCommand.SET_DIGITAL_VALUE); + _firmataStream.WriteByte((byte)pin); + _firmataStream.WriteByte((byte)(value == PinValue.High ? 1 : 0)); + _firmataStream.Flush(); + } + } + + public void SendI2cConfigCommand() + { + lock (_synchronisationLock) + { + // The command is mandatory, even if the argument is typically ignored + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.I2C_CONFIG); + _firmataStream.WriteByte(0); + _firmataStream.WriteByte(0); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + } + } + + public void WriteReadI2cData(int slaveAddress, ReadOnlySpan writeData, Span replyData) + { + // See documentation at https://github.com/firmata/protocol/blob/master/i2c.md + lock (_synchronisationLock) + { + if (writeData != null && writeData.Length > 0) + { + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.I2C_REQUEST); + _firmataStream.WriteByte((byte)slaveAddress); + _firmataStream.WriteByte(0); // Write flag is 0, all other bits as well + SendValuesAsTwo7bitBytes(writeData); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + } + + if (replyData != null && replyData.Length > 0) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.I2C_REQUEST); + _firmataStream.WriteByte((byte)slaveAddress); + _firmataStream.WriteByte(0b1000); // Read flag is 1, all other bits are 0 + byte length = (byte)replyData.Length; + // Only write the length of the expected data. + // We could insert the register to read here, but we assume that has been written already (the client is responsible for that) + _firmataStream.WriteByte((byte)(length & (uint)sbyte.MaxValue)); + _firmataStream.WriteByte((byte)(length >> 7 & sbyte.MaxValue)); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + bool result = _dataReceived.WaitOne(DefaultReplyTimeout); + if (result == false) + { + throw new I2cCommunicationException("Timeout waiting for device reply"); + } + + if (_lastResponse[0] != (byte)FirmataSysexCommand.I2C_REPLY) + { + throw new I2cCommunicationException("Firmata protocol error: received incorrect query response"); + } + + if (_lastResponse[1] != (byte)slaveAddress && slaveAddress != 0) + { + throw new I2cCommunicationException($"Firmata protocol error: The wrong device did answer. Expected {slaveAddress} but got {_lastResponse[1]}."); + } + + // Byte 0: I2C_REPLY + // Bytes 1 & 2: Slave address (the MSB is always 0, since we're only supporting 7-bit addresses) + // Bytes 3 & 4: Register. Often 0, and probably not needed + // Anything after that: reply data, with 2 bytes for each byte in the data stream + int bytesReceived = ReassembleByteString(_lastResponse, 5, _lastResponse.Count - 5, replyData); + + if (replyData.Length != bytesReceived) + { + throw new I2cCommunicationException($"Expected {replyData.Length} bytes, got only {bytesReceived}"); + } + } + } + } + + public void SetPwmChannel(int pin, double dutyCycle) + { + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.EXTENDED_ANALOG); + _firmataStream.WriteByte((byte)pin); + // The arduino expects values between 0 and 255 for PWM channels. + // The frequency cannot be set. + int pwmMaxValue = _supportedPinConfigurations[pin].PwmResolutionBits; // This is 8 for most arduino boards + pwmMaxValue = (1 << pwmMaxValue) - 1; + int value = (int)Math.Max(0, Math.Min(dutyCycle * pwmMaxValue, pwmMaxValue)); + _firmataStream.WriteByte((byte)(value & (uint)sbyte.MaxValue)); // lower 7 bits + _firmataStream.WriteByte((byte)(value >> 7 & sbyte.MaxValue)); // top bit (rest unused) + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + } + } + + /// + /// This takes the pin number in Arduino's own Analog numbering scheme. So A0 shall be specifed as 0 + /// + public void EnableAnalogReporting(int pinNumber) + { + lock (_synchronisationLock) + { + _lastAnalogValues[pinNumber] = 0; // to make sure this entry exists + _firmataStream.WriteByte((byte)((int)FirmataCommand.REPORT_ANALOG_PIN + pinNumber)); + _firmataStream.WriteByte((byte)1); + } + } + + public void DisableAnalogReporting(int pinNumber) + { + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)((int)FirmataCommand.REPORT_ANALOG_PIN + pinNumber)); + _firmataStream.WriteByte((byte)0); + } + } + + public void EnableSpi() + { + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SPI_DATA); + _firmataStream.WriteByte((byte)FirmataSpiCommand.SPI_BEGIN); + _firmataStream.WriteByte((byte)0); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + } + } + + public void DisableSpi() + { + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SPI_DATA); + _firmataStream.WriteByte((byte)FirmataSpiCommand.SPI_END); + _firmataStream.WriteByte((byte)0); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + } + } + + public void SpiWrite(int csPin, ReadOnlySpan writeBytes) + { + lock (_synchronisationLock) + { + SpiWrite(csPin, FirmataSpiCommand.SPI_WRITE, writeBytes); + } + } + + public void SpiTransfer(int csPin, ReadOnlySpan writeBytes, Span readBytes) + { + lock (_synchronisationLock) + { + _dataReceived.Reset(); + byte requestId = SpiWrite(csPin, FirmataSpiCommand.SPI_TRANSFER, writeBytes); + bool result = _dataReceived.WaitOne(DefaultReplyTimeout); + if (result == false) + { + throw new I2cCommunicationException("Timeout waiting for device reply"); + } + + if (_lastResponse[0] != (byte)FirmataSysexCommand.SPI_DATA || _lastResponse[1] != (byte)FirmataSpiCommand.SPI_REPLY) + { + throw new I2cCommunicationException("Firmata protocol error: received incorrect query response"); + } + + if (_lastResponse[3] != (byte)requestId) + { + throw new I2cCommunicationException($"Firmata protocol sequence error."); + } + + ReassembleByteString(_lastResponse, 5, _lastResponse[4] * 2, readBytes); + } + } + + private byte SpiWrite(int csPin, FirmataSpiCommand command, ReadOnlySpan writeBytes) + { + byte requestId = (byte)(_lastRequestId++ & 0x7F); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SPI_DATA); + _firmataStream.WriteByte((byte)command); + _firmataStream.WriteByte((byte)(csPin << 3)); // Device ID / channel + _firmataStream.WriteByte(requestId); + _firmataStream.WriteByte(1); // Deselect CS after transfer (yes) + _firmataStream.WriteByte((byte)writeBytes.Length); + SendValuesAsTwo7bitBytes(writeBytes); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + return requestId; + } + + public void SetSamplingInterval(TimeSpan interval) + { + int millis = (int)interval.TotalMilliseconds; + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SAMPLING_INTERVAL); + int value = millis; + _firmataStream.WriteByte((byte)(value & (uint)sbyte.MaxValue)); // lower 7 bits + _firmataStream.WriteByte((byte)(value >> 7 & sbyte.MaxValue)); // top bits + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + } + } + + public void ConfigureSpiDevice(SpiConnectionSettings connectionSettings) + { + if (connectionSettings.ChipSelectLine >= 15) + { + // this is currently because we derive the device id from the CS line, and that one has only 4 bits + throw new NotSupportedException("Only pins <=15 are allowed as CS line"); + } + + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SPI_DATA); + _firmataStream.WriteByte((byte)FirmataSpiCommand.SPI_DEVICE_CONFIG); + byte deviceIdChannel = (byte)(connectionSettings.ChipSelectLine << 3); + _firmataStream.WriteByte((byte)(deviceIdChannel)); + _firmataStream.WriteByte((byte)1); + int clockSpeed = 1_000_000; // Hz + _firmataStream.WriteByte((byte)(clockSpeed & 0x7F)); + _firmataStream.WriteByte((byte)((clockSpeed >> 7) & 0x7F)); + _firmataStream.WriteByte((byte)((clockSpeed >> 15) & 0x7F)); + _firmataStream.WriteByte((byte)((clockSpeed >> 22) & 0x7F)); + _firmataStream.WriteByte((byte)((clockSpeed >> 29) & 0x7F)); + _firmataStream.WriteByte(0); // Word size (default = 8) + _firmataStream.WriteByte(1); // Default CS pin control (enable) + _firmataStream.WriteByte((byte)(connectionSettings.ChipSelectLine)); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + } + } + + public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, out Ratio humidity) + { + temperature = default; + humidity = default; + lock (_synchronisationLock) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.DHT_SENSOR_DATA_REQUEST); + _firmataStream.WriteByte((byte)dhtType); + _firmataStream.WriteByte((byte)pinNumber); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + + bool result = _dataReceived.WaitOne(DefaultReplyTimeout); + if (result == false) + { + throw new TimeoutException("Timeout waiting for device reply"); + } + + // Command, type, pin number and 4x2 bytes data + if (_lastResponse.Count < 11) + { + return false; + } + + if (_lastResponse[2] != pinNumber) + { + return false; + } + + Span reply = stackalloc byte[4]; + ReassembleByteString(_lastResponse, 3, 8, reply); + + var arr = reply.ToArray(); + + short h = BitConverter.ToInt16(arr, 0); + float t = BitConverter.ToInt16(arr, 2) / 10.0f; + if (double.IsNaN(t) || double.IsNaN(h)) + { + return false; + } + + temperature = Temperature.FromDegreesCelsius(t); + humidity = Ratio.FromPercent(h); + } + + return true; + } + + public uint GetAnalogRawValue(int pinNumber) + { + lock (_lastAnalogValueLock) + { + return _lastAnalogValues[pinNumber]; + } + } + + private void WaitAndHandleIlCommandReply() + { + bool result = _dataReceived.WaitOne(DefaultReplyTimeout); + if (result == false) + { + throw new TimeoutException("Arduino failed to accept code sequence."); + } + + if (_lastIlExecutionError != 0) + { + throw new TaskSchedulerException($"Task scheduler method returned state {_lastIlExecutionError}."); + } + } + + public void SendMethodIlCode(byte methodIndex, byte[] byteCode) + { + lock (_synchronisationLock) + { + const int BYTES_PER_PACKET = 20; + int codeIndex = 0; + while (codeIndex < byteCode.Length) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); + _firmataStream.WriteByte((byte)0xFF); // IL data + _firmataStream.WriteByte((byte)ExecutorCommand.LoadIl); + _firmataStream.WriteByte(methodIndex); + _firmataStream.WriteByte((byte)byteCode.Length); + _firmataStream.WriteByte((byte)codeIndex); + int bytesThisPacket = Math.Min(BYTES_PER_PACKET, byteCode.Length - codeIndex); + SendValuesAsTwo7bitBytes(byteCode.AsSpan(codeIndex, bytesThisPacket)); + codeIndex += bytesThisPacket; + + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + + WaitAndHandleIlCommandReply(); + } + } + } + + public void ExecuteIlCode(byte codeReference, int[] parameters, Type returnType) + { + lock (_synchronisationLock) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); + _firmataStream.WriteByte((byte)0xFF); // IL data + _firmataStream.WriteByte((byte)ExecutorCommand.StartTask); + _firmataStream.WriteByte(codeReference); + for (int i = 0; i < parameters.Length; i++) + { + byte[] param = BitConverter.GetBytes(parameters[i]); + SendValuesAsTwo7bitBytes(param); + } + + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + WaitAndHandleIlCommandReply(); + } + } + + public void SendMethodDeclaration(byte codeReference, int declarationToken, MethodFlags methodFlags, byte maxLocals, byte argCount) + { + lock (_synchronisationLock) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); + _firmataStream.WriteByte((byte)0xFF); // IL data + _firmataStream.WriteByte((byte)ExecutorCommand.DeclareMethod); + _firmataStream.WriteByte(codeReference); + _firmataStream.WriteByte((byte)methodFlags); + _firmataStream.WriteByte(maxLocals); + _firmataStream.WriteByte(argCount); + byte[] param = BitConverter.GetBytes(declarationToken); + SendValuesAsTwo7bitBytes(param); + + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + + WaitAndHandleIlCommandReply(); + } + } + + public void SendTokenMap(byte codeReference, int[] data) + { + lock (_synchronisationLock) + { + _dataReceived.Reset(); + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); + _firmataStream.WriteByte((byte)0xFF); // IL data + _firmataStream.WriteByte((byte)ExecutorCommand.SetMethodTokens); + _firmataStream.WriteByte(codeReference); + for (int i = 0; i < data.Length; i++) + { + byte[] param = BitConverter.GetBytes(data[i]); + SendValuesAsTwo7bitBytes(param); + } + + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + WaitAndHandleIlCommandReply(); + } + } + + public void SendIlResetCommand(bool force) + { + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); + _firmataStream.WriteByte((byte)0xFF); // IL data + _firmataStream.WriteByte((byte)ExecutorCommand.ResetExecutor); + _firmataStream.WriteByte((byte)(force ? 1 : 0)); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + Thread.Sleep(100); + } + } + + public void SendKillTask(byte codeReference) + { + lock (_synchronisationLock) + { + _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); + _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); + _firmataStream.WriteByte((byte)0xFF); // IL data + _firmataStream.WriteByte((byte)ExecutorCommand.KillTask); + _firmataStream.WriteByte(codeReference); + _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); + _firmataStream.Flush(); + Thread.Sleep(100); + } + } + + private void SendValuesAsTwo7bitBytes(ReadOnlySpan values) + { + for (int i = 0; i < values.Length; i++) + { + _firmataStream.WriteByte((byte)(values[i] & (uint)sbyte.MaxValue)); + _firmataStream.WriteByte((byte)(values[i] >> 7 & sbyte.MaxValue)); + } + } + + /// + /// Firmata uses 2 bytes to encode 8-bit data, because byte values with the top bit set + /// are reserved for commands. This decodes such data chunks. + /// + private int ReassembleByteString(IList byteStream, int startIndex, int length, Span reply) + { + int num; + if (reply.Length < length / 2) + { + length = reply.Length * 2; + } + + for (num = 0; num < length / 2; ++num) + { + reply[num] = (byte)(byteStream[startIndex + (num * 2)] | + byteStream[startIndex + (num * 2) + 1] << 7); + } + + return length / 2; + } + + private void Dispose(bool disposing) + { + if (disposing) + { + Close(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/devices/Arduino/FirmataSpiCommand.cs b/src/devices/Arduino/FirmataSpiCommand.cs new file mode 100644 index 0000000000..96a50a80f8 --- /dev/null +++ b/src/devices/Arduino/FirmataSpiCommand.cs @@ -0,0 +1,13 @@ +namespace Iot.Device.Arduino +{ + internal enum FirmataSpiCommand + { + SPI_BEGIN = 0x0, + SPI_DEVICE_CONFIG = 0x01, + SPI_TRANSFER = 0x02, + SPI_WRITE = 0x03, + SPI_READ = 0x04, + SPI_REPLY = 0x05, + SPI_END = 0x06, + } +} diff --git a/src/devices/Arduino/FirmataSysexCommand.cs b/src/devices/Arduino/FirmataSysexCommand.cs new file mode 100644 index 0000000000..87a3622413 --- /dev/null +++ b/src/devices/Arduino/FirmataSysexCommand.cs @@ -0,0 +1,34 @@ +#pragma warning disable CS1591 + +namespace Iot.Device.Arduino +{ + /// + /// Extended firmata commands + /// + public enum FirmataSysexCommand + { + ENCODER_DATA = 0x61, + SPI_DATA = 0x68, + SERVO_CONFIG = 0x70, + STRING_DATA = 0x71, + STEPPER_DATA = 0x72, + ONEWIRE_DATA = 0x73, + SHIFT_DATA = 0x75, + I2C_REQUEST = 0x76, + I2C_REPLY = 0x77, + I2C_CONFIG = 0x78, + EXTENDED_ANALOG = 0x6F, + PIN_STATE_QUERY = 0x6D, + PIN_STATE_RESPONSE = 0x6E, + CAPABILITY_QUERY = 0x6B, + CAPABILITY_RESPONSE = 0x6C, + ANALOG_MAPPING_QUERY = 0x69, + ANALOG_MAPPING_RESPONSE = 0x6A, + REPORT_FIRMWARE = 0x79, + SAMPLING_INTERVAL = 0x7A, + SCHEDULER_DATA = 0x7B, + SYSEX_NON_REALTIME = 0x7E, + SYSEX_REALTIME = 0x7F, + DHT_SENSOR_DATA_REQUEST = 0x02, // User defined block + } +} diff --git a/src/devices/Arduino/README.md b/src/devices/Arduino/README.md new file mode 100644 index 0000000000..c69a484046 --- /dev/null +++ b/src/devices/Arduino/README.md @@ -0,0 +1,47 @@ +# SPI, GPIO and I2C drivers for Arduino + +This binding supports GPIO and I2C access from a normal Desktop environment (Windows, Linux) trough an Arduino board. + +## Device family + +This binding remotely controls different Arduino boards directly from PC Software. It provides support for accessing GPIO ports as well as I2C devices. The Arduino is remote controlled by individual commands from the PC, the entire program will run on the PC, and not on the Arduino, so the connection cannot be removed while the device is being used. + +## Desktop Requirements + +In order to have an Arduino board working with the PC, you need to Install the Arduino IDE together with the drivers for your board type. If you get a simple sketch uploaded and running (such as the blinking LED example) you are fine to start. + +## Preparing your Arduino +You need to upload a special sketch to the Arduino. This sketch implements the "Firmata-Protocol", a communication protocol that allows to remotely control all the inputs and outputs of the board. See https://github.com/firmata/protocol/blob/master/protocol.md for details. + +The binding requires Firmata Version 2.6, which is implemented i.e. by the ConfigurableFirmata project. +- Open the Arduino IDE +- Go to the library manager and check that you have the "ConfigurableFirmata" library installed +- Open "CommonFirmataFeatures.ino" from the device binding folder or go to http://firmatabuilder.com/ to create your own custom firmata firmware. Make sure you have at least the features checked that you will need. +- Upload this sketch to your Arduino. + +After these steps, you can start coding with Iot.Devices.Arduino and make your Arduino do whatever you want, from blinking LEDS to your personal weather station. For usage and examples see the samples folder. Note that ConfigurableFirmata uses a default UART speed of 57600 baud. It is recommended to increase it to 115200, though. + +## Usage + +### I2C + +### SPI + +### GPIO + +## Known limitations + +This SPI and I2C implementation are over USB which can contains some delays and not be as fast as a native implementation. It has been developed mainly for development purpose and being able to run and debug easily SPI and I2C device code from a Windows 64 bits machine. It is not recommended to use this type of chipset for production purpose. + +For the moment this project supports only SPI and I2C in a Windows environment. Here is the list of tested features: + +- [ ] SPI master support for Windows 64/32 +- [x] I2C master support for Windows 64/32 +- [x] Basic GPIO support for Windows 64/32 +- [x] Advanced GPIO support for Windows 64/32 +- [ ] SPI support for MacOS +- [ ] I2C support for MacOS +- [ ] GPIO support for MacOS +- [ ] SPI support for Linux 64 +- [ ] I2C support for Linux 64 +- [ ] GPIO support for Linux 64 diff --git a/src/devices/Arduino/SupportedMode.cs b/src/devices/Arduino/SupportedMode.cs new file mode 100644 index 0000000000..28e451b11a --- /dev/null +++ b/src/devices/Arduino/SupportedMode.cs @@ -0,0 +1,92 @@ +namespace Iot.Device.Arduino +{ + /// + /// Mode bits for the Firmata protocol. + /// These are used both for capability reporting as well as to set a mode + /// + public enum SupportedMode + { + /// + /// The pin supports digital input + /// + DIGITAL_INPUT = (0x00), + + /// + /// The pin supports digital output; + /// + DIGITAL_OUTPUT = (0x01), + + /// + /// The pin supports analog input + /// + ANALOG_INPUT = (0x02), + + /// + /// The pin supports PWM + /// + PWM = (0x03), + + /// + /// The pin supports servo motor controls + /// + SERVO = (0x04), + + /// + /// Unused + /// + SHIFT = (0x05), + + /// + /// The pin supports I2C data transfer + /// + I2C = (0x06), + + /// + /// The pin supports one wire communication + /// + ONEWIRE = (0x07), + + /// + /// The pin can drive a stepper motor + /// + STEPPER = (0x08), + + /// + /// The pin has an encoder + /// + ENCODER = (0x09), + + /// + /// The pin can perform UART (TX or RX) + /// + SERIAL = (0x0A), + + /// + /// The pin can be set to input-pullup. + /// + INPUT_PULLUP = (0x0B), + + /// + /// The pin can be used for SPI transfer (Clock, MOSI, MISO and default CS pin) + /// For most Arduinos, MOSI=11, MISO=12 and Clock = 13. The default CS pin is 10. + /// + SPI = 0x0C, + + //// Unofficial extensions (only supported by special firmware) + + /// + /// HC-SR04 + /// + SONAR = 0x0D, + + /// + /// Arduino tone library + /// + TONE = 0x0E, + + /// + /// DHT type sensors + /// + DHT = 0x0F, + } +} diff --git a/src/devices/Arduino/SupportedPinConfiguration.cs b/src/devices/Arduino/SupportedPinConfiguration.cs new file mode 100644 index 0000000000..c5bc38f28b --- /dev/null +++ b/src/devices/Arduino/SupportedPinConfiguration.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Device.Gpio; +using System.Linq; +using System.Text; + +#pragma warning disable CS1591 + +namespace Iot.Device.Arduino +{ + internal class SupportedPinConfiguration + { + public SupportedPinConfiguration(int pin) + { + Pin = pin; + PinModes = new List(); + PwmResolutionBits = 0; + AnalogInputResolutionBits = 1; // binary + AnalogPinNumber = 127; // = Not an analog pin + } + + public int Pin + { + get; + } + + public List PinModes + { + get; + } + + public int PwmResolutionBits + { + get; + internal set; + } + + /// + /// This contains the resolution of an analog input channel, in bits + /// + public int AnalogInputResolutionBits + { + get; + internal set; + } + + /// + /// This gets the number of the analog input pin, as commonly used by Arduino software + /// + public byte AnalogPinNumber + { + get; + internal set; + } + + public override string ToString() + { + string pinModes = String.Join(", ", PinModes); + return $"{nameof(Pin)}: {Pin}, {nameof(PinModes)}: [{pinModes}], {nameof(PwmResolutionBits)}: {PwmResolutionBits}, {nameof(AnalogInputResolutionBits)}: {AnalogInputResolutionBits}, {nameof(AnalogPinNumber)}: {AnalogPinNumber}"; + } + } +} diff --git a/src/devices/Arduino/category.txt b/src/devices/Arduino/category.txt new file mode 100644 index 0000000000..6e669d971c --- /dev/null +++ b/src/devices/Arduino/category.txt @@ -0,0 +1,6 @@ +i2c +usb +gpio +adc +protocol +spi diff --git a/src/devices/Arduino/samples/Arduino.Monitor.cs b/src/devices/Arduino/samples/Arduino.Monitor.cs new file mode 100644 index 0000000000..fc69fc315b --- /dev/null +++ b/src/devices/Arduino/samples/Arduino.Monitor.cs @@ -0,0 +1,301 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Device.Gpio; +using System.Device.Gpio.Drivers; +using System.Device.I2c; +using System.Device.Spi; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading; +using Iot.Device.Adc; +using Iot.Device.Arduino; +using Iot.Device.Arduino.Sample; +using Iot.Device.Bmxx80; +using Iot.Device.Bmxx80.PowerMode; +using Iot.Device.Common; +using Iot.Device.CpuTemperature; +using UnitsNet; + +namespace Arduino.Samples +{ + /// + /// Sample application for Ft4222 + /// + internal class Program + { + /// + /// Main entry point + /// + /// Unused + public static void Main(string[] args) + { + string portName = "COM4"; + if (args.Length > 1) + { + portName = args[1]; + } + + using (var port = new SerialPort(portName, 115200)) + { + Console.WriteLine($"Connecting to Arduino on {portName}"); + try + { + port.Open(); + } + catch (UnauthorizedAccessException x) + { + Console.WriteLine($"Could not open COM port: {x.Message} Possible reason: Arduino IDE connected or serial console open"); + return; + } + + ArduinoBoard board = new ArduinoBoard(port.BaseStream); + try + { + board.LogMessages += BoardOnLogMessages; + board.Initialize(); + Console.WriteLine($"Connection successful. Firmware version: {board.FirmwareVersion}, Builder: {board.FirmwareName}"); + DisplayModes(board); + } + catch (TimeoutException x) + { + Console.WriteLine($"No answer from board: {x.Message} "); + } + finally + { + port.Close(); + board?.Dispose(); + } + } + } + + private static void BoardOnLogMessages(string message, Exception exception) + { + Console.WriteLine("Log message: " + message); + if (exception != null) + { + Console.WriteLine(exception); + } + } + + public static void DisplayModes(ArduinoBoard board) + { + const int Gpio2 = 2; + const int MaxMode = 10; + const double StationAltitude = 650; + int mode = 0; + var gpioController = board.CreateGpioController(PinNumberingScheme.Board); + gpioController.OpenPin(Gpio2); + gpioController.SetPinMode(Gpio2, PinMode.Input); + CharacterDisplay disp = new CharacterDisplay(board); + Console.WriteLine("Display output test"); + Console.WriteLine("The button on GPIO 2 changes modes"); + Console.WriteLine("Press x to exit"); + disp.Output.ScrollUpDelay = TimeSpan.FromMilliseconds(500); + AutoResetEvent buttonClicked = new AutoResetEvent(false); + + void ChangeMode(object sender, PinValueChangedEventArgs pinValueChangedEventArgs) + { + mode++; + if (mode > MaxMode) + { + // Don't change back to 0 + mode = 1; + } + + buttonClicked.Set(); + } + + gpioController.RegisterCallbackForPinValueChangedEvent(Gpio2, PinEventTypes.Falling, ChangeMode); + var device = board.CreateI2cDevice(new I2cConnectionSettings(0, Bmp280.DefaultI2cAddress)); + Bmp280 bmp; + try + { + bmp = new Bmp280(device); + bmp.StandbyTime = StandbyTime.Ms250; + bmp.SetPowerMode(Bmx280PowerMode.Normal); + } + catch (IOException) + { + bmp = null; + Console.WriteLine("BMP280 not available"); + } + + OpenHardwareMonitor hardwareMonitor = new OpenHardwareMonitor(); + hardwareMonitor.EnableDerivedSensors(); + TimeSpan sleeptime = TimeSpan.FromMilliseconds(500); + string modeName = string.Empty; + string previousModeName = string.Empty; + int firstCharInText = 0; + while (true) + { + if (Console.KeyAvailable && Console.ReadKey(true).KeyChar == 'x') + { + break; + } + + // Default + sleeptime = TimeSpan.FromMilliseconds(500); + + switch (mode) + { + case 0: + modeName = "Display ready"; + disp.Output.ReplaceLine(1, "Button for mode"); + // Just text + break; + case 1: + { + modeName = "Time"; + disp.Output.ReplaceLine(1, DateTime.Now.ToLongTimeString()); + sleeptime = TimeSpan.FromMilliseconds(200); + break; + } + + case 2: + { + modeName = "Date"; + disp.Output.ReplaceLine(1, DateTime.Now.ToShortDateString()); + break; + } + + case 3: + modeName = "Temperature / Barometric Pressure"; + if (bmp != null && bmp.TryReadTemperature(out Temperature temp) && bmp.TryReadPressure(out Pressure p2)) + { + Pressure p3 = WeatherHelper.CalculateBarometricPressure(p2, temp, StationAltitude); + disp.Output.ReplaceLine(1, string.Format(CultureInfo.CurrentCulture, "{0:s1} {1:s1}", temp, p3)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + case 4: + modeName = "Temperature / Humidity"; + if (board.TryReadDht(3, 11, out temp, out var humidity)) + { + disp.Output.ReplaceLine(1, string.Format(CultureInfo.CurrentCulture, "{0:s1} {1:s0}", temp, humidity)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + + case 5: + modeName = "Dew point"; + if (bmp != null && bmp.TryReadPressure(out p2) && board.TryReadDht(3, 11, out temp, out humidity)) + { + Temperature dewPoint = WeatherHelper.CalculateDewPoint(temp, humidity.Percent); + disp.Output.ReplaceLine(1, dewPoint.ToString("s1", CultureInfo.CurrentCulture)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + case 6: + modeName = "CPU Temperature"; + if (hardwareMonitor.TryGetAverageCpuTemperature(out temp)) + { + disp.Output.ReplaceLine(1, temp.ToString("s1", CultureInfo.CurrentCulture)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + case 7: + modeName = "GPU Temperature"; + if (hardwareMonitor.TryGetAverageGpuTemperature(out temp)) + { + disp.Output.ReplaceLine(1, temp.ToString("s1", CultureInfo.CurrentCulture)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + case 8: + modeName = "CPU Load"; + disp.Output.ReplaceLine(1, hardwareMonitor.GetCpuLoad().ToString("s1", CultureInfo.CurrentCulture)); + break; + + case 9: + modeName = "Total power dissipation"; + var powerSources = hardwareMonitor.GetSensorList().Where(x => x.SensorType == SensorType.Power); + Power totalPower = Power.Zero; + foreach (var power in powerSources) + { + if (power.Name != "CPU Cores" && power.TryGetValue(out Power powerConsumption)) // included in CPU Package + { + totalPower = totalPower + powerConsumption; + } + } + + disp.Output.ReplaceLine(1, totalPower.ToString("s1", CultureInfo.CurrentCulture)); + break; + + case 10: + modeName = "Energy consumed"; + var energySources = hardwareMonitor.GetSensorList().Where(x => x.SensorType == SensorType.Energy); + Energy totalEnergy = Energy.FromWattHours(0); // Set up the desired output unit + foreach (var e in energySources) + { + if (!e.Name.StartsWith("CPU Cores") && e.TryGetValue(out Energy powerConsumption)) // included in CPU Package + { + totalEnergy = totalEnergy + powerConsumption; + } + } + + disp.Output.ReplaceLine(1, totalEnergy.ToString("s1", CultureInfo.CurrentCulture)); + break; + } + + int displayWidth = disp.Output.Size.Width; + if (modeName.Length > displayWidth) + { + // Add one space at the end, makes it a bit easier to read + if (firstCharInText < modeName.Length - displayWidth + 1) + { + firstCharInText++; + } + else + { + firstCharInText = 0; + } + + disp.Output.ReplaceLine(0, modeName.Substring(firstCharInText)); + } + + if (modeName != previousModeName) + { + disp.Output.ReplaceLine(0, modeName); + + previousModeName = modeName; + firstCharInText = 0; + } + + buttonClicked.WaitOne(sleeptime); + } + + hardwareMonitor.Dispose(); + disp.Output.Clear(); + disp.Dispose(); + bmp?.Dispose(); + gpioController.Dispose(); + } + } +} diff --git a/src/devices/Arduino/samples/Arduino.Monitor.csproj b/src/devices/Arduino/samples/Arduino.Monitor.csproj new file mode 100644 index 0000000000..c5e9e61596 --- /dev/null +++ b/src/devices/Arduino/samples/Arduino.Monitor.csproj @@ -0,0 +1,30 @@ + + + + Exe + netcoreapp2.1 + Iot.Device.Arduino.Sample + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/devices/Arduino/samples/Arduino.sample.cs b/src/devices/Arduino/samples/Arduino.sample.cs new file mode 100644 index 0000000000..d3db0aeed4 --- /dev/null +++ b/src/devices/Arduino/samples/Arduino.sample.cs @@ -0,0 +1,567 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Device.Gpio; +using System.Device.Gpio.Drivers; +using System.Device.I2c; +using System.Device.Spi; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Ports; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using Iot.Device.Adc; +using Iot.Device.Arduino; +using Iot.Device.Bmxx80; +using Iot.Device.Bmxx80.PowerMode; +using UnitsNet; + +namespace Arduino.Samples +{ + /// + /// Test application for Arduino/Firmata protocol + /// + internal class Program + { + /// + /// Main entry point + /// + /// Unused + public static void Main(string[] args) + { + string portName = "COM4"; + if (args.Length > 1) + { + portName = args[1]; + } + + TextWriter logFile = new StringWriter(); + + using (var port = new SerialPort(portName, 115200)) + { + Console.WriteLine($"Connecting to Arduino on {portName}"); + try + { + port.Open(); + } + catch (UnauthorizedAccessException x) + { + Console.WriteLine($"Could not open COM port: {x.Message} Possible reason: Arduino IDE connected or serial console open"); + return; + } + + DebugLogStream dls = new DebugLogStream(port.BaseStream, logFile); + + ArduinoBoard board = new ArduinoBoard(dls); + try + { + board.LogMessages += BoardOnLogMessages; + board.Initialize(); + Console.WriteLine($"Connection successful. Firmware version: {board.FirmwareVersion}, Builder: {board.FirmwareName}"); + while (Menu(board)) + { + } + } + catch (TimeoutException x) + { + Console.WriteLine($"No answer from board: {x.Message} "); + } + finally + { + port.Close(); + board?.Dispose(); + } + } + + Debug.Write(logFile.ToString()); + } + + private static void BoardOnLogMessages(string message, Exception exception) + { + Console.WriteLine("Log message: " + message); + if (exception != null) + { + Console.WriteLine(exception); + } + } + + private static bool Menu(ArduinoBoard board) + { + Console.WriteLine("Hello I2C and GPIO on Arduino!"); + Console.WriteLine("Select the test you want to run:"); + Console.WriteLine(" 1 Run I2C tests with a BMP280"); + Console.WriteLine(" 2 Run GPIO tests with a simple led blinking on GPIO6 port"); + Console.WriteLine(" 3 Run polling button test on GPIO2"); + Console.WriteLine(" 4 Run event wait test event on GPIO2 on Falling and Rising"); + Console.WriteLine(" 5 Run callback event test on GPIO2"); + Console.WriteLine(" 6 Run PWM test with a simple led dimming on GPIO6 port"); + Console.WriteLine(" 7 Dim the LED according to the input on A1"); + Console.WriteLine(" 8 Read analog channel as fast as possible"); + Console.WriteLine(" 9 Run SPI tests with an MCP3008 (experimental)"); + Console.WriteLine(" 0 Detect all devices on the I2C bus"); + Console.WriteLine(" H Read DHT11 Humidity sensor on GPIO 3 (experimental)"); + Console.WriteLine(" C C#/IL Code execution on Arduino (VERY experimental)"); + Console.WriteLine(" X Exit"); + var key = Console.ReadKey(); + Console.WriteLine(); + + switch (key.KeyChar) + { + case '1': + TestI2c(board); + break; + case '2': + TestGpio(board); + break; + case '3': + TestInput(board); + break; + case '4': + TestEventsDirectWait(board); + break; + case '5': + TestEventsCallback(board); + break; + case '6': + TestPwm(board); + break; + case '7': + TestAnalogIn(board); + break; + case '8': + TestAnalogCallback(board); + break; + case '9': + TestSpi(board); + break; + case '0': + ScanDeviceAddressesOnI2cBus(board); + break; + case 'h': + case 'H': + TestDht(board); + break; + case 'x': + case 'X': + return false; + case 'c': + case 'C': + TestIlInterpreter(board); + break; + } + + return true; + } + + private static void TestPwm(ArduinoBoard board) + { + int pin = 6; + using (var pwm = board.CreatePwmChannel(0, pin, 100, 0)) + { + Console.WriteLine("Now dimming LED. Press any key to exit"); + while (!Console.KeyAvailable) + { + pwm.Start(); + for (double fadeValue = 0; fadeValue <= 1.0; fadeValue += 0.05) + { + // sets the value (range from 0 to 255): + pwm.DutyCycle = fadeValue; + // wait for 30 milliseconds to see the dimming effect + Thread.Sleep(30); + } + + // fade out from max to min in increments of 5 points: + for (double fadeValue = 1.0; fadeValue >= 0; fadeValue -= 0.05) + { + // sets the value (range from 0 to 255): + pwm.DutyCycle = fadeValue; + // wait for 30 milliseconds to see the dimming effect + Thread.Sleep(30); + } + + } + + Console.ReadKey(); + pwm.Stop(); + } + } + + private static void TestI2c(ArduinoBoard board) + { + var device = board.CreateI2cDevice(new I2cConnectionSettings(0, Bmp280.DefaultI2cAddress)); + + var bmp = new Bmp280(device); + bmp.StandbyTime = StandbyTime.Ms250; + bmp.SetPowerMode(Bmx280PowerMode.Normal); + Console.WriteLine("Device open"); + while (!Console.KeyAvailable) + { + bmp.TryReadTemperature(out var temperature); + bmp.TryReadPressure(out var pressure); + Console.Write($"\rTemperature: {temperature.DegreesCelsius:F2}°C. Pressure {pressure.Hectopascals:F1} hPa "); + Thread.Sleep(100); + } + + bmp.Dispose(); + device.Dispose(); + Console.ReadKey(); + Console.WriteLine(); + } + + private static void ScanDeviceAddressesOnI2cBus(ArduinoBoard board) + { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.Append(" 0 1 2 3 4 5 6 7 8 9 a b c d e f"); + stringBuilder.Append(Environment.NewLine); + + for (int startingRowAddress = 0; startingRowAddress < 128; startingRowAddress += 16) + { + stringBuilder.Append($"{startingRowAddress:x2}: "); // Beginning of row. + + for (int rowAddress = 0; rowAddress < 16; rowAddress++) + { + int deviceAddress = startingRowAddress + rowAddress; + + // Skip the unwanted addresses. + if (deviceAddress < 0x3 || deviceAddress > 0x77) + { + stringBuilder.Append(" "); + continue; + } + + var connectionSettings = new I2cConnectionSettings(0, deviceAddress); + using (var i2cDevice = board.CreateI2cDevice(connectionSettings)) + { + try + { + i2cDevice.ReadByte(); // Only checking if device is present. + stringBuilder.Append($"{deviceAddress:x2} "); + } + catch + { + stringBuilder.Append("-- "); + } + } + } + + stringBuilder.Append(Environment.NewLine); + } + + Console.WriteLine(stringBuilder.ToString()); + } + + public static void TestGpio(ArduinoBoard board) + { + // Use Pin 6 + const int gpio = 6; + var gpioController = board.CreateGpioController(PinNumberingScheme.Board); + + // Opening GPIO2 + gpioController.OpenPin(gpio); + gpioController.SetPinMode(gpio, PinMode.Output); + + Console.WriteLine("Blinking GPIO6"); + while (!Console.KeyAvailable) + { + gpioController.Write(gpio, PinValue.High); + Thread.Sleep(500); + gpioController.Write(gpio, PinValue.Low); + Thread.Sleep(500); + } + + Console.ReadKey(); + gpioController.Dispose(); + } + + public static void TestAnalogIn(ArduinoBoard board) + { + // Use Pin 6 + const int gpio = 6; + const int analogPin = 15; + var gpioController = board.CreateGpioController(PinNumberingScheme.Board); + var analogController = board.CreateAnalogController(0); + + analogController.OpenPin(analogPin); + gpioController.OpenPin(gpio); + gpioController.SetPinMode(gpio, PinMode.Output); + + Console.WriteLine("Blinking GPIO6, based on analog input."); + while (!Console.KeyAvailable) + { + double voltage = analogController.ReadVoltage(analogPin); + gpioController.Write(gpio, PinValue.High); + Thread.Sleep((int)voltage * 100); + voltage = analogController.ReadVoltage(analogPin); + gpioController.Write(gpio, PinValue.Low); + Thread.Sleep((int)voltage * 100); + } + + Console.ReadKey(); + analogController.Dispose(); + gpioController.Dispose(); + } + + public static void TestAnalogCallback(ArduinoBoard board) + { + const int analogPin = 15; + var analogController = board.CreateAnalogController(0); + + analogController.OpenPin(analogPin); + analogController.EnableAnalogValueChangedEvent(analogPin, null, 0); + + analogController.ValueChanged += (sender, args) => + { + if (args.PinNumber == analogPin) + { + Console.WriteLine($"New voltage: {args.Value}."); + } + }; + + Console.WriteLine("Waiting for changes on the analog input"); + while (!Console.KeyAvailable) + { + // Nothing to do + Thread.Sleep(100); + } + + Console.ReadKey(); + analogController.DisableAnalogValueChangedEvent(analogPin); + analogController.Dispose(); + } + + public static void TestInput(ArduinoBoard board) + { + const int gpio = 2; + var gpioController = board.CreateGpioController(PinNumberingScheme.Board); + + // Opening GPIO2 + gpioController.OpenPin(gpio); + gpioController.SetPinMode(gpio, PinMode.Input); + + if (gpioController.GetPinMode(gpio) != PinMode.Input) + { + throw new InvalidOperationException("Couldn't set pin mode"); + } + + Console.WriteLine("Polling input pin 2"); + var lastState = gpioController.Read(gpio); + while (!Console.KeyAvailable) + { + var newState = gpioController.Read(gpio); + if (newState != lastState) + { + if (newState == PinValue.High) + { + Console.WriteLine("Button pressed"); + } + else + { + Console.WriteLine("Button released"); + } + } + + lastState = newState; + Thread.Sleep(10); + } + + Console.ReadKey(); + gpioController.Dispose(); + } + + public static void TestEventsDirectWait(ArduinoBoard board) + { + const int Gpio2 = 2; + var gpioController = board.CreateGpioController(PinNumberingScheme.Board); + + // Opening GPIO2 + gpioController.OpenPin(Gpio2); + gpioController.SetPinMode(Gpio2, PinMode.Input); + + Console.WriteLine("Waiting for both falling and rising events"); + while (!Console.KeyAvailable) + { + var res = gpioController.WaitForEvent(Gpio2, PinEventTypes.Falling | PinEventTypes.Rising, new TimeSpan(0, 0, 0, 0, 50)); + if ((!res.TimedOut) && (res.EventTypes != PinEventTypes.None)) + { + Console.WriteLine($"Event on GPIO {Gpio2}, event type: {res.EventTypes}"); + } + } + + Console.ReadKey(); + Console.WriteLine("Waiting for only rising events"); + while (!Console.KeyAvailable) + { + var res = gpioController.WaitForEvent(Gpio2, PinEventTypes.Rising, new TimeSpan(0, 0, 0, 0, 50)); + if ((!res.TimedOut) && (res.EventTypes != PinEventTypes.None)) + { + MyCallback(gpioController, new PinValueChangedEventArgs(res.EventTypes, Gpio2)); + } + } + + gpioController.Dispose(); + } + + public static void TestEventsCallback(ArduinoBoard board) + { + const int Gpio2 = 2; + var gpioController = board.CreateGpioController(PinNumberingScheme.Board); + + // Opening GPIO2 + gpioController.OpenPin(Gpio2); + gpioController.SetPinMode(Gpio2, PinMode.Input); + + Console.WriteLine("Setting up events on GPIO2 for rising and falling"); + + gpioController.RegisterCallbackForPinValueChangedEvent(Gpio2, PinEventTypes.Falling | PinEventTypes.Rising, MyCallback); + Console.WriteLine("Event setup, press a key to remove the falling event"); + while (!Console.KeyAvailable) + { + // Nothing to do + Thread.Sleep(100); + } + + Console.ReadKey(); + gpioController.UnregisterCallbackForPinValueChangedEvent(Gpio2, MyCallback); + gpioController.RegisterCallbackForPinValueChangedEvent(Gpio2, PinEventTypes.Rising, MyCallback); + Console.WriteLine("Now only waiting for rising events, press a key to remove all events and quit"); + while (!Console.KeyAvailable) + { + // Nothing to do + Thread.Sleep(100); + } + + Console.ReadKey(); + gpioController.UnregisterCallbackForPinValueChangedEvent(Gpio2, MyCallback); + gpioController.Dispose(); + } + + private static void MyCallback(object sender, PinValueChangedEventArgs pinValueChangedEventArgs) + { + Console.WriteLine($"Event on GPIO {pinValueChangedEventArgs.PinNumber}, event type: {pinValueChangedEventArgs.ChangeType}"); + } + + public static void TestSpi(ArduinoBoard board) + { + const double vssValue = 5; // Set this to the supply voltage of the arduino. Most boards have 5V, some newer ones run at 3.3V. + SpiConnectionSettings settings = new SpiConnectionSettings(0, 10); + using (var spi = board.CreateSpiDevice(settings)) + using (Mcp3008 mcp = new Mcp3008(spi)) + { + Console.WriteLine("SPI Device open"); + while (!Console.KeyAvailable) + { + double vdd = mcp.Read(5); + double vss = mcp.Read(6); + double middle = mcp.Read(7); + Console.WriteLine($"Raw values: VSS {vss} VDD {vdd} Average {middle}"); + vdd = vssValue * vdd / 1024; + vss = vssValue * vss / 1024; + middle = vssValue * middle / 1024; + Console.WriteLine($"Converted values: VSS {vss:F2}V, VDD {vdd:F2}V, Average {middle:F2}V"); + Thread.Sleep(200); + } + } + + Console.ReadKey(); + } + + public static void TestDht(ArduinoBoard board) + { + Console.WriteLine("Reading DHT11. Any key to quit."); + + while (!Console.KeyAvailable) + { + if (board.TryReadDht(3, 11, out var temperature, out var humidity)) + { + Console.WriteLine($"Temperature: {temperature}, Humidity {humidity}"); + } + else + { + Console.WriteLine("Unable to read DHT11"); + } + + Thread.Sleep(2000); + } + + Console.ReadKey(); + } + + public static void TestIlInterpreter(ArduinoBoard board) + { + ArduinoCsCompiler compiler = new ArduinoCsCompiler(board); + var method1 = compiler.LoadCode>(ArduinoCompilerSampleMethods.AddInts); + method1.InvokeAsync(2, 3); + int result; + method1.WaitForResult(); + method1.GetMethodResults(out object[] data, out MethodState state); + if (state != MethodState.Stopped) + { + Console.WriteLine("Method returned result but did not end?!?"); + } + + result = (int)data[0]; + Console.WriteLine($"2 + 3 = {result}"); + method1.InvokeAsync(255, 5); + method1.WaitForResult(); + method1.GetMethodResults(out data, out state); + result = (int)data[0]; + Console.WriteLine($"255 + 5 = {result}"); + + var method2 = compiler.LoadCode(new Func(ArduinoCompilerSampleMethods.Equal)); + method2.InvokeAsync(2, 3); + method2.WaitForResult(); + method2.GetMethodResults(out data, out state); + bool trueOrFalse = (bool)data[0]; + Console.WriteLine($"Is 2 == 3? {trueOrFalse}"); + method2.InvokeAsync(257, 257); + method2.WaitForResult(); + method2.GetMethodResults(out data, out state); + trueOrFalse = (bool)data[0]; + Console.WriteLine($"Is 257 == 257? {trueOrFalse}"); + + compiler.LoadLowLevelInterface(); + compiler.LoadCode(new Func(ArduinoCompilerSampleMethods.Smaller)); + var method3 = compiler.LoadCode(new Action(ArduinoCompilerSampleMethods.Blink)); + method3.InvokeAsync(0, 10, 500); + + // While the above method executes (and blinks the led), we query the analog input + var analogController = board.CreateAnalogController(0); + int analogPin = 15; + + analogController.OpenPin(analogPin); + + while (method3.State == MethodState.Running) + { + double value = analogController.ReadVoltage(analogPin); + Console.WriteLine($"Read analog value as {value:F2}"); + Thread.Sleep(100); + } + + analogController.ClosePin(analogPin); + method3.WaitForResult(); + + compiler.ClearAllData(true); + + // Start task again and terminate it immediately + compiler.LoadLowLevelInterface(); + compiler.LoadCode(new Func(ArduinoCompilerSampleMethods.Smaller)); + method3 = compiler.LoadCode(new Action(ArduinoCompilerSampleMethods.Blink)); + method3.InvokeAsync(0, 10, 500); + method3.Terminate(); + if (method3.State != MethodState.Killed) + { + Console.WriteLine("Unable to terminate task"); + } + + method3.Dispose(); + compiler.ClearAllData(true); + compiler.Dispose(); + } + } +} diff --git a/src/devices/Arduino/samples/Arduino.sample.csproj b/src/devices/Arduino/samples/Arduino.sample.csproj new file mode 100644 index 0000000000..03ab403ea0 --- /dev/null +++ b/src/devices/Arduino/samples/Arduino.sample.csproj @@ -0,0 +1,27 @@ + + + + Exe + netcoreapp2.1 + Iot.Device.Arduino.Sample + false + + + + + + + + + + + + + + + + + + + + diff --git a/src/devices/Arduino/samples/CharacterDisplay.cs b/src/devices/Arduino/samples/CharacterDisplay.cs new file mode 100644 index 0000000000..17aa4b46c1 --- /dev/null +++ b/src/devices/Arduino/samples/CharacterDisplay.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Device.Gpio; +using System.Globalization; +using System.Text; +using Iot.Device.CharacterLcd; + +namespace Iot.Device.Arduino.Sample +{ + internal sealed class CharacterDisplay : IDisposable + { + private GpioController _controller; + private Lcd1602 _display; + private LcdConsole _textController; + + public CharacterDisplay(ArduinoBoard board) + { + _controller = board.CreateGpioController(PinNumberingScheme.Logical); + _display = new Lcd1602(8, 9, new int[] { 4, 5, 6, 7 }, -1, 1.0f, -1, _controller); + _display.BlinkingCursorVisible = false; + _display.UnderlineCursorVisible = false; + _display.Clear(); + ////for (int i = 0; i < 16; i++) + ////{ + //// for (int j = 0; j < 16; j++) + //// { + //// Span b = new Span(new byte[] { (byte)(j + i * 16) }); + //// _display.Write(b); + //// } + + //// Console.ReadKey(); + //// _display.Clear(); + ////} + + _textController = new LcdConsole(_display, "SplC780", false); + _textController.Clear(); + LcdCharacterEncodingFactory f = new LcdCharacterEncodingFactory(); + var cultureEncoding = f.Create(CultureInfo.CurrentCulture, "SplC780", '?', _display.NumberOfCustomCharactersSupported); + _textController.LoadEncoding(cultureEncoding); + } + + public LcdConsole Output + { + get + { + return _textController; + } + } + + public void Dispose() + { + _display.Dispose(); + _controller.Dispose(); + } + } +} From 76ae7b7739bdae8ee7d02cc64dbb1d652645f79c Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 31 Oct 2020 18:20:48 +0100 Subject: [PATCH 02/56] Add analog controller base classes again Rewrote to new suggested concept with AnalogInputPin class instead of "int pinNumber" everywhere. --- src/devices/Arduino/Arduino.csproj | 1 + src/devices/Arduino/Arduino.sln | 2 +- .../Arduino/ArduinoAnalogController.cs | 84 +++++ .../Arduino/ArduinoAnalogControllerDriver.cs | 133 ------- src/devices/Arduino/ArduinoAnalogInputPin.cs | 71 ++++ src/devices/Arduino/ArduinoBoard.cs | 47 +-- src/devices/Arduino/ArduinoI2cDevice.cs | 4 +- src/devices/Arduino/ArduinoMethodDecl.cs | 83 ----- src/devices/Arduino/ArduinoTask.cs | 154 -------- src/devices/Arduino/CommonFirmataFeatures.ino | 102 ------ .../ConfigurableFirmata.ino | 339 ++++++++++++++++++ src/devices/Arduino/FirmataDevice.cs | 174 +-------- .../Arduino/samples/Arduino.Monitor.csproj | 2 +- src/devices/Arduino/samples/Arduino.sample.cs | 93 +---- src/devices/Common/CommonHelpers.csproj | 11 +- .../System/Device/Analog/AnalogController.cs | 150 ++++++++ .../System/Device/Analog/AnalogInputPin.cs | 169 +++++++++ .../System/Device/Analog/TriggerReason.cs | 32 ++ .../Device/Analog/ValueChangedEventArgs.cs | 56 +++ .../Device/Analog/ValueChangedEventHandler.cs | 13 + 20 files changed, 953 insertions(+), 767 deletions(-) create mode 100644 src/devices/Arduino/ArduinoAnalogController.cs delete mode 100644 src/devices/Arduino/ArduinoAnalogControllerDriver.cs create mode 100644 src/devices/Arduino/ArduinoAnalogInputPin.cs delete mode 100644 src/devices/Arduino/ArduinoMethodDecl.cs delete mode 100644 src/devices/Arduino/ArduinoTask.cs delete mode 100644 src/devices/Arduino/CommonFirmataFeatures.ino create mode 100644 src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino create mode 100644 src/devices/Common/System/Device/Analog/AnalogController.cs create mode 100644 src/devices/Common/System/Device/Analog/AnalogInputPin.cs create mode 100644 src/devices/Common/System/Device/Analog/TriggerReason.cs create mode 100644 src/devices/Common/System/Device/Analog/ValueChangedEventArgs.cs create mode 100644 src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs diff --git a/src/devices/Arduino/Arduino.csproj b/src/devices/Arduino/Arduino.csproj index c0da12b58a..a955b01949 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 e26436d8c7..7cf230c24d 100644 --- a/src/devices/Arduino/Arduino.sln +++ b/src/devices/Arduino/Arduino.sln @@ -15,7 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mcp3xxx", "..\Mcp3xxx\Mcp3x EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CharacterLcd", "..\CharacterLcd\CharacterLcd.csproj", "{D47A6627-4041-4CEA-8903-A6C67C6993CF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "..\Common\Iot\Device\Common\CommonHelpers.csproj", "{CD593083-8E94-4B60-86BF-DF3FB7873893}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "..\Common\CommonHelpers.csproj", "{CD593083-8E94-4B60-86BF-DF3FB7873893}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CpuTemperature", "..\CpuTemperature\CpuTemperature.csproj", "{F3950513-A564-462F-887B-E00972D20FAD}" EndProject diff --git a/src/devices/Arduino/ArduinoAnalogController.cs b/src/devices/Arduino/ArduinoAnalogController.cs new file mode 100644 index 0000000000..582c070a90 --- /dev/null +++ b/src/devices/Arduino/ArduinoAnalogController.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Device.Analog; +using System.Device.Gpio; +using System.Linq; +using System.Text; +using System.Threading; + +#pragma warning disable CS1591 +namespace Iot.Device.Arduino +{ + internal class ArduinoAnalogController : AnalogController + { + private readonly ArduinoBoard _board; + private readonly List _supportedPinConfigurations; + private readonly Dictionary _callbacks; + private int _firstAnalogPin; + + public ArduinoAnalogController(ArduinoBoard board, + List supportedPinConfigurations, PinNumberingScheme scheme) + : base(scheme) + { + _board = board ?? throw new ArgumentNullException(nameof(board)); + _supportedPinConfigurations = supportedPinConfigurations ?? throw new ArgumentNullException(nameof(supportedPinConfigurations)); + _callbacks = new Dictionary(); + PinCount = _supportedPinConfigurations.Count; + + // Note: While the Arduino does have an external analog input reference pin, Firmata doesn't allow configuring it. + VoltageReference = 5.0; + // Number of the first analog pin. Serves for converting between logical A0-based pin numbers and digital pin numbers. + // The value of this is 14 for most arduinos. + var firstPin = _supportedPinConfigurations.FirstOrDefault(x => x.PinModes.Contains(SupportedMode.ANALOG_INPUT)); + if (firstPin != null) + { + _firstAnalogPin = firstPin.Pin; + } + else + { + _firstAnalogPin = 0; + } + } + + public override int PinCount + { + get; + } + + public override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber) + { + return pinNumber - _firstAnalogPin; + } + + public override int ConvertLogicalNumberingSchemeToPinNumber(int logicalPinNumber) + { + return logicalPinNumber + _firstAnalogPin; + } + + public override bool SupportsAnalogInput(int pinNumber) + { + return _supportedPinConfigurations[pinNumber].PinModes.Contains(SupportedMode.ANALOG_INPUT); + } + + protected override AnalogInputPin OpenPinInternal(int pinNumber) + { + // This method is called with the logical pin numbering (input pin A0 is 0, A1 is 1, etc) + // but the SetPinMode method operates on the global numbers + int fullNumber = ConvertLogicalNumberingSchemeToPinNumber(pinNumber); + _board.Firmata.SetPinMode(fullNumber, SupportedMode.ANALOG_INPUT); + _board.Firmata.EnableAnalogReporting(pinNumber); + return new ArduinoAnalogInputPin(_board, this, _supportedPinConfigurations[fullNumber], pinNumber, VoltageReference); + } + + public override void Close(AnalogInputPin pin) + { + _board.Firmata.DisableAnalogReporting(ConvertPinNumberToLogicalNumberingScheme(pin.PinNumber)); + } + + protected override void Dispose(bool disposing) + { + _callbacks.Clear(); + base.Dispose(disposing); + } + } +} diff --git a/src/devices/Arduino/ArduinoAnalogControllerDriver.cs b/src/devices/Arduino/ArduinoAnalogControllerDriver.cs deleted file mode 100644 index 6ccca4cbc6..0000000000 --- a/src/devices/Arduino/ArduinoAnalogControllerDriver.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Device.Analog; -using System.Device.Gpio; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Iot.Device.Arduino -{ - internal class ArduinoAnalogControllerDriver : AnalogControllerDriver - { - private readonly ArduinoBoard _board; - private readonly List _supportedPinConfigurations; - private readonly Dictionary _callbacks; - private int _autoReportingReferenceCount; - private int _firstAnalogPin; - - public ArduinoAnalogControllerDriver(ArduinoBoard board, - List supportedPinConfigurations) - { - _board = board ?? throw new ArgumentNullException(nameof(board)); - _supportedPinConfigurations = supportedPinConfigurations ?? throw new ArgumentNullException(nameof(supportedPinConfigurations)); - _callbacks = new Dictionary(); - _autoReportingReferenceCount = 0; - PinCount = _supportedPinConfigurations.Count; - VoltageReference = 5.0; - // Number of the first analog pin. Serves for converting between logical A0-based pin numbers and digital pin numbers. - // The value of this is 14 for most arduinos. - var firstPin = _supportedPinConfigurations.FirstOrDefault(x => x.PinModes.Contains(SupportedMode.ANALOG_INPUT)); - if (firstPin != null) - { - _firstAnalogPin = firstPin.Pin; - } - else - { - _firstAnalogPin = 0; - } - } - - public override int PinCount - { - get; - } - - protected override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber) - { - return pinNumber - _firstAnalogPin; - } - - protected override int ConvertLogicalNumberingSchemeToPinNumber(int logicalPinNumber) - { - return logicalPinNumber + _firstAnalogPin; - } - - public override bool SupportsAnalogInput(int pinNumber) - { - return _supportedPinConfigurations[pinNumber].PinModes.Contains(SupportedMode.ANALOG_INPUT); - } - - public override void EnableAnalogValueChangedEvent(int pinNumber, GpioController masterController, int masterPin) - { - // The pin is already open, so analog reporting is enabled, we just need to forward it. - if (_autoReportingReferenceCount == 0) - { - _board.Firmata.AnalogPinValueUpdated += FirmataOnAnalogPinValueUpdated; - } - - _autoReportingReferenceCount += 1; - } - - public override void DisableAnalogValueChangedEvent(int pinNumber) - { - _autoReportingReferenceCount -= 1; - if (_autoReportingReferenceCount == 0) - { - _board.Firmata.AnalogPinValueUpdated -= FirmataOnAnalogPinValueUpdated; - } - } - - private void FirmataOnAnalogPinValueUpdated(int pin, uint rawvalue) - { - if (_autoReportingReferenceCount > 0) - { - int physicalPin = ConvertLogicalNumberingSchemeToPinNumber(pin); - double voltage = ConvertToVoltage(physicalPin, rawvalue); - var message = new ValueChangedEventArgs(rawvalue, voltage, physicalPin, TriggerReason.Timed); - FireValueChanged(message); - } - } - - public override void OpenPin(int pinNumber) - { - if (!_supportedPinConfigurations[pinNumber].PinModes.Contains(SupportedMode.ANALOG_INPUT)) - { - throw new NotSupportedException($"Pin {pinNumber} does not support Analog input"); - } - - _board.Firmata.SetPinMode(pinNumber, SupportedMode.ANALOG_INPUT); - _board.Firmata.EnableAnalogReporting(ConvertPinNumberToLogicalNumberingScheme(pinNumber)); - } - - public override void ClosePin(int pinNumber) - { - _board.Firmata.DisableAnalogReporting(ConvertPinNumberToLogicalNumberingScheme(pinNumber)); - } - - /// - /// Return the resolution of an analog input pin. - /// - /// The pin number - /// Returns the resolution of the ADC in number of bits, including the sign bit (if applicable) - /// Minimum measurable voltage - /// Maximum measurable voltage - public override void QueryResolution(int pinNumber, out int numberOfBits, out double minVoltage, out double maxVoltage) - { - numberOfBits = _supportedPinConfigurations[pinNumber].AnalogInputResolutionBits; - minVoltage = 0.0; - maxVoltage = VoltageReference; - } - - public override uint ReadRaw(int pinNumber) - { - return _board.Firmata.GetAnalogRawValue(ConvertPinNumberToLogicalNumberingScheme(pinNumber)); - } - - protected override void Dispose(bool disposing) - { - _callbacks.Clear(); - base.Dispose(disposing); - } - } -} diff --git a/src/devices/Arduino/ArduinoAnalogInputPin.cs b/src/devices/Arduino/ArduinoAnalogInputPin.cs new file mode 100644 index 0000000000..0c0f062e92 --- /dev/null +++ b/src/devices/Arduino/ArduinoAnalogInputPin.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Device.Analog; +using System.Device.Gpio; +using System.Text; + +namespace Iot.Device.Arduino +{ + internal class ArduinoAnalogInputPin : AnalogInputPin + { + private readonly SupportedPinConfiguration _configuration; + private int _autoReportingReferenceCount; + private ArduinoBoard _board; + + public ArduinoAnalogInputPin(ArduinoBoard board, AnalogController controller, SupportedPinConfiguration configuration, + int pinNumber, double voltageReference) + : base(controller, pinNumber, voltageReference) + { + _board = board; + _configuration = configuration; + } + + public override void EnableAnalogValueChangedEvent(GpioController masterController, int masterPin) + { + // The pin is already open, so analog reporting is enabled, we just need to forward it. + if (_autoReportingReferenceCount == 0) + { + _board.Firmata.AnalogPinValueUpdated += FirmataOnAnalogPinValueUpdated; + } + + _autoReportingReferenceCount += 1; + } + + public override void DisableAnalogValueChangedEvent() + { + _autoReportingReferenceCount -= 1; + if (_autoReportingReferenceCount == 0) + { + _board.Firmata.AnalogPinValueUpdated -= FirmataOnAnalogPinValueUpdated; + } + } + + private void FirmataOnAnalogPinValueUpdated(int pin, uint rawvalue) + { + if (_autoReportingReferenceCount > 0) + { + int physicalPin = Controller.ConvertLogicalNumberingSchemeToPinNumber(pin); + double voltage = ConvertToVoltage(rawvalue); + var message = new ValueChangedEventArgs(rawvalue, voltage, physicalPin, TriggerReason.Timed); + FireValueChanged(message); + } + } + + public override void QueryResolution(out int numberOfBits, out double minVoltage, out double maxVoltage) + { + numberOfBits = _configuration.AnalogInputResolutionBits; + minVoltage = 0.0; + maxVoltage = VoltageReference; + } + + public override uint ReadRaw() + { + return _board.Firmata.GetAnalogRawValue(PinNumber); + } + + public override bool SupportsAnalogInput() + { + return _configuration.PinModes.Contains(SupportedMode.ANALOG_INPUT); + } + } +} diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index 06c761a3d2..4dc1bc5898 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.IO; using System.Threading; +using System.Linq; using UnitsNet; #pragma warning disable CS1591 @@ -30,9 +31,6 @@ public class ArduinoBoard : IDisposable private string _firmwareName; private List _supportedPinConfigurations; - // Only a delegate, not an event, because one board can only have one compiler attached at a time - private Action _compilerCallback; - // Counts how many spi devices are attached, to make sure we enable/disable the bus only when no devices are attached private int _spiEnabled; @@ -73,8 +71,6 @@ public virtual void Initialize() // _firmata.SetSamplingInterval(TimeSpan.FromMilliseconds(100)); _firmata.EnableDigitalReporting(); - - _firmata.OnSchedulerReply += FirmataOnSchedulerReply; } public Version FirmwareVersion @@ -119,19 +115,6 @@ private void FirmataOnError(string message, Exception innerException) LogMessages?.Invoke(message, innerException); } - private void FirmataOnSchedulerReply(byte method, MethodState schedulerMethodState, int numArgs, IList bytesOfArgs) - { - object[] data = new object[numArgs]; - - for (int i = 0; i < numArgs * 4; i += 4) - { - int retVal = bytesOfArgs[i] | bytesOfArgs[i + 1] << 8 | bytesOfArgs[i + 2] << 16 | bytesOfArgs[i + 3] << 24; - data[i / 4] = retVal; - } - - _compilerCallback?.Invoke(method, schedulerMethodState, data); - } - public GpioController CreateGpioController(PinNumberingScheme pinNumberingScheme) { return new GpioController(pinNumberingScheme, new ArduinoGpioControllerDriver(this, _supportedPinConfigurations)); @@ -139,6 +122,11 @@ public GpioController CreateGpioController(PinNumberingScheme pinNumberingScheme public I2cDevice CreateI2cDevice(I2cConnectionSettings 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); } @@ -155,6 +143,11 @@ public SpiDevice CreateSpiDevice(SpiConnectionSettings settings) throw new NotSupportedException("Only Bus Id 0 is supported"); } + if (!SupportedPinConfigurations.Any(x => x.PinModes.Contains(SupportedMode.SPI))) + { + throw new NotSupportedException("No Pins with SPI support found. Is the SPI module loaded?"); + } + return new ArduinoSpiDevice(this, settings); } @@ -169,7 +162,7 @@ public PwmChannel CreatePwmChannel( public AnalogController CreateAnalogController(int chip) { - return new AnalogController(PinNumberingScheme.Logical, new ArduinoAnalogControllerDriver(this, _supportedPinConfigurations)); + return new ArduinoAnalogController(this, SupportedPinConfigurations, PinNumberingScheme.Logical); } /// @@ -235,21 +228,5 @@ internal void DisableSpi() _firmata.DisableSpi(); } } - - public void SetCompilerCallback(Action onCompilerCallback) - { - if (onCompilerCallback == null) - { - _compilerCallback = null; - return; - } - - if (_compilerCallback != null) - { - throw new InvalidOperationException("Only one compiler can be active for a single board"); - } - - _compilerCallback = onCompilerCallback; - } } } diff --git a/src/devices/Arduino/ArduinoI2cDevice.cs b/src/devices/Arduino/ArduinoI2cDevice.cs index 7e73554afb..dca776c6c0 100644 --- a/src/devices/Arduino/ArduinoI2cDevice.cs +++ b/src/devices/Arduino/ArduinoI2cDevice.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using System.Device.Gpio.I2c; using System.Device.I2c; +using System.IO; using System.Linq; using System.Text; @@ -53,7 +53,7 @@ public ArduinoI2cDevice(ArduinoBoard board, I2cConnectionSettings connectionSett ReadByte(); break; } - catch (Exception x) when (x is TimeoutException || x is I2cCommunicationException) + catch (Exception x) when (x is TimeoutException || x is IOException) { } } diff --git a/src/devices/Arduino/ArduinoMethodDecl.cs b/src/devices/Arduino/ArduinoMethodDecl.cs deleted file mode 100644 index bc03617714..0000000000 --- a/src/devices/Arduino/ArduinoMethodDecl.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Reflection; - -#pragma warning disable CS1591 -namespace Iot.Device.Arduino -{ - public sealed class ArduinoMethodDeclaration - { - public ArduinoMethodDeclaration(int index, MethodInfo methodInfo) - { - Index = index; - MethodInfo = methodInfo; - Flags = MethodFlags.None; - var body = methodInfo.GetMethodBody(); - Token = methodInfo.MetadataToken; - MaxLocals = body.LocalVariables.Count; - MaxStack = body.MaxStackSize; - ArgumentCount = methodInfo.GetParameters().Length; - if (methodInfo.CallingConvention.HasFlag(CallingConventions.HasThis)) - { - ArgumentCount += 1; - } - - if (methodInfo.IsStatic) - { - Flags |= MethodFlags.Static; - } - - if (methodInfo.IsVirtual) - { - Flags |= MethodFlags.Virtual; - } - - if (methodInfo.ReturnParameter == null || methodInfo.ReturnParameter.ParameterType == typeof(void)) - { - Flags |= MethodFlags.Void; - } - } - - public ArduinoMethodDeclaration(int index, int token, MethodInfo methodInfo, MethodFlags flags, int maxStack) - { - Index = index; - Token = token; - MethodInfo = methodInfo; - Flags = flags; - MaxLocals = MaxStack = maxStack; - ArgumentCount = methodInfo.GetParameters().Length; - if (methodInfo.CallingConvention.HasFlag(CallingConventions.HasThis)) - { - ArgumentCount += 1; - } - - if (methodInfo.ReturnParameter.ParameterType == typeof(void)) - { - Flags |= MethodFlags.Void; - } - } - - public int Index { get; } - public int Token { get; } - public MethodInfo MethodInfo { get; } - - public MethodFlags Flags - { - get; - } - - public int MaxLocals - { - get; - } - - public int MaxStack - { - get; - } - - public int ArgumentCount - { - get; - } - } -} diff --git a/src/devices/Arduino/ArduinoTask.cs b/src/devices/Arduino/ArduinoTask.cs deleted file mode 100644 index 72cec915ff..0000000000 --- a/src/devices/Arduino/ArduinoTask.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -#pragma warning disable CS1591 -namespace Iot.Device.Arduino -{ - public interface IArduinoTask : IDisposable - { - MethodState State { get; } - ArduinoMethodDeclaration MethodInfo { get; } - void AddData(MethodState state, object[] args); - } - - public sealed class ArduinoTask : IArduinoTask, IDisposable - where T : Delegate - { - private ConcurrentQueue<(MethodState, object[])> _collectedValues; - private AutoResetEvent _dataAdded; - internal ArduinoTask(T function, ArduinoCsCompiler compiler, ArduinoMethodDeclaration methodInfo) - { - Function = function; - Compiler = compiler; - MethodInfo = methodInfo; - State = MethodState.Stopped; - _collectedValues = new ConcurrentQueue<(MethodState, object[])>(); - _dataAdded = new AutoResetEvent(false); - } - - public T Function { get; } - public ArduinoCsCompiler Compiler { get; } - public ArduinoMethodDeclaration MethodInfo { get; } - - /// - /// Returns the current state of the task - /// - public MethodState State - { - get; - private set; - } - - public void AddData(MethodState state, object[] args) - { - _collectedValues.Enqueue((state, args)); - State = state; - _dataAdded.Set(); - } - - public void InvokeAsync(params int[] arguments) - { - if (State == MethodState.Running) - { - throw new InvalidOperationException("Task is already running"); - } - - State = MethodState.Running; - Compiler.Invoke(MethodInfo.MethodInfo, arguments); - } - - public bool Invoke(CancellationToken cancellationToken, params int[] arguments) - { - InvokeAsync(arguments); - Task task = WaitForResult(cancellationToken); - task.Wait(cancellationToken); - return task.Result; - } - - public void Terminate() - { - Compiler.KillTask(MethodInfo.MethodInfo); - } - - /// - /// Returns a data set obtained from the realtime method. - /// If this returns false, no data is available. - /// If this returns true, the next data set is returned, together with the state of the task at that point. - /// If the returned state is , the data returned is the return value of the method. - /// - /// A set of values sent or returned by the task method - /// The state of the method matching the task at that time - /// True if data was available, false otherwise - public bool GetMethodResults(out object[] data, out MethodState state) - { - if (_collectedValues.TryDequeue(out var d)) - { - data = d.Item2; - state = d.Item1; - return true; - } - - data = null; - state = State; - return false; - } - - public void Dispose() - { - Compiler.TaskDone(this); - _collectedValues.Clear(); - _dataAdded?.Dispose(); - _dataAdded = null; - } - - /// - /// Await at least one result data set (or the end of the method) - /// - /// True if there is at least one data set, false otherwise - public async Task WaitForResult(CancellationToken token) - { - if (_dataAdded == null) - { - throw new ObjectDisposedException(nameof(ArduinoTask)); - } - - if (State != MethodState.Running) - { - return false; - } - - var task = Task.Factory.StartNew(() => - { - while (_collectedValues.IsEmpty && State == MethodState.Running) - { - if (token.IsCancellationRequested) - { - return false; - } - - WaitHandle.WaitAny(new WaitHandle[] - { - token.WaitHandle, _dataAdded - }); - } - - return !_collectedValues.IsEmpty; - }); - - await task; - return task.Result; - } - - public bool WaitForResult() - { - var task = WaitForResult(CancellationToken.None); - task.Wait(); - return task.Result; - } - } -} diff --git a/src/devices/Arduino/CommonFirmataFeatures.ino b/src/devices/Arduino/CommonFirmataFeatures.ino deleted file mode 100644 index 3cbe09f9c3..0000000000 --- a/src/devices/Arduino/CommonFirmataFeatures.ino +++ /dev/null @@ -1,102 +0,0 @@ -/* - * CommonFirmataFeatures.ino generated by FirmataBuilder - * Sun Mar 29 2020 15:10:48 GMT-0400 (EDT) - */ - -#include - -#include -DigitalInputFirmata digitalInput; - -#include -DigitalOutputFirmata digitalOutput; - -#include -AnalogInputFirmata analogInput; - -#include -AnalogOutputFirmata analogOutput; - -#include -#include -I2CFirmata i2c; - -#include -OneWireFirmata oneWire; - -#include -SerialFirmata serial; - -#include -FirmataExt firmataExt; - -#include - -// The scheduler allows to store scripts on the board, however this requires a kind of compiler on the client side. -// The feature only needs 20Bytes of Ram, so it doesn't hurt to have it (There's enough flash left) -#include -FirmataScheduler scheduler; - -#include -FirmataReporting reporting; - -void systemResetCallback() -{ - for (byte i = 0; i < TOTAL_PINS; i++) { - if (IS_PIN_ANALOG(i)) { - Firmata.setPinMode(i, ANALOG); - } else if (IS_PIN_DIGITAL(i)) { - Firmata.setPinMode(i, OUTPUT); - } - } - firmataExt.reset(); -} - -void initTransport() -{ - // Uncomment to save a couple of seconds by disabling the startup blink sequence. - // Firmata.disableBlinkVersion(); - Firmata.begin(115200); -} - -void initFirmata() -{ - Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION); - - firmataExt.addFeature(digitalInput); - firmataExt.addFeature(digitalOutput); - firmataExt.addFeature(analogInput); - firmataExt.addFeature(analogOutput); - firmataExt.addFeature(i2c); - firmataExt.addFeature(oneWire); - firmataExt.addFeature(serial); - firmataExt.addFeature(scheduler); - firmataExt.addFeature(reporting); - - Firmata.attach(SYSTEM_RESET, systemResetCallback); -} - -void setup() -{ - initFirmata(); - - initTransport(); - - Firmata.parse(SYSTEM_RESET); -} - -void loop() -{ - digitalInput.report(); - - while(Firmata.available()) { - Firmata.processInput(); - } - - if (reporting.elapsed()) { - analogInput.report(); - i2c.report(); - } - - serial.update(); -} diff --git a/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino b/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino new file mode 100644 index 0000000000..429e74b6f1 --- /dev/null +++ b/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino @@ -0,0 +1,339 @@ +/* + * CommonFirmataFeatures.ino generated by FirmataBuilder + */ + +// Use these defines to easily enable or disable certain modules + +/* Note: Currently no client support by dotnet/iot for these, so they're disabled by default */ +/* Enabling all modules on the smaller Arduino boards (such as the UNO or the Nano) won't work anyway, as there is both + * not enough flash as well as not enough RAM. + */ +//#define ENABLE_ONE_WIRE +//#define ENABLE_SERVO +//#define ENABLE_ACCELSTEPPER +//#define ENABLE_BASIC_SCHEDULER +//#define ENABLE_SERIAL + +// Support for analog in and analog out (PWM or real DACs, if available) +#define ENABLE_ANALOG +// Support for digital in and digital out +#define ENABLE_DIGITAL + +// I2C support (most boards have just one channel) +#define ENABLE_I2C +// SPI support. This feature requires a non-standard module compiled into the firmware +// #define ENABLE_SPI + +// Native reading of DHTXX sensors. Reading a DHT11 directly using GPIO methods from a remote PC will not work, because of the very tight timing requirements of these sensors +// This feature requires a non-standard module compiled into the firmware +// #define ENABLE_DHT + + +#include + +#ifdef ENABLE_DIGITAL +#include +DigitalInputFirmata digitalInput; + +#include +DigitalOutputFirmata digitalOutput; +#endif + +#ifdef ENABLE_ANALOG +#include +AnalogInputFirmata analogInput; + +#include +AnalogOutputFirmata analogOutput; +#endif + +#ifdef ENABLE_I2C +#include +#include +I2CFirmata i2c; +#endif + +#ifdef ENABLE_ONE_WIRE +#include +OneWireFirmata oneWire; +#endif + +#ifdef ENABLE_SERIAL +#include +SerialFirmata serial; +#endif + +#include +FirmataExt firmataExt; + +#ifdef ENABLE_SPI +#include +SpiFirmata spi; +#endif + +#ifdef ENABLE_SERVO +#include +#include +ServoFirmata servo; +#endif +#include + +#ifdef ENABLE_BASIC_SCHDULER +// The scheduler allows to store scripts on the board, however this requires a kind of compiler on the client side. +// When running dotnet/iot on the client side, prefer using the FirmataIlExecutor module instead +#include +FirmataScheduler scheduler; +#endif + +#include +FirmataReporting reporting; + +#ifdef ENABLE_ACCELSTEPPER +#include +AccelStepperFirmata accelStepper; +#endif + +#ifdef ENABLE_DHT +#include +DhtFirmata dhtFirmata; +#endif + +#ifdef ENABLE_IL_EXECUTOR +#include "FirmataIlExecutor.h" +FirmataIlExecutor ilExecutor; +#endif +#ifdef DEBUG_STREAM +const byte SimulatedInput[] PROGMEM = +{ +0xFF, 0xF9, 0xF0, 0x79, 0xF7, 0xF0, 0x6B, 0xF7, 0xF0, 0x69, 0xF7, 0xD0, 0x01, 0xD1, 0x01, 0xD2, +0x01, 0xF0, 0x7B, 0xFF, 0x05, 0x01, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x00, 0x01, 0x08, 0x02, 0x17, +0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x00, 0x04, 0x00, 0x00, +0x00, 0x02, 0x00, 0x03, 0x00, 0x58, 0x00, 0x2A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x00, 0x02, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, +0xF0, 0x7B, 0xFF, 0x04, 0x00, 0x7F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x01, 0x01, 0x08, 0x02, 0x1A, 0x00, +0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x01, 0x05, 0x00, 0x00, 0x00, +0x02, 0x00, 0x03, 0x00, 0x7E, 0x01, 0x01, 0x00, 0x2A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x01, +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, +0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x02, 0x01, 0x08, 0x02, 0x13, +0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x02, 0x1B, 0x00, 0x00, +0x00, 0x03, 0x00, 0x1F, 0x00, 0x14, 0x00, 0x32, 0x00, 0x02, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x02, +0x00, 0x03, 0x00, 0x31, 0x00, 0x02, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x03, 0x00, 0x18, 0x00, 0x2E, +0x00, 0x02, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x02, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x02, 0x1B, +0x00, 0x14, 0x00, 0x16, 0x00, 0x30, 0x00, 0x02, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x17, 0x00, 0x2A, +0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, +0xF0, 0x7B, 0xFF, 0x05, 0x01, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x00, 0x04, 0x04, 0x01, 0x49, 0x01, +0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x01, 0x0C, 0x01, 0x03, 0x4A, +0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x02, 0x0C, 0x02, 0x03, +0x4B, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x03, 0x04, 0x03, +0x02, 0x4C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x04, 0x0C, +0x05, 0x02, 0x4D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x05, +0x04, 0x06, 0x01, 0x4E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, +0x06, 0x0C, 0x07, 0x02, 0x4F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, +0x01, 0x07, 0x09, 0x03, 0x02, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, +0xFF, 0x03, 0x07, 0x3E, 0x00, 0x00, 0x00, 0x03, 0x00, 0x16, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x02, +0x00, 0x20, 0x00, 0x2A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x4F, 0x01, 0x00, +0x00, 0x00, 0x00, 0x06, 0x00, 0x2A, 0x00, 0x02, 0x00, 0x6F, 0x00, 0x49, 0x01, 0x00, 0x00, 0xF7, +0xF0, 0x7B, 0xFF, 0x03, 0x07, 0x3E, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x06, +0x00, 0x03, 0x00, 0x58, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x07, 0x00, 0x31, 0x00, 0x1A, 0x00, 0x06, +0x00, 0x0C, 0x00, 0x2B, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x02, 0x00, 0x6F, 0x00, 0x49, +0x01, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x07, 0x3E, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, +0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x32, 0x00, 0x73, 0x01, 0x2B, 0x00, 0x07, 0x00, 0x02, +0x00, 0x6F, 0x00, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, +0x00, 0x30, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x07, 0x3E, 0x00, 0x3C, 0x00, 0x75, 0x01, 0x2A, +0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x08, 0x01, 0x07, 0x02, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, +0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x02, 0x08, 0x0C, 0x00, 0x00, 0x00, 0x4A, 0x01, 0x00, 0x00, +0x00, 0x00, 0x06, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x4B, 0x01, 0x00, 0x00, +0x00, 0x00, 0x06, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, +0x02, 0x08, 0x0C, 0x00, 0x04, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x01, +0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x4F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x01, +0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x02, 0x08, 0x0C, 0x00, 0x08, 0x00, +0x4C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, +0x4E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, +0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x00, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, 0x00, +0x0C, 0x00, 0x1F, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x20, 0x00, 0x50, 0x01, 0x07, 0x00, 0x00, 0x00, +0x00, 0x00, 0x13, 0x00, 0x04, 0x00, 0x02, 0x00, 0x03, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7E, 0x00, +0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x14, 0x00, 0x00, 0x00, 0x0A, 0x00, +0x02, 0x00, 0x03, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, +0x02, 0x00, 0x09, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, +0x02, 0x00, 0x09, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x28, 0x00, 0x16, 0x00, +0x6F, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x1F, 0x00, 0x14, 0x00, +0x28, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x16, 0x00, +0x6F, 0x00, 0x7F, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x3C, 0x00, +0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x1F, 0x00, 0x14, 0x00, 0x28, 0x00, 0x00, 0x01, 0x00, 0x00, +0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x19, 0x00, 0x6F, 0x00, 0x7E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x04, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, +0x50, 0x00, 0x0A, 0x00, 0x2B, 0x00, 0x14, 0x00, 0x06, 0x00, 0x25, 0x00, 0x17, 0x00, 0x59, 0x00, +0x0A, 0x00, 0x2D, 0x00, 0x0D, 0x00, 0x02, 0x00, 0x20, 0x00, 0x2A, 0x01, 0x2A, 0x01, 0x00, 0x00, +0x00, 0x00, 0x6F, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, +0x28, 0x02, 0x64, 0x00, 0x0A, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x6F, 0x00, +0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x2C, 0x00, 0x63, 0x01, 0x11, 0x00, 0x04, 0x00, +0x0A, 0x00, 0x2B, 0x00, 0x1C, 0x00, 0x06, 0x00, 0x25, 0x00, 0x17, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, +0x03, 0x08, 0x28, 0x02, 0x78, 0x00, 0x59, 0x00, 0x0A, 0x00, 0x2D, 0x00, 0x15, 0x00, 0x02, 0x00, +0x09, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, +0x20, 0x00, 0x3B, 0x01, 0x3B, 0x01, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x01, 0x01, 0xF7, 0xF0, +0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x0C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x16, 0x00, +0x2A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x6F, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, +0x17, 0x00, 0x2E, 0x00, 0x5A, 0x01, 0x02, 0x00, 0x09, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7F, 0x00, +0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, +0x16, 0x00, 0x13, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x78, 0x00, 0x11, 0x00, 0x04, 0x00, 0x0A, 0x00, +0x2B, 0x00, 0x11, 0x00, 0x06, 0x00, 0x25, 0x00, 0x17, 0x00, 0x59, 0x00, 0x0A, 0x00, 0x2D, 0x00, +0x0A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x34, 0x01, 0x02, 0x00, 0x11, 0x00, +0x05, 0x00, 0x6F, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x16, 0x00, 0x2A, 0x00, +0x02, 0x00, 0x03, 0x00, 0x6F, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x2C, 0x00, +0x66, 0x01, 0x02, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x48, 0x01, 0x6F, 0x00, +0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x13, 0x00, 0x06, 0x00, 0x11, 0x00, 0x04, 0x00, +0x0A, 0x00, 0x2B, 0x00, 0x11, 0x00, 0x06, 0x00, 0x25, 0x00, 0x17, 0x00, 0x59, 0x00, 0x0A, 0x00, +0x2D, 0x00, 0x0A, 0x00, 0x02, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x5C, 0x01, +0x11, 0x00, 0x05, 0x00, 0x6F, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x16, 0x00, +0x2A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x6F, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, +0x17, 0x00, 0x2E, 0x00, 0x65, 0x01, 0x02, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, +0x70, 0x01, 0x6F, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x06, 0x00, +0x59, 0x00, 0x07, 0x00, 0x17, 0x00, 0x62, 0x00, 0x0B, 0x00, 0x20, 0x00, 0x40, 0x00, 0x42, 0x00, +0x0F, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x20, 0x00, 0x40, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, +0x28, 0x02, 0x04, 0x02, 0x42, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x1F, 0x00, 0x1E, 0x00, +0x36, 0x00, 0x04, 0x00, 0x07, 0x00, 0x17, 0x00, 0x60, 0x00, 0x0B, 0x00, 0x11, 0x00, 0x05, 0x00, +0x1F, 0x00, 0x1F, 0x00, 0x33, 0x00, 0x04, 0x00, 0x07, 0x00, 0x0C, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, +0x03, 0x08, 0x28, 0x02, 0x18, 0x02, 0x16, 0x00, 0x0B, 0x00, 0x11, 0x00, 0x05, 0x00, 0x17, 0x00, +0x58, 0x00, 0x13, 0x00, 0x05, 0x00, 0x11, 0x00, 0x05, 0x00, 0x1F, 0x00, 0x28, 0x00, 0x32, 0x00, +0x02, 0x01, 0x08, 0x00, 0x2A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, +0x06, 0x08, 0xF7, +}; + +#include +FlashMemoryStream debugStream(SimulatedInput, sizeof(SimulatedInput)); +#endif + +void systemResetCallback() +{ + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_PIN_ANALOG(i)) { + Firmata.setPinMode(i, ANALOG); + } else if (IS_PIN_DIGITAL(i)) { + Firmata.setPinMode(i, OUTPUT); + } + } + firmataExt.reset(); +} + +void initTransport() +{ + // Uncomment to save a couple of seconds by disabling the startup blink sequence. + // Firmata.disableBlinkVersion(); +#ifdef DEBUG_STREAM + Firmata.begin(debugStream); +#else + Firmata.begin(115200); +#endif +} + +void initFirmata() +{ + // Set firmware name and version. The name is automatically derived from the name of this file. + // Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION); + // The usage of the above shortcut is not recommended, since it stores the full path of the file name in a + // string constant, using both flash and ram. + Firmata.setFirmwareNameAndVersion("ConfigurableFirmata", FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION); + +#ifdef ENABLE_DIGITAL + firmataExt.addFeature(digitalInput); + firmataExt.addFeature(digitalOutput); +#endif + +#ifdef ENABLE_ANALOG + firmataExt.addFeature(analogInput); + firmataExt.addFeature(analogOutput); +#endif + +#ifdef ENABLE_SERVO + firmataExt.addFeature(servo); +#endif + +#ifdef ENABLE_I2C + firmataExt.addFeature(i2c); +#endif + +#ifdef ENABLE_ONE_WIRE + firmataExt.addFeature(oneWire); +#endif + +#ifdef ENABLE_SERIAL + firmataExt.addFeature(serial); +#endif + +#ifdef ENABLE_BASIC_SCHEDULER + firmataExt.addFeature(scheduler); +#endif + + firmataExt.addFeature(reporting); +#ifdef ENABLE_SPI + firmataExt.addFeature(spi); +#endif +#ifdef ENABLE_ACCELSTEPPER + firmataExt.addFeature(accelStepper); +#endif + +#ifdef ENABLE_DHT + firmataExt.addFeature(dhtFirmata); +#endif + +#ifdef ENABLE_IL_EXECUTOR + firmataExt.addFeature(ilExecutor); +#endif + + Firmata.attach(SYSTEM_RESET, systemResetCallback); +} + +void setup() +{ + initFirmata(); + + initTransport(); + + Firmata.parse(SYSTEM_RESET); +} + +void loop() +{ + while(Firmata.available()) { + Firmata.processInput(); + if (!Firmata.isParsingMessage()) { + goto runtasks; + } + } + if (!Firmata.isParsingMessage()) + { +runtasks: +#ifdef ENABLE_BASIC_SCHEDULER + scheduler.runTasks(); +#endif +#ifdef ENABLE_IL_EXECUTOR + ilExecutor.runStep(); +#else + 0 == 0; // Do something useless +#endif + } + + if (reporting.elapsed()) { +#ifdef ENABLE_ANALOG + analogInput.report(); +#endif +#ifdef ENABLE_I2C + i2c.report(); +#endif + } + +#ifdef ENABLE_DIGITAL + digitalInput.report(); +#endif + +#ifdef ENABLE_ACCELSTEPPER + accelStepper.update(); +#endif +#ifdef ENABLE_SERIAL + serial.update(); +#endif +} diff --git a/src/devices/Arduino/FirmataDevice.cs b/src/devices/Arduino/FirmataDevice.cs index 211757625b..a161da8cfb 100644 --- a/src/devices/Arduino/FirmataDevice.cs +++ b/src/devices/Arduino/FirmataDevice.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.Device.Gpio; -using System.Device.Gpio.I2c; using System.Device.Spi; using System.Diagnostics; using System.IO; @@ -51,7 +50,6 @@ internal sealed class FirmataDevice : IDisposable // Event used when waiting for answers (i.e. after requesting firmware version) private AutoResetEvent _dataReceived; - public event Action> OnSchedulerReply; public event DigitalPinValueChanged DigitalPortValueUpdated; @@ -465,39 +463,6 @@ private void ProcessInput() _dataReceived.Set(); break; - case FirmataSysexCommand.SCHEDULER_DATA: - { - if (raw_data.Length == 4 && raw_data[1] == (byte)ExecutorCommand.Ack) - { - // Just an ack for a programming command. - _lastIlExecutionError = 0; - _dataReceived.Set(); - return; - } - - if (raw_data.Length == 4 && raw_data[1] == (byte)ExecutorCommand.Nack) - { - // This is a Nack - _lastIlExecutionError = raw_data[3]; - _dataReceived.Set(); - return; - } - - // Data from real-time methods - if (raw_data.Length < 7) - { - OnError?.Invoke("Code execution returned invalid result or state", null); - break; - } - - int numArgs = raw_data[3]; - Span bytesReceived = stackalloc byte[numArgs * 4]; - ReassembleByteString(raw_data, 4, numArgs * 8, bytesReceived); - - OnSchedulerReply?.Invoke(raw_data[1], (MethodState)raw_data[2], numArgs, bytesReceived.ToArray()); - break; - } - default: // we pass the data forward as-is for any other type of sysex command @@ -855,17 +820,17 @@ public void WriteReadI2cData(int slaveAddress, ReadOnlySpan writeData, Sp bool result = _dataReceived.WaitOne(DefaultReplyTimeout); if (result == false) { - throw new I2cCommunicationException("Timeout waiting for device reply"); + throw new TimeoutException("Timeout waiting for device reply"); } if (_lastResponse[0] != (byte)FirmataSysexCommand.I2C_REPLY) { - throw new I2cCommunicationException("Firmata protocol error: received incorrect query response"); + throw new IOException("Firmata protocol error: received incorrect query response"); } if (_lastResponse[1] != (byte)slaveAddress && slaveAddress != 0) { - throw new I2cCommunicationException($"Firmata protocol error: The wrong device did answer. Expected {slaveAddress} but got {_lastResponse[1]}."); + throw new IOException($"Firmata protocol error: The wrong device did answer. Expected {slaveAddress} but got {_lastResponse[1]}."); } // Byte 0: I2C_REPLY @@ -876,7 +841,7 @@ public void WriteReadI2cData(int slaveAddress, ReadOnlySpan writeData, Sp if (replyData.Length != bytesReceived) { - throw new I2cCommunicationException($"Expected {replyData.Length} bytes, got only {bytesReceived}"); + throw new IOException($"Expected {replyData.Length} bytes, got only {bytesReceived}"); } } } @@ -964,17 +929,17 @@ public void SpiTransfer(int csPin, ReadOnlySpan writeBytes, Span rea bool result = _dataReceived.WaitOne(DefaultReplyTimeout); if (result == false) { - throw new I2cCommunicationException("Timeout waiting for device reply"); + throw new TimeoutException("Timeout waiting for device reply"); } if (_lastResponse[0] != (byte)FirmataSysexCommand.SPI_DATA || _lastResponse[1] != (byte)FirmataSpiCommand.SPI_REPLY) { - throw new I2cCommunicationException("Firmata protocol error: received incorrect query response"); + throw new IOException("Firmata protocol error: received incorrect query response"); } if (_lastResponse[3] != (byte)requestId) { - throw new I2cCommunicationException($"Firmata protocol sequence error."); + throw new IOException($"Firmata protocol sequence error."); } ReassembleByteString(_lastResponse, 5, _lastResponse[4] * 2, readBytes); @@ -1113,131 +1078,6 @@ private void WaitAndHandleIlCommandReply() } } - public void SendMethodIlCode(byte methodIndex, byte[] byteCode) - { - lock (_synchronisationLock) - { - const int BYTES_PER_PACKET = 20; - int codeIndex = 0; - while (codeIndex < byteCode.Length) - { - _dataReceived.Reset(); - _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); - _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); - _firmataStream.WriteByte((byte)0xFF); // IL data - _firmataStream.WriteByte((byte)ExecutorCommand.LoadIl); - _firmataStream.WriteByte(methodIndex); - _firmataStream.WriteByte((byte)byteCode.Length); - _firmataStream.WriteByte((byte)codeIndex); - int bytesThisPacket = Math.Min(BYTES_PER_PACKET, byteCode.Length - codeIndex); - SendValuesAsTwo7bitBytes(byteCode.AsSpan(codeIndex, bytesThisPacket)); - codeIndex += bytesThisPacket; - - _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); - _firmataStream.Flush(); - - WaitAndHandleIlCommandReply(); - } - } - } - - public void ExecuteIlCode(byte codeReference, int[] parameters, Type returnType) - { - lock (_synchronisationLock) - { - _dataReceived.Reset(); - _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); - _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); - _firmataStream.WriteByte((byte)0xFF); // IL data - _firmataStream.WriteByte((byte)ExecutorCommand.StartTask); - _firmataStream.WriteByte(codeReference); - for (int i = 0; i < parameters.Length; i++) - { - byte[] param = BitConverter.GetBytes(parameters[i]); - SendValuesAsTwo7bitBytes(param); - } - - _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); - _firmataStream.Flush(); - WaitAndHandleIlCommandReply(); - } - } - - public void SendMethodDeclaration(byte codeReference, int declarationToken, MethodFlags methodFlags, byte maxLocals, byte argCount) - { - lock (_synchronisationLock) - { - _dataReceived.Reset(); - _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); - _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); - _firmataStream.WriteByte((byte)0xFF); // IL data - _firmataStream.WriteByte((byte)ExecutorCommand.DeclareMethod); - _firmataStream.WriteByte(codeReference); - _firmataStream.WriteByte((byte)methodFlags); - _firmataStream.WriteByte(maxLocals); - _firmataStream.WriteByte(argCount); - byte[] param = BitConverter.GetBytes(declarationToken); - SendValuesAsTwo7bitBytes(param); - - _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); - _firmataStream.Flush(); - - WaitAndHandleIlCommandReply(); - } - } - - public void SendTokenMap(byte codeReference, int[] data) - { - lock (_synchronisationLock) - { - _dataReceived.Reset(); - _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); - _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); - _firmataStream.WriteByte((byte)0xFF); // IL data - _firmataStream.WriteByte((byte)ExecutorCommand.SetMethodTokens); - _firmataStream.WriteByte(codeReference); - for (int i = 0; i < data.Length; i++) - { - byte[] param = BitConverter.GetBytes(data[i]); - SendValuesAsTwo7bitBytes(param); - } - - _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); - _firmataStream.Flush(); - WaitAndHandleIlCommandReply(); - } - } - - public void SendIlResetCommand(bool force) - { - lock (_synchronisationLock) - { - _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); - _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); - _firmataStream.WriteByte((byte)0xFF); // IL data - _firmataStream.WriteByte((byte)ExecutorCommand.ResetExecutor); - _firmataStream.WriteByte((byte)(force ? 1 : 0)); - _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); - _firmataStream.Flush(); - Thread.Sleep(100); - } - } - - public void SendKillTask(byte codeReference) - { - lock (_synchronisationLock) - { - _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); - _firmataStream.WriteByte((byte)FirmataSysexCommand.SCHEDULER_DATA); - _firmataStream.WriteByte((byte)0xFF); // IL data - _firmataStream.WriteByte((byte)ExecutorCommand.KillTask); - _firmataStream.WriteByte(codeReference); - _firmataStream.WriteByte((byte)FirmataCommand.END_SYSEX); - _firmataStream.Flush(); - Thread.Sleep(100); - } - } - private void SendValuesAsTwo7bitBytes(ReadOnlySpan values) { for (int i = 0; i < values.Length; i++) diff --git a/src/devices/Arduino/samples/Arduino.Monitor.csproj b/src/devices/Arduino/samples/Arduino.Monitor.csproj index c5e9e61596..f43f0e066e 100644 --- a/src/devices/Arduino/samples/Arduino.Monitor.csproj +++ b/src/devices/Arduino/samples/Arduino.Monitor.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/devices/Arduino/samples/Arduino.sample.cs b/src/devices/Arduino/samples/Arduino.sample.cs index d3db0aeed4..0ad669ead6 100644 --- a/src/devices/Arduino/samples/Arduino.sample.cs +++ b/src/devices/Arduino/samples/Arduino.sample.cs @@ -105,7 +105,6 @@ private static bool Menu(ArduinoBoard board) Console.WriteLine(" 9 Run SPI tests with an MCP3008 (experimental)"); Console.WriteLine(" 0 Detect all devices on the I2C bus"); Console.WriteLine(" H Read DHT11 Humidity sensor on GPIO 3 (experimental)"); - Console.WriteLine(" C C#/IL Code execution on Arduino (VERY experimental)"); Console.WriteLine(" X Exit"); var key = Console.ReadKey(); Console.WriteLine(); @@ -149,10 +148,6 @@ private static bool Menu(ArduinoBoard board) case 'x': case 'X': return false; - case 'c': - case 'C': - TestIlInterpreter(board); - break; } return true; @@ -287,21 +282,22 @@ public static void TestAnalogIn(ArduinoBoard board) var gpioController = board.CreateGpioController(PinNumberingScheme.Board); var analogController = board.CreateAnalogController(0); - analogController.OpenPin(analogPin); + var pin = analogController.OpenPin(analogPin); gpioController.OpenPin(gpio); gpioController.SetPinMode(gpio, PinMode.Output); Console.WriteLine("Blinking GPIO6, based on analog input."); while (!Console.KeyAvailable) { - double voltage = analogController.ReadVoltage(analogPin); + double voltage = pin.ReadVoltage(); gpioController.Write(gpio, PinValue.High); Thread.Sleep((int)voltage * 100); - voltage = analogController.ReadVoltage(analogPin); + voltage = pin.ReadVoltage(); gpioController.Write(gpio, PinValue.Low); Thread.Sleep((int)voltage * 100); } + pin.Dispose(); Console.ReadKey(); analogController.Dispose(); gpioController.Dispose(); @@ -312,10 +308,10 @@ public static void TestAnalogCallback(ArduinoBoard board) const int analogPin = 15; var analogController = board.CreateAnalogController(0); - analogController.OpenPin(analogPin); - analogController.EnableAnalogValueChangedEvent(analogPin, null, 0); + var pin = analogController.OpenPin(analogPin); + pin.EnableAnalogValueChangedEvent(null, 0); - analogController.ValueChanged += (sender, args) => + pin.ValueChanged += (sender, args) => { if (args.PinNumber == analogPin) { @@ -331,7 +327,8 @@ public static void TestAnalogCallback(ArduinoBoard board) } Console.ReadKey(); - analogController.DisableAnalogValueChangedEvent(analogPin); + pin.DisableAnalogValueChangedEvent(); + pin.Dispose(); analogController.Dispose(); } @@ -491,77 +488,5 @@ public static void TestDht(ArduinoBoard board) Console.ReadKey(); } - - public static void TestIlInterpreter(ArduinoBoard board) - { - ArduinoCsCompiler compiler = new ArduinoCsCompiler(board); - var method1 = compiler.LoadCode>(ArduinoCompilerSampleMethods.AddInts); - method1.InvokeAsync(2, 3); - int result; - method1.WaitForResult(); - method1.GetMethodResults(out object[] data, out MethodState state); - if (state != MethodState.Stopped) - { - Console.WriteLine("Method returned result but did not end?!?"); - } - - result = (int)data[0]; - Console.WriteLine($"2 + 3 = {result}"); - method1.InvokeAsync(255, 5); - method1.WaitForResult(); - method1.GetMethodResults(out data, out state); - result = (int)data[0]; - Console.WriteLine($"255 + 5 = {result}"); - - var method2 = compiler.LoadCode(new Func(ArduinoCompilerSampleMethods.Equal)); - method2.InvokeAsync(2, 3); - method2.WaitForResult(); - method2.GetMethodResults(out data, out state); - bool trueOrFalse = (bool)data[0]; - Console.WriteLine($"Is 2 == 3? {trueOrFalse}"); - method2.InvokeAsync(257, 257); - method2.WaitForResult(); - method2.GetMethodResults(out data, out state); - trueOrFalse = (bool)data[0]; - Console.WriteLine($"Is 257 == 257? {trueOrFalse}"); - - compiler.LoadLowLevelInterface(); - compiler.LoadCode(new Func(ArduinoCompilerSampleMethods.Smaller)); - var method3 = compiler.LoadCode(new Action(ArduinoCompilerSampleMethods.Blink)); - method3.InvokeAsync(0, 10, 500); - - // While the above method executes (and blinks the led), we query the analog input - var analogController = board.CreateAnalogController(0); - int analogPin = 15; - - analogController.OpenPin(analogPin); - - while (method3.State == MethodState.Running) - { - double value = analogController.ReadVoltage(analogPin); - Console.WriteLine($"Read analog value as {value:F2}"); - Thread.Sleep(100); - } - - analogController.ClosePin(analogPin); - method3.WaitForResult(); - - compiler.ClearAllData(true); - - // Start task again and terminate it immediately - compiler.LoadLowLevelInterface(); - compiler.LoadCode(new Func(ArduinoCompilerSampleMethods.Smaller)); - method3 = compiler.LoadCode(new Action(ArduinoCompilerSampleMethods.Blink)); - method3.InvokeAsync(0, 10, 500); - method3.Terminate(); - if (method3.State != MethodState.Killed) - { - Console.WriteLine("Unable to terminate task"); - } - - method3.Dispose(); - compiler.ClearAllData(true); - compiler.Dispose(); - } } } diff --git a/src/devices/Common/CommonHelpers.csproj b/src/devices/Common/CommonHelpers.csproj index 91b41da17d..0657bbad29 100644 --- a/src/devices/Common/CommonHelpers.csproj +++ b/src/devices/Common/CommonHelpers.csproj @@ -9,11 +9,12 @@ - - - - + + + + + - \ No newline at end of file + diff --git a/src/devices/Common/System/Device/Analog/AnalogController.cs b/src/devices/Common/System/Device/Analog/AnalogController.cs new file mode 100644 index 0000000000..c5852c1011 --- /dev/null +++ b/src/devices/Common/System/Device/Analog/AnalogController.cs @@ -0,0 +1,150 @@ +using System.Collections.Generic; +using System.Device.Gpio; +using System.Linq; + +namespace System.Device.Analog +{ + /// + /// Base class for Analog Controllers. + /// These control analog input pins. + /// + public abstract class AnalogController : IDisposable + { + private readonly List _openPins; + + /// + /// Initializes a new instance of the class that will use the specified numbering scheme and driver. + /// + /// The numbering scheme used to represent pins provided by the controller. + public AnalogController(PinNumberingScheme numberingScheme) + { + NumberingScheme = numberingScheme; + _openPins = new List(); + } + + /// + /// The numbering scheme used to represent pins provided by the controller. + /// + public PinNumberingScheme NumberingScheme { get; } + + /// + /// The number of pins provided by the controller. + /// + public abstract int PinCount + { + get; + } + + /// + /// Reference voltage (the maximum voltage measurable). + /// For some hardware, it might be necessary to manually set this value for the method to return correct values. + /// + public double VoltageReference + { + get; + set; + } + + /// + /// Returns true if the given pin supports analog input + /// + /// Number of the pin + /// True if the pin supports analog input + public abstract bool SupportsAnalogInput(int pin); + + /// + /// Convert the input pin number to logical pin numbers. + /// + /// Pin Number, in the numbering scheme of the analog controller + /// Logical pin number + public virtual int ConvertPinNumberToLogicalNumberingScheme(int pinNumber) + { + return pinNumber; + } + + /// + /// Convert logical pin to caller's pin numbering scheme. + /// + /// Logical pin numbering of the board + /// Pin number in the scheme of the parent analog controller + public virtual int ConvertLogicalNumberingSchemeToPinNumber(int logicalPinNumber) + { + return logicalPinNumber; + } + + /// + /// Opens a pin in order for it to be ready to use. + /// + /// The pin number in the controller's numbering scheme. + public AnalogInputPin OpenPin(int pinNumber) + { + // This does the number conversion itself, therefore done first + if (!SupportsAnalogInput(pinNumber)) + { + throw new NotSupportedException($"Pin {pinNumber} is not supporting analog input."); + } + + int logicalPinNumber = ConvertPinNumberToLogicalNumberingScheme(pinNumber); + + if (_openPins.Any(x => x.PinNumber == pinNumber)) + { + throw new InvalidOperationException("The selected pin is already open."); + } + + AnalogInputPin openPin = OpenPinInternal(logicalPinNumber); + _openPins.Add(openPin); + return openPin; + } + + /// + /// Overriden by derived classes: Returns an instance of the for the specified pin. + /// + /// Pin number, in the logical pin numbering scheme + /// An instance of a pin + protected abstract AnalogInputPin OpenPinInternal(int pinNumber); + + /// + /// Checks if a specific pin is open. + /// + /// The pin number in the controller's numbering scheme. + /// The status if the pin is open or closed. + public virtual bool IsPinOpen(int pinNumber) + { + int logicalPinNumber = ConvertPinNumberToLogicalNumberingScheme(pinNumber); + return _openPins.Any(x => x.PinNumber == logicalPinNumber); + } + + /// + /// Closes the given pin + /// + /// The pin to close + public virtual void Close(AnalogInputPin pin) + { + if (_openPins.Remove(pin)) + { + // This may fire back, therefore the test above + pin.Close(); + } + } + + /// + /// Disposes this instance + /// + protected virtual void Dispose(bool disposing) + { + foreach (var pin in _openPins) + { + pin.Dispose(); + } + + _openPins.Clear(); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/devices/Common/System/Device/Analog/AnalogInputPin.cs b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs new file mode 100644 index 0000000000..e5076b68df --- /dev/null +++ b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Device.Gpio; + +namespace System.Device.Analog +{ + /// + /// Driver vor analog input pins + /// + public abstract class AnalogInputPin : IDisposable + { + /// + /// Fires when a value is changed for which a trigger is registered + /// + public event ValueChangedEventHandler ValueChanged; + + /// + /// Construct an instance of an analog pin. + /// This is not usually called directly. Use instead. + /// + public AnalogInputPin(AnalogController controller, int pinNumber, double voltageReference) + { + Controller = controller; + VoltageReference = voltageReference; + PinNumber = pinNumber; + } + + /// + /// Reference to the controller for this pin + /// + protected AnalogController Controller + { + get; + } + + /// + /// The reference voltage level to convert raw values into voltages. + /// Some boards (i.e. the ADS111x series) always return an absolute voltage. Then this value is meaningless. + /// + public double VoltageReference + { + get; + } + + /// + /// The logical pin number of this instance. + /// + public int PinNumber + { + get; + } + + /// + /// Return the resolution of an analog input pin. + /// + /// Returns the resolution of the ADC in number of bits, including the sign bit (if applicable) + /// Minimum measurable voltage + /// Maximum measurable voltage + public abstract void QueryResolution(out int numberOfBits, out double minVoltage, out double maxVoltage); + + /// + /// Read a raw value from the pin + /// + /// Raw value of the analog input. Scale depends on hardware. + public abstract uint ReadRaw(); + + /// + /// Read a raw value and convert it to a voltage + /// + /// Reads a new value from the input and converts it to a voltage. For many boards, the needs to be correctly set for this to work. + public virtual double ReadVoltage() + { + uint raw = ReadRaw(); + return ConvertToVoltage(raw); + } + + /// + /// Converts an input raw value to a voltage + /// + protected virtual double ConvertToVoltage(uint rawValue) + { + QueryResolution(out int numberOfBits, out double minVoltage, out double maxVoltage); + if (minVoltage >= 0) + { + // The ADC can handle only positive values + int maxRawValue = (1 << numberOfBits) - 1; + double voltage = ((double)rawValue / maxRawValue) * maxVoltage; + return voltage; + } + else + { + // The ADC also handles negative values. This means that the number of bits includes the sign. + uint maxRawValue = (uint)((1 << (numberOfBits - 1)) - 1); + if (rawValue < maxRawValue) + { + double voltage = ((double)rawValue / maxRawValue) * maxVoltage; + return voltage; + } + else + { + // This is a bitmask which has all the bits 1 that are not used in the data. + // We use this to sign-extend our raw value. The mask also includes the sign bit itself, + // but we know that this is already 1 + uint topBits = ~maxRawValue; + rawValue |= topBits; + int raw2 = (int)rawValue; + double voltage = ((double)raw2 / maxRawValue) * maxVoltage; + return voltage; // This is now negative + } + } + } + + /// + /// True if this pin supports analog input + /// + /// + public abstract bool SupportsAnalogInput(); + + /// + /// Enable event callback when the value of this pin changes. + /// + /// If an external interrupt handler is required, it can be provided here. Can be null if another interrupt feature is available + /// Input pin on the master controller. + public abstract void EnableAnalogValueChangedEvent(GpioController masterController, int masterPin); + + /// + /// Disables the event callback + /// + public abstract void DisableAnalogValueChangedEvent(); + + /// + /// Fires a value changed event. + /// + /// New value + protected void FireValueChanged(ValueChangedEventArgs eventArgs) + { + ValueChanged?.Invoke(this, eventArgs); + } + + /// + /// Close the pin + /// + public virtual void Close() + { + Controller.Close(this); + } + + /// + /// Dispose this instance + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Close(); + } + } + + /// + /// Dispose this instance + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/devices/Common/System/Device/Analog/TriggerReason.cs b/src/devices/Common/System/Device/Analog/TriggerReason.cs new file mode 100644 index 0000000000..0f3046f6a0 --- /dev/null +++ b/src/devices/Common/System/Device/Analog/TriggerReason.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Device.Analog +{ + /// + /// Gives the reason why a new value was provided + /// + public enum TriggerReason + { + /// + /// The reason for the new message is unknown + /// + Unknown, + + /// + /// A new value is available + /// + NewMeasurement, + + /// + /// A new value is read with a specific frequency + /// + Timed, + + /// + /// A value is provided when certain thresholds are exceeded + /// + LimitExceeded + } +} diff --git a/src/devices/Common/System/Device/Analog/ValueChangedEventArgs.cs b/src/devices/Common/System/Device/Analog/ValueChangedEventArgs.cs new file mode 100644 index 0000000000..218d1df69e --- /dev/null +++ b/src/devices/Common/System/Device/Analog/ValueChangedEventArgs.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Device.Analog +{ + /// + /// Arguments passed in when an event is triggered by the GPIO. + /// + public class ValueChangedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The raw analog sensor reading + /// The analog sensor reading, converted to voltage. + /// The pin number that triggered the event. + /// The reason for the event + public ValueChangedEventArgs(uint rawValue, double newValue, int pinNumber, TriggerReason triggerReason) + { + RawValue = rawValue; + Value = newValue; + PinNumber = pinNumber; + TriggerReason = triggerReason; + } + + /// + /// Raw value, unscaled. + /// + public uint RawValue + { + get; + } + + /// + /// The change type that triggered the event. + /// + public double Value + { + get; + } + + /// + /// The pin number that triggered the event. + /// + public int PinNumber + { + get; + } + + /// + /// The reason that triggered this message. + /// + public TriggerReason TriggerReason { get; } + } +} diff --git a/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs b/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs new file mode 100644 index 0000000000..ccb9b176dd --- /dev/null +++ b/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Device.Analog +{ + /// + /// Delegate that defines the structure for callbacks when the value of a measurement (i.e. analog input) changes. + /// + /// The sender of the event. + /// The pin value changed arguments from the event. + public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs pinValueChangedEventArgs); +} From 6c6b038cc130f119bd9fa6cddb2d0f12c8ddeb98 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 31 Oct 2020 18:22:00 +0100 Subject: [PATCH 03/56] Sample depends on other binding Will re-add once the OpenHardwareMonitor PR is merged --- src/devices/Arduino/Arduino.sln | 15 - .../Arduino/samples/Arduino.Monitor.cs | 301 ------------------ .../Arduino/samples/Arduino.Monitor.csproj | 30 -- .../Arduino/samples/CharacterDisplay.cs | 56 ---- 4 files changed, 402 deletions(-) delete mode 100644 src/devices/Arduino/samples/Arduino.Monitor.cs delete mode 100644 src/devices/Arduino/samples/Arduino.Monitor.csproj delete mode 100644 src/devices/Arduino/samples/CharacterDisplay.cs diff --git a/src/devices/Arduino/Arduino.sln b/src/devices/Arduino/Arduino.sln index 7cf230c24d..9965057f92 100644 --- a/src/devices/Arduino/Arduino.sln +++ b/src/devices/Arduino/Arduino.sln @@ -21,8 +21,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CpuTemperature", "..\CpuTem EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9E5A25ED-9839-4C1A-9B27-993437D1CB31}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Monitor", "samples\Arduino.Monitor.csproj", "{23B4B60C-9594-42BB-9D25-C54983B0F809}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -129,25 +127,12 @@ Global {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Release|Any CPU.Build.0 = Release|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Release|Any CPU.Build.0 = Release|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.Build.0 = Release|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {2670F7BF-A7C8-49EB-9A99-1719A90D0C67} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31} - {23B4B60C-9594-42BB-9D25-C54983B0F809} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {47BF9684-1876-4AC1-8C87-04B8C7FA6C3A} diff --git a/src/devices/Arduino/samples/Arduino.Monitor.cs b/src/devices/Arduino/samples/Arduino.Monitor.cs deleted file mode 100644 index fc69fc315b..0000000000 --- a/src/devices/Arduino/samples/Arduino.Monitor.cs +++ /dev/null @@ -1,301 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Device.Gpio; -using System.Device.Gpio.Drivers; -using System.Device.I2c; -using System.Device.Spi; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.IO.Ports; -using System.Linq; -using System.Text; -using System.Threading; -using Iot.Device.Adc; -using Iot.Device.Arduino; -using Iot.Device.Arduino.Sample; -using Iot.Device.Bmxx80; -using Iot.Device.Bmxx80.PowerMode; -using Iot.Device.Common; -using Iot.Device.CpuTemperature; -using UnitsNet; - -namespace Arduino.Samples -{ - /// - /// Sample application for Ft4222 - /// - internal class Program - { - /// - /// Main entry point - /// - /// Unused - public static void Main(string[] args) - { - string portName = "COM4"; - if (args.Length > 1) - { - portName = args[1]; - } - - using (var port = new SerialPort(portName, 115200)) - { - Console.WriteLine($"Connecting to Arduino on {portName}"); - try - { - port.Open(); - } - catch (UnauthorizedAccessException x) - { - Console.WriteLine($"Could not open COM port: {x.Message} Possible reason: Arduino IDE connected or serial console open"); - return; - } - - ArduinoBoard board = new ArduinoBoard(port.BaseStream); - try - { - board.LogMessages += BoardOnLogMessages; - board.Initialize(); - Console.WriteLine($"Connection successful. Firmware version: {board.FirmwareVersion}, Builder: {board.FirmwareName}"); - DisplayModes(board); - } - catch (TimeoutException x) - { - Console.WriteLine($"No answer from board: {x.Message} "); - } - finally - { - port.Close(); - board?.Dispose(); - } - } - } - - private static void BoardOnLogMessages(string message, Exception exception) - { - Console.WriteLine("Log message: " + message); - if (exception != null) - { - Console.WriteLine(exception); - } - } - - public static void DisplayModes(ArduinoBoard board) - { - const int Gpio2 = 2; - const int MaxMode = 10; - const double StationAltitude = 650; - int mode = 0; - var gpioController = board.CreateGpioController(PinNumberingScheme.Board); - gpioController.OpenPin(Gpio2); - gpioController.SetPinMode(Gpio2, PinMode.Input); - CharacterDisplay disp = new CharacterDisplay(board); - Console.WriteLine("Display output test"); - Console.WriteLine("The button on GPIO 2 changes modes"); - Console.WriteLine("Press x to exit"); - disp.Output.ScrollUpDelay = TimeSpan.FromMilliseconds(500); - AutoResetEvent buttonClicked = new AutoResetEvent(false); - - void ChangeMode(object sender, PinValueChangedEventArgs pinValueChangedEventArgs) - { - mode++; - if (mode > MaxMode) - { - // Don't change back to 0 - mode = 1; - } - - buttonClicked.Set(); - } - - gpioController.RegisterCallbackForPinValueChangedEvent(Gpio2, PinEventTypes.Falling, ChangeMode); - var device = board.CreateI2cDevice(new I2cConnectionSettings(0, Bmp280.DefaultI2cAddress)); - Bmp280 bmp; - try - { - bmp = new Bmp280(device); - bmp.StandbyTime = StandbyTime.Ms250; - bmp.SetPowerMode(Bmx280PowerMode.Normal); - } - catch (IOException) - { - bmp = null; - Console.WriteLine("BMP280 not available"); - } - - OpenHardwareMonitor hardwareMonitor = new OpenHardwareMonitor(); - hardwareMonitor.EnableDerivedSensors(); - TimeSpan sleeptime = TimeSpan.FromMilliseconds(500); - string modeName = string.Empty; - string previousModeName = string.Empty; - int firstCharInText = 0; - while (true) - { - if (Console.KeyAvailable && Console.ReadKey(true).KeyChar == 'x') - { - break; - } - - // Default - sleeptime = TimeSpan.FromMilliseconds(500); - - switch (mode) - { - case 0: - modeName = "Display ready"; - disp.Output.ReplaceLine(1, "Button for mode"); - // Just text - break; - case 1: - { - modeName = "Time"; - disp.Output.ReplaceLine(1, DateTime.Now.ToLongTimeString()); - sleeptime = TimeSpan.FromMilliseconds(200); - break; - } - - case 2: - { - modeName = "Date"; - disp.Output.ReplaceLine(1, DateTime.Now.ToShortDateString()); - break; - } - - case 3: - modeName = "Temperature / Barometric Pressure"; - if (bmp != null && bmp.TryReadTemperature(out Temperature temp) && bmp.TryReadPressure(out Pressure p2)) - { - Pressure p3 = WeatherHelper.CalculateBarometricPressure(p2, temp, StationAltitude); - disp.Output.ReplaceLine(1, string.Format(CultureInfo.CurrentCulture, "{0:s1} {1:s1}", temp, p3)); - } - else - { - disp.Output.ReplaceLine(1, "N/A"); - } - - break; - case 4: - modeName = "Temperature / Humidity"; - if (board.TryReadDht(3, 11, out temp, out var humidity)) - { - disp.Output.ReplaceLine(1, string.Format(CultureInfo.CurrentCulture, "{0:s1} {1:s0}", temp, humidity)); - } - else - { - disp.Output.ReplaceLine(1, "N/A"); - } - - break; - - case 5: - modeName = "Dew point"; - if (bmp != null && bmp.TryReadPressure(out p2) && board.TryReadDht(3, 11, out temp, out humidity)) - { - Temperature dewPoint = WeatherHelper.CalculateDewPoint(temp, humidity.Percent); - disp.Output.ReplaceLine(1, dewPoint.ToString("s1", CultureInfo.CurrentCulture)); - } - else - { - disp.Output.ReplaceLine(1, "N/A"); - } - - break; - case 6: - modeName = "CPU Temperature"; - if (hardwareMonitor.TryGetAverageCpuTemperature(out temp)) - { - disp.Output.ReplaceLine(1, temp.ToString("s1", CultureInfo.CurrentCulture)); - } - else - { - disp.Output.ReplaceLine(1, "N/A"); - } - - break; - case 7: - modeName = "GPU Temperature"; - if (hardwareMonitor.TryGetAverageGpuTemperature(out temp)) - { - disp.Output.ReplaceLine(1, temp.ToString("s1", CultureInfo.CurrentCulture)); - } - else - { - disp.Output.ReplaceLine(1, "N/A"); - } - - break; - case 8: - modeName = "CPU Load"; - disp.Output.ReplaceLine(1, hardwareMonitor.GetCpuLoad().ToString("s1", CultureInfo.CurrentCulture)); - break; - - case 9: - modeName = "Total power dissipation"; - var powerSources = hardwareMonitor.GetSensorList().Where(x => x.SensorType == SensorType.Power); - Power totalPower = Power.Zero; - foreach (var power in powerSources) - { - if (power.Name != "CPU Cores" && power.TryGetValue(out Power powerConsumption)) // included in CPU Package - { - totalPower = totalPower + powerConsumption; - } - } - - disp.Output.ReplaceLine(1, totalPower.ToString("s1", CultureInfo.CurrentCulture)); - break; - - case 10: - modeName = "Energy consumed"; - var energySources = hardwareMonitor.GetSensorList().Where(x => x.SensorType == SensorType.Energy); - Energy totalEnergy = Energy.FromWattHours(0); // Set up the desired output unit - foreach (var e in energySources) - { - if (!e.Name.StartsWith("CPU Cores") && e.TryGetValue(out Energy powerConsumption)) // included in CPU Package - { - totalEnergy = totalEnergy + powerConsumption; - } - } - - disp.Output.ReplaceLine(1, totalEnergy.ToString("s1", CultureInfo.CurrentCulture)); - break; - } - - int displayWidth = disp.Output.Size.Width; - if (modeName.Length > displayWidth) - { - // Add one space at the end, makes it a bit easier to read - if (firstCharInText < modeName.Length - displayWidth + 1) - { - firstCharInText++; - } - else - { - firstCharInText = 0; - } - - disp.Output.ReplaceLine(0, modeName.Substring(firstCharInText)); - } - - if (modeName != previousModeName) - { - disp.Output.ReplaceLine(0, modeName); - - previousModeName = modeName; - firstCharInText = 0; - } - - buttonClicked.WaitOne(sleeptime); - } - - hardwareMonitor.Dispose(); - disp.Output.Clear(); - disp.Dispose(); - bmp?.Dispose(); - gpioController.Dispose(); - } - } -} diff --git a/src/devices/Arduino/samples/Arduino.Monitor.csproj b/src/devices/Arduino/samples/Arduino.Monitor.csproj deleted file mode 100644 index f43f0e066e..0000000000 --- a/src/devices/Arduino/samples/Arduino.Monitor.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - Exe - netcoreapp2.1 - Iot.Device.Arduino.Sample - false - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/devices/Arduino/samples/CharacterDisplay.cs b/src/devices/Arduino/samples/CharacterDisplay.cs deleted file mode 100644 index 17aa4b46c1..0000000000 --- a/src/devices/Arduino/samples/CharacterDisplay.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Device.Gpio; -using System.Globalization; -using System.Text; -using Iot.Device.CharacterLcd; - -namespace Iot.Device.Arduino.Sample -{ - internal sealed class CharacterDisplay : IDisposable - { - private GpioController _controller; - private Lcd1602 _display; - private LcdConsole _textController; - - public CharacterDisplay(ArduinoBoard board) - { - _controller = board.CreateGpioController(PinNumberingScheme.Logical); - _display = new Lcd1602(8, 9, new int[] { 4, 5, 6, 7 }, -1, 1.0f, -1, _controller); - _display.BlinkingCursorVisible = false; - _display.UnderlineCursorVisible = false; - _display.Clear(); - ////for (int i = 0; i < 16; i++) - ////{ - //// for (int j = 0; j < 16; j++) - //// { - //// Span b = new Span(new byte[] { (byte)(j + i * 16) }); - //// _display.Write(b); - //// } - - //// Console.ReadKey(); - //// _display.Clear(); - ////} - - _textController = new LcdConsole(_display, "SplC780", false); - _textController.Clear(); - LcdCharacterEncodingFactory f = new LcdCharacterEncodingFactory(); - var cultureEncoding = f.Create(CultureInfo.CurrentCulture, "SplC780", '?', _display.NumberOfCustomCharactersSupported); - _textController.LoadEncoding(cultureEncoding); - } - - public LcdConsole Output - { - get - { - return _textController; - } - } - - public void Dispose() - { - _display.Dispose(); - _controller.Dispose(); - } - } -} From 5a1c017f84c0425446629dfbc0b95f7182278fb1 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 31 Oct 2020 21:12:38 +0100 Subject: [PATCH 04/56] Support non-continuous analog pin numbers --- .../Arduino/ArduinoAnalogController.cs | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/devices/Arduino/ArduinoAnalogController.cs b/src/devices/Arduino/ArduinoAnalogController.cs index 582c070a90..c2a5dc0382 100644 --- a/src/devices/Arduino/ArduinoAnalogController.cs +++ b/src/devices/Arduino/ArduinoAnalogController.cs @@ -14,7 +14,6 @@ internal class ArduinoAnalogController : AnalogController private readonly ArduinoBoard _board; private readonly List _supportedPinConfigurations; private readonly Dictionary _callbacks; - private int _firstAnalogPin; public ArduinoAnalogController(ArduinoBoard board, List supportedPinConfigurations, PinNumberingScheme scheme) @@ -27,17 +26,6 @@ public ArduinoAnalogController(ArduinoBoard board, // Note: While the Arduino does have an external analog input reference pin, Firmata doesn't allow configuring it. VoltageReference = 5.0; - // Number of the first analog pin. Serves for converting between logical A0-based pin numbers and digital pin numbers. - // The value of this is 14 for most arduinos. - var firstPin = _supportedPinConfigurations.FirstOrDefault(x => x.PinModes.Contains(SupportedMode.ANALOG_INPUT)); - if (firstPin != null) - { - _firstAnalogPin = firstPin.Pin; - } - else - { - _firstAnalogPin = 0; - } } public override int PinCount @@ -47,12 +35,38 @@ public override int PinCount public override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber) { - return pinNumber - _firstAnalogPin; + int numberAnalogPinsFound = 0; + for (int i = 0; i < _supportedPinConfigurations.Count; i++) + { + if (_supportedPinConfigurations[i].PinModes.Contains(SupportedMode.ANALOG_INPUT)) + { + numberAnalogPinsFound++; + if (pinNumber == i) + { + return numberAnalogPinsFound - 1; + } + } + } + + throw new InvalidOperationException($"Pin {pinNumber} is not a valid analog input pin."); } public override int ConvertLogicalNumberingSchemeToPinNumber(int logicalPinNumber) { - return logicalPinNumber + _firstAnalogPin; + int numberAnalogPinsFound = 0; + for (int i = 0; i < _supportedPinConfigurations.Count; i++) + { + if (_supportedPinConfigurations[i].PinModes.Contains(SupportedMode.ANALOG_INPUT)) + { + numberAnalogPinsFound++; + if (logicalPinNumber == numberAnalogPinsFound - 1) + { + return i; + } + } + } + + throw new InvalidOperationException($"Pin A{logicalPinNumber} is not existing"); } public override bool SupportsAnalogInput(int pinNumber) @@ -72,7 +86,7 @@ protected override AnalogInputPin OpenPinInternal(int pinNumber) public override void Close(AnalogInputPin pin) { - _board.Firmata.DisableAnalogReporting(ConvertPinNumberToLogicalNumberingScheme(pin.PinNumber)); + _board.Firmata.DisableAnalogReporting(pin.PinNumber); } protected override void Dispose(bool disposing) From 7b5e5c77039bae6ef3dcb3fb3d2275a6c2d0dda7 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 31 Oct 2020 21:12:50 +0100 Subject: [PATCH 05/56] Remove obsolete solution configurations --- src/devices/Arduino/Arduino.sln | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/devices/Arduino/Arduino.sln b/src/devices/Arduino/Arduino.sln index 9965057f92..4551d29daf 100644 --- a/src/devices/Arduino/Arduino.sln +++ b/src/devices/Arduino/Arduino.sln @@ -24,11 +24,7 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Linux-Debug|Any CPU = Linux-Debug|Any CPU - Linux-Release|Any CPU = Linux-Release|Any CPU Release|Any CPU = Release|Any CPU - Windows-Debug|Any CPU = Windows-Debug|Any CPU - Windows-Release|Any CPU = Windows-Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU From 3500082f75d619241c3c1531623ac831d82f1522 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 31 Oct 2020 21:13:10 +0100 Subject: [PATCH 06/56] Provide overload that takes a serial port name --- src/devices/Arduino/Arduino.csproj | 1 + src/devices/Arduino/ArduinoBoard.cs | 41 ++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/devices/Arduino/Arduino.csproj b/src/devices/Arduino/Arduino.csproj index a955b01949..45deef0af2 100644 --- a/src/devices/Arduino/Arduino.csproj +++ b/src/devices/Arduino/Arduino.csproj @@ -13,6 +13,7 @@ + diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index 4dc1bc5898..644b0ed195 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -8,6 +8,7 @@ using System.Device.Spi; using System.Diagnostics; using System.IO; +using System.IO.Ports; using System.Threading; using System.Linq; using UnitsNet; @@ -24,6 +25,7 @@ namespace Iot.Device.Arduino /// public class ArduinoBoard : IDisposable { + private SerialPort _serialPort; private Stream _serialPortStream; private FirmataDevice _firmata; private Version _firmwareVersion; @@ -34,12 +36,38 @@ public class ArduinoBoard : IDisposable // Counts how many spi devices are attached, to make sure we enable/disable the bus only when no devices are attached private int _spiEnabled; + /// + /// Creates an instance of an Ardino board connection using the given stream (typically from a serial port) + /// + /// A stream to an Arduino/Firmata device public ArduinoBoard(Stream serialPortStream) { _serialPortStream = serialPortStream; _spiEnabled = 0; } + /// + /// Creates an instance of the Arduino board connection connected to a serial port + /// + /// Port name. On Windows, this is usually "COM3" or "COM4" for an Arduino attached via USB. + /// 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) + { + _serialPort = new SerialPort(portName, baudRate); + _serialPort.Open(); + _serialPortStream = _serialPort.BaseStream; + } + + public PinNumberingScheme DefaultPinNumberingScheme + { + get + { + // We have only one scheme + return PinNumberingScheme.Logical; + } + } + public event Action LogMessages; public virtual void Initialize() @@ -115,6 +143,11 @@ private void FirmataOnError(string message, Exception innerException) LogMessages?.Invoke(message, innerException); } + public GpioController CreateGpioController() + { + return CreateGpioController(DefaultPinNumberingScheme); + } + public GpioController CreateGpioController(PinNumberingScheme pinNumberingScheme) { return new GpioController(pinNumberingScheme, new ArduinoGpioControllerDriver(this, _supportedPinConfigurations)); @@ -192,9 +225,15 @@ protected virtual void Dispose(bool disposing) { _serialPortStream.Close(); _serialPortStream.Dispose(); + _serialPortStream = null; + } + + if (_serialPort != null) + { + _serialPort.Dispose(); + _serialPort = null; } - _serialPortStream = null; if (_firmata != null) { _firmata.OnError -= FirmataOnError; From 7f517fc8c7ffb07d30a16144f3e36da32fe37027 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 31 Oct 2020 21:18:00 +0100 Subject: [PATCH 07/56] Remove dead code --- src/devices/Arduino/ArduinoBoard.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index 644b0ed195..61c7ce223d 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -97,7 +97,6 @@ public virtual void Initialize() Log(pin.ToString()); } - // _firmata.SetSamplingInterval(TimeSpan.FromMilliseconds(100)); _firmata.EnableDigitalReporting(); } From a67114dff279f26b30c9ae76d32fe95efe2724e6 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 31 Oct 2020 21:40:07 +0100 Subject: [PATCH 08/56] Add documentation --- .../Arduino/ArduinoAnalogController.cs | 1 - src/devices/Arduino/ArduinoBoard.cs | 57 +++++++++++++++++-- .../Arduino/ArduinoGpioControllerDriver.cs | 1 - src/devices/Arduino/ArduinoI2cDevice.cs | 1 - src/devices/Arduino/FirmataDevice.cs | 4 +- src/devices/Arduino/FirmataSysexCommand.cs | 6 +- src/devices/Arduino/SupportedMode.cs | 2 +- .../Arduino/SupportedPinConfiguration.cs | 2 - .../Arduino/samples/Arduino.sample.csproj | 1 + .../Arduino/{ => samples}/DebugLogStream.cs | 2 +- 10 files changed, 61 insertions(+), 16 deletions(-) rename src/devices/Arduino/{ => samples}/DebugLogStream.cs (98%) diff --git a/src/devices/Arduino/ArduinoAnalogController.cs b/src/devices/Arduino/ArduinoAnalogController.cs index c2a5dc0382..c32b92d68a 100644 --- a/src/devices/Arduino/ArduinoAnalogController.cs +++ b/src/devices/Arduino/ArduinoAnalogController.cs @@ -6,7 +6,6 @@ using System.Text; using System.Threading; -#pragma warning disable CS1591 namespace Iot.Device.Arduino { internal class ArduinoAnalogController : AnalogController diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index 61c7ce223d..f546c0ee1b 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -13,8 +13,6 @@ using System.Linq; using UnitsNet; -#pragma warning disable CS1591 - namespace Iot.Device.Arduino { /// @@ -59,6 +57,9 @@ public ArduinoBoard(string portName, int baudRate) _serialPortStream = _serialPort.BaseStream; } + /// + /// Default pin numbering scheme of this board. Always , because the Arduino has only one pin numbering scheme. + /// public PinNumberingScheme DefaultPinNumberingScheme { get @@ -68,8 +69,15 @@ public PinNumberingScheme DefaultPinNumberingScheme } } + /// + /// Attach to this event to retrieve log messages + /// public event Action LogMessages; + /// + /// 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. public virtual void Initialize() { _firmata = new FirmataDevice(); @@ -78,7 +86,7 @@ public virtual void Initialize() _protocolVersion = _firmata.QueryFirmataVersion(); if (_protocolVersion < _firmata.QuerySupportedFirmataVersion()) { - throw new NotSupportedException($"Firmata version on board is {_protocolVersion}. Expected {_firmata.QuerySupportedFirmataVersion()}. They must be equal."); + throw new NotSupportedException($"Firmata version on board is {_protocolVersion}. Expected at least {_firmata.QuerySupportedFirmataVersion()}."); } Log($"Firmata version on board is {_protocolVersion}."); @@ -100,6 +108,9 @@ public virtual void Initialize() _firmata.EnableDigitalReporting(); } + /// + /// Firmware version on the device + /// public Version FirmwareVersion { get @@ -108,6 +119,9 @@ public Version FirmwareVersion } } + /// + /// Name of the firmware. + /// public string FirmwareName { get @@ -142,16 +156,32 @@ private void FirmataOnError(string message, Exception innerException) LogMessages?.Invoke(message, innerException); } + /// + /// 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() { return CreateGpioController(DefaultPinNumberingScheme); } + /// + /// Creates a GPIO Controller instance for the board. This allows working with digital input/output pins. + /// + /// Pin numbering scheme to use for this controller + /// An instance of GpioController, using an Arduino-Enabled driver public GpioController CreateGpioController(PinNumberingScheme pinNumberingScheme) { return new GpioController(pinNumberingScheme, 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 I2cDevice CreateI2cDevice(I2cConnectionSettings connectionSettings) { if (!SupportedPinConfigurations.Any(x => x.PinModes.Contains(SupportedMode.I2C))) @@ -167,7 +197,7 @@ public I2cDevice CreateI2cDevice(I2cConnectionSettings connectionSettings) /// This therefore returns a Software SPI device for the default Arduino SPI port on pins 11, 12 and 13. /// /// Spi Connection settings - /// + /// An instance. public SpiDevice CreateSpiDevice(SpiConnectionSettings settings) { if (settings.BusId != 0) @@ -183,6 +213,14 @@ public SpiDevice CreateSpiDevice(SpiConnectionSettings settings) return new ArduinoSpiDevice(this, settings); } + /// + /// Creates a PWM channel. + /// + /// Must be 0. + /// Pin number to use + /// This value is ignored + /// The duty cycle as a fraction. + /// public PwmChannel CreatePwmChannel( int chip, int channel, @@ -192,6 +230,11 @@ public PwmChannel CreatePwmChannel( return new ArduinoPwmChannel(this, chip, channel, frequency, dutyCyclePercentage); } + /// + /// Creates an anlog controller for this board. + /// + /// Must be 0 + /// An instance public AnalogController CreateAnalogController(int chip) { return new ArduinoAnalogController(this, SupportedPinConfigurations, PinNumberingScheme.Logical); @@ -217,6 +260,9 @@ public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, return Firmata.TryReadDht(pinNumber, dhtType, out temperature, out humidity); } + /// + /// Standard dispose pattern + /// protected virtual void Dispose(bool disposing) { // Do this first, to force any blocking read operations to end @@ -243,6 +289,9 @@ protected virtual void Dispose(bool disposing) _firmata = null; } + /// + /// Dispose this instance + /// public void Dispose() { Dispose(true); diff --git a/src/devices/Arduino/ArduinoGpioControllerDriver.cs b/src/devices/Arduino/ArduinoGpioControllerDriver.cs index 54b2e46518..023c083230 100644 --- a/src/devices/Arduino/ArduinoGpioControllerDriver.cs +++ b/src/devices/Arduino/ArduinoGpioControllerDriver.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; -#pragma warning disable CS1591 namespace Iot.Device.Arduino { internal class ArduinoGpioControllerDriver : GpioDriver diff --git a/src/devices/Arduino/ArduinoI2cDevice.cs b/src/devices/Arduino/ArduinoI2cDevice.cs index dca776c6c0..cc6a1f958e 100644 --- a/src/devices/Arduino/ArduinoI2cDevice.cs +++ b/src/devices/Arduino/ArduinoI2cDevice.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text; -#pragma warning disable CS1591 namespace Iot.Device.Arduino { /// diff --git a/src/devices/Arduino/FirmataDevice.cs b/src/devices/Arduino/FirmataDevice.cs index a161da8cfb..325af4a0dd 100644 --- a/src/devices/Arduino/FirmataDevice.cs +++ b/src/devices/Arduino/FirmataDevice.cs @@ -12,9 +12,11 @@ using System.Threading.Tasks; using UnitsNet; -#pragma warning disable CS1591 namespace Iot.Device.Arduino { + /// + /// This delegate is used to fire digital value changed events. + /// public delegate void DigitalPinValueChanged(int pin, PinValue newValue); internal delegate void AnalogPinValueUpdated(int pin, uint rawValue); diff --git a/src/devices/Arduino/FirmataSysexCommand.cs b/src/devices/Arduino/FirmataSysexCommand.cs index 87a3622413..0b3957de11 100644 --- a/src/devices/Arduino/FirmataSysexCommand.cs +++ b/src/devices/Arduino/FirmataSysexCommand.cs @@ -1,11 +1,9 @@ -#pragma warning disable CS1591 - -namespace Iot.Device.Arduino +namespace Iot.Device.Arduino { /// /// Extended firmata commands /// - public enum FirmataSysexCommand + internal enum FirmataSysexCommand { ENCODER_DATA = 0x61, SPI_DATA = 0x68, diff --git a/src/devices/Arduino/SupportedMode.cs b/src/devices/Arduino/SupportedMode.cs index 28e451b11a..5b9261fc0d 100644 --- a/src/devices/Arduino/SupportedMode.cs +++ b/src/devices/Arduino/SupportedMode.cs @@ -4,7 +4,7 @@ /// Mode bits for the Firmata protocol. /// These are used both for capability reporting as well as to set a mode /// - public enum SupportedMode + internal enum SupportedMode { /// /// The pin supports digital input diff --git a/src/devices/Arduino/SupportedPinConfiguration.cs b/src/devices/Arduino/SupportedPinConfiguration.cs index c5bc38f28b..ed8b04c5a3 100644 --- a/src/devices/Arduino/SupportedPinConfiguration.cs +++ b/src/devices/Arduino/SupportedPinConfiguration.cs @@ -4,8 +4,6 @@ using System.Linq; using System.Text; -#pragma warning disable CS1591 - namespace Iot.Device.Arduino { internal class SupportedPinConfiguration diff --git a/src/devices/Arduino/samples/Arduino.sample.csproj b/src/devices/Arduino/samples/Arduino.sample.csproj index 03ab403ea0..230f89760f 100644 --- a/src/devices/Arduino/samples/Arduino.sample.csproj +++ b/src/devices/Arduino/samples/Arduino.sample.csproj @@ -13,6 +13,7 @@ + diff --git a/src/devices/Arduino/DebugLogStream.cs b/src/devices/Arduino/samples/DebugLogStream.cs similarity index 98% rename from src/devices/Arduino/DebugLogStream.cs rename to src/devices/Arduino/samples/DebugLogStream.cs index 01a7668203..f63fc702b0 100644 --- a/src/devices/Arduino/DebugLogStream.cs +++ b/src/devices/Arduino/samples/DebugLogStream.cs @@ -5,7 +5,7 @@ using System.Text; #pragma warning disable CS1591 -namespace Iot.Device.Arduino +namespace Arduino.Samples { public class DebugLogStream : Stream { From bdd284fa89891bb720df0600f2877048abbcfc55 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sun, 1 Nov 2020 11:55:35 +0100 Subject: [PATCH 09/56] Update DHT protocol format. Now closer to the proposed standard at https://github.com/firmata/protocol/pull/106/commits/9389c89d3d7998dc2bd703f8c796e6a7c03f2551 --- .../ConfigurableFirmata.ino | 6 ++--- src/devices/Arduino/FirmataDevice.cs | 23 ++++++------------- src/devices/Arduino/FirmataSysexCommand.cs | 2 +- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino b/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino index 429e74b6f1..2b592ee63f 100644 --- a/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino +++ b/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino @@ -22,11 +22,11 @@ // I2C support (most boards have just one channel) #define ENABLE_I2C // SPI support. This feature requires a non-standard module compiled into the firmware -// #define ENABLE_SPI +#define ENABLE_SPI // Native reading of DHTXX sensors. Reading a DHT11 directly using GPIO methods from a remote PC will not work, because of the very tight timing requirements of these sensors -// This feature requires a non-standard module compiled into the firmware -// #define ENABLE_DHT +// This feature requires a non-standard module compiled into the firmware (see Readme.md). In addition, the "DHT Sensor library" from Adafruit is required. +#define ENABLE_DHT #include diff --git a/src/devices/Arduino/FirmataDevice.cs b/src/devices/Arduino/FirmataDevice.cs index 325af4a0dd..5d63e3737a 100644 --- a/src/devices/Arduino/FirmataDevice.cs +++ b/src/devices/Arduino/FirmataDevice.cs @@ -1028,31 +1028,22 @@ public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, throw new TimeoutException("Timeout waiting for device reply"); } - // Command, type, pin number and 4x2 bytes data - if (_lastResponse.Count < 11) + // Command, pin number and 2x2 bytes data (+ END_SYSEX byte) + if (_lastResponse.Count < 7) { return false; } - if (_lastResponse[2] != pinNumber) + if (_lastResponse[0] != (byte)FirmataSysexCommand.DHT_SENSOR_DATA_REQUEST && _lastResponse[1] != 0) { return false; } - Span reply = stackalloc byte[4]; - ReassembleByteString(_lastResponse, 3, 8, reply); + int t = _lastResponse[3] | _lastResponse[4] << 7; + int h = _lastResponse[5] | _lastResponse[6] << 7; - var arr = reply.ToArray(); - - short h = BitConverter.ToInt16(arr, 0); - float t = BitConverter.ToInt16(arr, 2) / 10.0f; - if (double.IsNaN(t) || double.IsNaN(h)) - { - return false; - } - - temperature = Temperature.FromDegreesCelsius(t); - humidity = Ratio.FromPercent(h); + temperature = Temperature.FromDegreesCelsius(t / 10.0); + humidity = Ratio.FromPercent(h / 10.0); } return true; diff --git a/src/devices/Arduino/FirmataSysexCommand.cs b/src/devices/Arduino/FirmataSysexCommand.cs index 0b3957de11..e32073be14 100644 --- a/src/devices/Arduino/FirmataSysexCommand.cs +++ b/src/devices/Arduino/FirmataSysexCommand.cs @@ -27,6 +27,6 @@ internal enum FirmataSysexCommand SCHEDULER_DATA = 0x7B, SYSEX_NON_REALTIME = 0x7E, SYSEX_REALTIME = 0x7F, - DHT_SENSOR_DATA_REQUEST = 0x02, // User defined block + DHT_SENSOR_DATA_REQUEST = 0x74, // User defined block } } From 45976f67fb3c69af415e0d3f73390afabf44bff4 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sun, 1 Nov 2020 12:29:49 +0100 Subject: [PATCH 10/56] These should not be enabled by default --- .../Arduino/ConfigurableFirmata/ConfigurableFirmata.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino b/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino index 2b592ee63f..a8e5325bfa 100644 --- a/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino +++ b/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino @@ -22,11 +22,11 @@ // I2C support (most boards have just one channel) #define ENABLE_I2C // SPI support. This feature requires a non-standard module compiled into the firmware -#define ENABLE_SPI +// #define ENABLE_SPI // Native reading of DHTXX sensors. Reading a DHT11 directly using GPIO methods from a remote PC will not work, because of the very tight timing requirements of these sensors // This feature requires a non-standard module compiled into the firmware (see Readme.md). In addition, the "DHT Sensor library" from Adafruit is required. -#define ENABLE_DHT +// #define ENABLE_DHT #include From d45a6d46e82bd8122b7c244bf0564ed9124db31d Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sun, 1 Nov 2020 12:29:03 +0100 Subject: [PATCH 11/56] Extend Readme --- src/devices/Arduino/README.md | 71 ++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/src/devices/Arduino/README.md b/src/devices/Arduino/README.md index c69a484046..3714dd0e09 100644 --- a/src/devices/Arduino/README.md +++ b/src/devices/Arduino/README.md @@ -1,44 +1,87 @@ # SPI, GPIO and I2C drivers for Arduino -This binding supports GPIO and I2C access from a normal Desktop environment (Windows, Linux) trough an Arduino board. +This binding supports GPIO, PWM, SPI and I2C access from a normal Desktop environment (Windows, Linux) trough an Arduino board. ## Device family -This binding remotely controls different Arduino boards directly from PC Software. It provides support for accessing GPIO ports as well as I2C devices. The Arduino is remote controlled by individual commands from the PC, the entire program will run on the PC, and not on the Arduino, so the connection cannot be removed while the device is being used. +This binding remotely controls different Arduino boards directly from PC Software. It provides support for accessing GPIO ports as well as I2C devices, SPI devices, PWM output and analog input. The Arduino is remote controlled by individual commands from the PC, the entire program will run on the PC, and not on the Arduino, so the connection cannot be removed while the device is being used. ## Desktop Requirements -In order to have an Arduino board working with the PC, you need to Install the Arduino IDE together with the drivers for your board type. If you get a simple sketch uploaded and running (such as the blinking LED example) you are fine to start. +In order to have an Arduino board working with the PC, you need to Install the Arduino IDE together with the drivers for your board type. If you get a simple sketch uploaded and running (such as the blinking LED example) you are fine to start. If you're new to the Arduino world, read the introduction at https://www.arduino.cc/en/Guide for a quick start. The explanations below assume you have the Arduino board connected trough an USB cable with your PC and you know how to upload a sketch. ## Preparing your Arduino -You need to upload a special sketch to the Arduino. This sketch implements the "Firmata-Protocol", a communication protocol that allows to remotely control all the inputs and outputs of the board. See https://github.com/firmata/protocol/blob/master/protocol.md for details. +### Quick start +You need to upload a special sketch to the Arduino. This sketch implements the "Firmata-Protocol", a communication protocol that allows to remotely control all the inputs and outputs of the board. See https://github.com/firmata/protocol/blob/master/protocol.md for details. We call this sketch (or variants of it, see below) the Firmata firmware. The binding requires Firmata Version 2.6, which is implemented i.e. by the ConfigurableFirmata project. - Open the Arduino IDE - Go to the library manager and check that you have the "ConfigurableFirmata" library installed -- Open "CommonFirmataFeatures.ino" from the device binding folder or go to http://firmatabuilder.com/ to create your own custom firmata firmware. Make sure you have at least the features checked that you will need. +- Open "ConfigurableFirmata.ino" from the device binding folder or go to http://firmatabuilder.com/ to create your own custom firmata firmware. Make sure you have at least the features checked that you will need. - Upload this sketch to your Arduino. After these steps, you can start coding with Iot.Devices.Arduino and make your Arduino do whatever you want, from blinking LEDS to your personal weather station. For usage and examples see the samples folder. Note that ConfigurableFirmata uses a default UART speed of 57600 baud. It is recommended to increase it to 115200, though. +### Advanced features +Some of the features require extended features on the Arduino firmware. These include SPI support and DHT sensor support. These features didn't make it +into the main branch yet, therefore these additional steps are required: +- Go to C:\users\\documents\arduino\libraries and delete the "ConfigurableFirmata" folder (save any work if you've changed anything there) +- Replace it with a clone of https://github.com/pgrawehr/ConfigurableFirmata and switch to branch "develop". +- Make sure you have the "DHT Sensor Library" from Adafruit installed (use the library manager for that). +- You can now enable the DHT and SPI features at the beginning of the ConfigurableFirmata.ino file. +- Compile and re-upload the sketch. ## Usage - -### I2C - -### SPI - -### GPIO +See the example for advanced usage instructions. + +Basic start: +``` + // Portname is "COM3", "COM4" on Windows, "/dev/ttyUSB0" or similar on linux + using (var port = new SerialPort(portName, 115200)) + { + Console.WriteLine($"Connecting to Arduino on {portName}"); + try + { + port.Open(); + } + catch (UnauthorizedAccessException x) + { + Console.WriteLine($"Could not open COM port: {x.Message} Possible reason: Arduino IDE connected or serial console open"); + return; + } + + ArduinoBoard board = new ArduinoBoard(port.BaseStream); + try + { + board.LogMessages += BoardOnLogMessages; // Get log messages + board.Initialize(); + Console.WriteLine($"Connection successful. Firmware version: {board.FirmwareVersion}, Builder: {board.FirmwareName}"); + // Add code that uses the board here. + } + catch (TimeoutException x) + { + Console.WriteLine($"No answer from board: {x.Message} "); + } + finally + { + port.Close(); + board?.Dispose(); + } + } + ``` + +On Windows, only one application can use the serial port at a time, therefore you'll get "permission denied" errors when you try to run your program while i.e. the Serial Port Monitor of the Arduino IDE is open when you start your program. On the other hand, trying to upload a new sketch while the program runs will also fail. ## Known limitations -This SPI and I2C implementation are over USB which can contains some delays and not be as fast as a native implementation. It has been developed mainly for development purpose and being able to run and debug easily SPI and I2C device code from a Windows 64 bits machine. It is not recommended to use this type of chipset for production purpose. +All communication is routed trough the USB cable immitating a serial port with a limited bandwith. Therefore, some not insignificant delays are to be expected when sending commands or retrieving data. Communicating with sensors which have time-critical behavior will most likely not work reliably for this reason and the standard bindings provided for these won't work. This includes sensors like the DHT11, DHT22 or HCSR-04. For some of these, special Firmata modules are available to execute the time-critical part directly on the Arduino. This problem does not exist for sensors using I2C or SPI protocols. -For the moment this project supports only SPI and I2C in a Windows environment. Here is the list of tested features: +For the moment this binding supports GPIO, Analog In, SPI, I2C and DHT on all platforms. Here is the list of tested features: -- [ ] SPI master support for Windows 64/32 +- [x] SPI master support for Windows 64/32 - [x] I2C master support for Windows 64/32 - [x] Basic GPIO support for Windows 64/32 - [x] Advanced GPIO support for Windows 64/32 +- [x] Analog input support on Windows 64/32 - [ ] SPI support for MacOS - [ ] I2C support for MacOS - [ ] GPIO support for MacOS From 80e5775792e9817968acd956cb28ea969998cce9 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sun, 1 Nov 2020 13:30:17 +0100 Subject: [PATCH 12/56] Add missing copyright headers --- src/devices/Arduino/ArduinoAnalogController.cs | 6 +++++- src/devices/Arduino/ArduinoAnalogInputPin.cs | 6 +++++- src/devices/Arduino/ArduinoBoard.cs | 6 +++++- src/devices/Arduino/ArduinoGpioControllerDriver.cs | 6 +++++- src/devices/Arduino/ArduinoI2cDevice.cs | 6 +++++- src/devices/Arduino/ArduinoPwmChannel.cs | 6 +++++- src/devices/Arduino/ArduinoSpiDevice.cs | 6 +++++- src/devices/Arduino/FirmataCommand.cs | 6 +++++- src/devices/Arduino/FirmataDevice.cs | 6 +++++- src/devices/Arduino/FirmataSpiCommand.cs | 6 +++++- src/devices/Arduino/FirmataSysexCommand.cs | 6 +++++- src/devices/Arduino/SupportedMode.cs | 6 +++++- src/devices/Arduino/SupportedPinConfiguration.cs | 6 +++++- src/devices/Arduino/samples/DebugLogStream.cs | 6 +++++- src/devices/Common/System/Device/Analog/AnalogController.cs | 6 +++++- src/devices/Common/System/Device/Analog/AnalogInputPin.cs | 6 +++++- src/devices/Common/System/Device/Analog/TriggerReason.cs | 6 +++++- .../Common/System/Device/Analog/ValueChangedEventHandler.cs | 6 +++++- 18 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/devices/Arduino/ArduinoAnalogController.cs b/src/devices/Arduino/ArduinoAnalogController.cs index c32b92d68a..effae3d133 100644 --- a/src/devices/Arduino/ArduinoAnalogController.cs +++ b/src/devices/Arduino/ArduinoAnalogController.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Device.Analog; using System.Device.Gpio; diff --git a/src/devices/Arduino/ArduinoAnalogInputPin.cs b/src/devices/Arduino/ArduinoAnalogInputPin.cs index 0c0f062e92..84958c3336 100644 --- a/src/devices/Arduino/ArduinoAnalogInputPin.cs +++ b/src/devices/Arduino/ArduinoAnalogInputPin.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Device.Analog; using System.Device.Gpio; diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index f546c0ee1b..f310e04bad 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Device.Analog; using System.Text; diff --git a/src/devices/Arduino/ArduinoGpioControllerDriver.cs b/src/devices/Arduino/ArduinoGpioControllerDriver.cs index 023c083230..947afa53ac 100644 --- a/src/devices/Arduino/ArduinoGpioControllerDriver.cs +++ b/src/devices/Arduino/ArduinoGpioControllerDriver.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Device.Gpio; diff --git a/src/devices/Arduino/ArduinoI2cDevice.cs b/src/devices/Arduino/ArduinoI2cDevice.cs index cc6a1f958e..db41e8fda5 100644 --- a/src/devices/Arduino/ArduinoI2cDevice.cs +++ b/src/devices/Arduino/ArduinoI2cDevice.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Device.I2c; using System.IO; diff --git a/src/devices/Arduino/ArduinoPwmChannel.cs b/src/devices/Arduino/ArduinoPwmChannel.cs index 38d454b979..1dcf7b29d5 100644 --- a/src/devices/Arduino/ArduinoPwmChannel.cs +++ b/src/devices/Arduino/ArduinoPwmChannel.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Device.Pwm; using System.Linq; diff --git a/src/devices/Arduino/ArduinoSpiDevice.cs b/src/devices/Arduino/ArduinoSpiDevice.cs index b59b98548d..cec33728b5 100644 --- a/src/devices/Arduino/ArduinoSpiDevice.cs +++ b/src/devices/Arduino/ArduinoSpiDevice.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Device.Spi; using System.Text; diff --git a/src/devices/Arduino/FirmataCommand.cs b/src/devices/Arduino/FirmataCommand.cs index 29380e4460..5ea3f8af5e 100644 --- a/src/devices/Arduino/FirmataCommand.cs +++ b/src/devices/Arduino/FirmataCommand.cs @@ -1,4 +1,8 @@ -namespace Iot.Device.Arduino +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Iot.Device.Arduino { /// /// Primary firmata commands diff --git a/src/devices/Arduino/FirmataDevice.cs b/src/devices/Arduino/FirmataDevice.cs index 5d63e3737a..577bf0d505 100644 --- a/src/devices/Arduino/FirmataDevice.cs +++ b/src/devices/Arduino/FirmataDevice.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections; using System.Collections.Generic; using System.Device.Gpio; diff --git a/src/devices/Arduino/FirmataSpiCommand.cs b/src/devices/Arduino/FirmataSpiCommand.cs index 96a50a80f8..b57a90ab1e 100644 --- a/src/devices/Arduino/FirmataSpiCommand.cs +++ b/src/devices/Arduino/FirmataSpiCommand.cs @@ -1,4 +1,8 @@ -namespace Iot.Device.Arduino +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Iot.Device.Arduino { internal enum FirmataSpiCommand { diff --git a/src/devices/Arduino/FirmataSysexCommand.cs b/src/devices/Arduino/FirmataSysexCommand.cs index e32073be14..ef16c7c4f3 100644 --- a/src/devices/Arduino/FirmataSysexCommand.cs +++ b/src/devices/Arduino/FirmataSysexCommand.cs @@ -1,4 +1,8 @@ -namespace Iot.Device.Arduino +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Iot.Device.Arduino { /// /// Extended firmata commands diff --git a/src/devices/Arduino/SupportedMode.cs b/src/devices/Arduino/SupportedMode.cs index 5b9261fc0d..dd6e89c623 100644 --- a/src/devices/Arduino/SupportedMode.cs +++ b/src/devices/Arduino/SupportedMode.cs @@ -1,4 +1,8 @@ -namespace Iot.Device.Arduino +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Iot.Device.Arduino { /// /// Mode bits for the Firmata protocol. diff --git a/src/devices/Arduino/SupportedPinConfiguration.cs b/src/devices/Arduino/SupportedPinConfiguration.cs index ed8b04c5a3..e1d25a122a 100644 --- a/src/devices/Arduino/SupportedPinConfiguration.cs +++ b/src/devices/Arduino/SupportedPinConfiguration.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Device.Gpio; using System.Linq; diff --git a/src/devices/Arduino/samples/DebugLogStream.cs b/src/devices/Arduino/samples/DebugLogStream.cs index f63fc702b0..5cfb6ae0ad 100644 --- a/src/devices/Arduino/samples/DebugLogStream.cs +++ b/src/devices/Arduino/samples/DebugLogStream.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Globalization; using System.IO; diff --git a/src/devices/Common/System/Device/Analog/AnalogController.cs b/src/devices/Common/System/Device/Analog/AnalogController.cs index c5852c1011..138fff83b4 100644 --- a/src/devices/Common/System/Device/Analog/AnalogController.cs +++ b/src/devices/Common/System/Device/Analog/AnalogController.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Device.Gpio; using System.Linq; diff --git a/src/devices/Common/System/Device/Analog/AnalogInputPin.cs b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs index e5076b68df..729036b92c 100644 --- a/src/devices/Common/System/Device/Analog/AnalogInputPin.cs +++ b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Text; using System.Device.Gpio; diff --git a/src/devices/Common/System/Device/Analog/TriggerReason.cs b/src/devices/Common/System/Device/Analog/TriggerReason.cs index 0f3046f6a0..1abff1088b 100644 --- a/src/devices/Common/System/Device/Analog/TriggerReason.cs +++ b/src/devices/Common/System/Device/Analog/TriggerReason.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Text; diff --git a/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs b/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs index ccb9b176dd..d4e8848e07 100644 --- a/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs +++ b/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.Text; From 7aecd85160a88935866ad6dba167772a7fb5f4de Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Fri, 18 Dec 2020 17:32:59 +0100 Subject: [PATCH 13/56] Revert "Sample depends on other binding" This reverts commit 0f17a6c5092ce2222ce09978d80d6356c155b23d. --- src/devices/Arduino/Arduino.sln | 15 + .../Arduino/samples/Arduino.Monitor.cs | 301 ++++++++++++++++++ .../Arduino/samples/Arduino.Monitor.csproj | 30 ++ .../Arduino/samples/CharacterDisplay.cs | 56 ++++ 4 files changed, 402 insertions(+) create mode 100644 src/devices/Arduino/samples/Arduino.Monitor.cs create mode 100644 src/devices/Arduino/samples/Arduino.Monitor.csproj create mode 100644 src/devices/Arduino/samples/CharacterDisplay.cs diff --git a/src/devices/Arduino/Arduino.sln b/src/devices/Arduino/Arduino.sln index 4551d29daf..2050c6fbef 100644 --- a/src/devices/Arduino/Arduino.sln +++ b/src/devices/Arduino/Arduino.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CpuTemperature", "..\CpuTem EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9E5A25ED-9839-4C1A-9B27-993437D1CB31}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Monitor", "samples\Arduino.Monitor.csproj", "{23B4B60C-9594-42BB-9D25-C54983B0F809}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -123,12 +125,25 @@ Global {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Release|Any CPU.Build.0 = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.Build.0 = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU + {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {2670F7BF-A7C8-49EB-9A99-1719A90D0C67} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31} + {23B4B60C-9594-42BB-9D25-C54983B0F809} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {47BF9684-1876-4AC1-8C87-04B8C7FA6C3A} diff --git a/src/devices/Arduino/samples/Arduino.Monitor.cs b/src/devices/Arduino/samples/Arduino.Monitor.cs new file mode 100644 index 0000000000..fc69fc315b --- /dev/null +++ b/src/devices/Arduino/samples/Arduino.Monitor.cs @@ -0,0 +1,301 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Device.Gpio; +using System.Device.Gpio.Drivers; +using System.Device.I2c; +using System.Device.Spi; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading; +using Iot.Device.Adc; +using Iot.Device.Arduino; +using Iot.Device.Arduino.Sample; +using Iot.Device.Bmxx80; +using Iot.Device.Bmxx80.PowerMode; +using Iot.Device.Common; +using Iot.Device.CpuTemperature; +using UnitsNet; + +namespace Arduino.Samples +{ + /// + /// Sample application for Ft4222 + /// + internal class Program + { + /// + /// Main entry point + /// + /// Unused + public static void Main(string[] args) + { + string portName = "COM4"; + if (args.Length > 1) + { + portName = args[1]; + } + + using (var port = new SerialPort(portName, 115200)) + { + Console.WriteLine($"Connecting to Arduino on {portName}"); + try + { + port.Open(); + } + catch (UnauthorizedAccessException x) + { + Console.WriteLine($"Could not open COM port: {x.Message} Possible reason: Arduino IDE connected or serial console open"); + return; + } + + ArduinoBoard board = new ArduinoBoard(port.BaseStream); + try + { + board.LogMessages += BoardOnLogMessages; + board.Initialize(); + Console.WriteLine($"Connection successful. Firmware version: {board.FirmwareVersion}, Builder: {board.FirmwareName}"); + DisplayModes(board); + } + catch (TimeoutException x) + { + Console.WriteLine($"No answer from board: {x.Message} "); + } + finally + { + port.Close(); + board?.Dispose(); + } + } + } + + private static void BoardOnLogMessages(string message, Exception exception) + { + Console.WriteLine("Log message: " + message); + if (exception != null) + { + Console.WriteLine(exception); + } + } + + public static void DisplayModes(ArduinoBoard board) + { + const int Gpio2 = 2; + const int MaxMode = 10; + const double StationAltitude = 650; + int mode = 0; + var gpioController = board.CreateGpioController(PinNumberingScheme.Board); + gpioController.OpenPin(Gpio2); + gpioController.SetPinMode(Gpio2, PinMode.Input); + CharacterDisplay disp = new CharacterDisplay(board); + Console.WriteLine("Display output test"); + Console.WriteLine("The button on GPIO 2 changes modes"); + Console.WriteLine("Press x to exit"); + disp.Output.ScrollUpDelay = TimeSpan.FromMilliseconds(500); + AutoResetEvent buttonClicked = new AutoResetEvent(false); + + void ChangeMode(object sender, PinValueChangedEventArgs pinValueChangedEventArgs) + { + mode++; + if (mode > MaxMode) + { + // Don't change back to 0 + mode = 1; + } + + buttonClicked.Set(); + } + + gpioController.RegisterCallbackForPinValueChangedEvent(Gpio2, PinEventTypes.Falling, ChangeMode); + var device = board.CreateI2cDevice(new I2cConnectionSettings(0, Bmp280.DefaultI2cAddress)); + Bmp280 bmp; + try + { + bmp = new Bmp280(device); + bmp.StandbyTime = StandbyTime.Ms250; + bmp.SetPowerMode(Bmx280PowerMode.Normal); + } + catch (IOException) + { + bmp = null; + Console.WriteLine("BMP280 not available"); + } + + OpenHardwareMonitor hardwareMonitor = new OpenHardwareMonitor(); + hardwareMonitor.EnableDerivedSensors(); + TimeSpan sleeptime = TimeSpan.FromMilliseconds(500); + string modeName = string.Empty; + string previousModeName = string.Empty; + int firstCharInText = 0; + while (true) + { + if (Console.KeyAvailable && Console.ReadKey(true).KeyChar == 'x') + { + break; + } + + // Default + sleeptime = TimeSpan.FromMilliseconds(500); + + switch (mode) + { + case 0: + modeName = "Display ready"; + disp.Output.ReplaceLine(1, "Button for mode"); + // Just text + break; + case 1: + { + modeName = "Time"; + disp.Output.ReplaceLine(1, DateTime.Now.ToLongTimeString()); + sleeptime = TimeSpan.FromMilliseconds(200); + break; + } + + case 2: + { + modeName = "Date"; + disp.Output.ReplaceLine(1, DateTime.Now.ToShortDateString()); + break; + } + + case 3: + modeName = "Temperature / Barometric Pressure"; + if (bmp != null && bmp.TryReadTemperature(out Temperature temp) && bmp.TryReadPressure(out Pressure p2)) + { + Pressure p3 = WeatherHelper.CalculateBarometricPressure(p2, temp, StationAltitude); + disp.Output.ReplaceLine(1, string.Format(CultureInfo.CurrentCulture, "{0:s1} {1:s1}", temp, p3)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + case 4: + modeName = "Temperature / Humidity"; + if (board.TryReadDht(3, 11, out temp, out var humidity)) + { + disp.Output.ReplaceLine(1, string.Format(CultureInfo.CurrentCulture, "{0:s1} {1:s0}", temp, humidity)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + + case 5: + modeName = "Dew point"; + if (bmp != null && bmp.TryReadPressure(out p2) && board.TryReadDht(3, 11, out temp, out humidity)) + { + Temperature dewPoint = WeatherHelper.CalculateDewPoint(temp, humidity.Percent); + disp.Output.ReplaceLine(1, dewPoint.ToString("s1", CultureInfo.CurrentCulture)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + case 6: + modeName = "CPU Temperature"; + if (hardwareMonitor.TryGetAverageCpuTemperature(out temp)) + { + disp.Output.ReplaceLine(1, temp.ToString("s1", CultureInfo.CurrentCulture)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + case 7: + modeName = "GPU Temperature"; + if (hardwareMonitor.TryGetAverageGpuTemperature(out temp)) + { + disp.Output.ReplaceLine(1, temp.ToString("s1", CultureInfo.CurrentCulture)); + } + else + { + disp.Output.ReplaceLine(1, "N/A"); + } + + break; + case 8: + modeName = "CPU Load"; + disp.Output.ReplaceLine(1, hardwareMonitor.GetCpuLoad().ToString("s1", CultureInfo.CurrentCulture)); + break; + + case 9: + modeName = "Total power dissipation"; + var powerSources = hardwareMonitor.GetSensorList().Where(x => x.SensorType == SensorType.Power); + Power totalPower = Power.Zero; + foreach (var power in powerSources) + { + if (power.Name != "CPU Cores" && power.TryGetValue(out Power powerConsumption)) // included in CPU Package + { + totalPower = totalPower + powerConsumption; + } + } + + disp.Output.ReplaceLine(1, totalPower.ToString("s1", CultureInfo.CurrentCulture)); + break; + + case 10: + modeName = "Energy consumed"; + var energySources = hardwareMonitor.GetSensorList().Where(x => x.SensorType == SensorType.Energy); + Energy totalEnergy = Energy.FromWattHours(0); // Set up the desired output unit + foreach (var e in energySources) + { + if (!e.Name.StartsWith("CPU Cores") && e.TryGetValue(out Energy powerConsumption)) // included in CPU Package + { + totalEnergy = totalEnergy + powerConsumption; + } + } + + disp.Output.ReplaceLine(1, totalEnergy.ToString("s1", CultureInfo.CurrentCulture)); + break; + } + + int displayWidth = disp.Output.Size.Width; + if (modeName.Length > displayWidth) + { + // Add one space at the end, makes it a bit easier to read + if (firstCharInText < modeName.Length - displayWidth + 1) + { + firstCharInText++; + } + else + { + firstCharInText = 0; + } + + disp.Output.ReplaceLine(0, modeName.Substring(firstCharInText)); + } + + if (modeName != previousModeName) + { + disp.Output.ReplaceLine(0, modeName); + + previousModeName = modeName; + firstCharInText = 0; + } + + buttonClicked.WaitOne(sleeptime); + } + + hardwareMonitor.Dispose(); + disp.Output.Clear(); + disp.Dispose(); + bmp?.Dispose(); + gpioController.Dispose(); + } + } +} diff --git a/src/devices/Arduino/samples/Arduino.Monitor.csproj b/src/devices/Arduino/samples/Arduino.Monitor.csproj new file mode 100644 index 0000000000..f43f0e066e --- /dev/null +++ b/src/devices/Arduino/samples/Arduino.Monitor.csproj @@ -0,0 +1,30 @@ + + + + Exe + netcoreapp2.1 + Iot.Device.Arduino.Sample + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/devices/Arduino/samples/CharacterDisplay.cs b/src/devices/Arduino/samples/CharacterDisplay.cs new file mode 100644 index 0000000000..17aa4b46c1 --- /dev/null +++ b/src/devices/Arduino/samples/CharacterDisplay.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Device.Gpio; +using System.Globalization; +using System.Text; +using Iot.Device.CharacterLcd; + +namespace Iot.Device.Arduino.Sample +{ + internal sealed class CharacterDisplay : IDisposable + { + private GpioController _controller; + private Lcd1602 _display; + private LcdConsole _textController; + + public CharacterDisplay(ArduinoBoard board) + { + _controller = board.CreateGpioController(PinNumberingScheme.Logical); + _display = new Lcd1602(8, 9, new int[] { 4, 5, 6, 7 }, -1, 1.0f, -1, _controller); + _display.BlinkingCursorVisible = false; + _display.UnderlineCursorVisible = false; + _display.Clear(); + ////for (int i = 0; i < 16; i++) + ////{ + //// for (int j = 0; j < 16; j++) + //// { + //// Span b = new Span(new byte[] { (byte)(j + i * 16) }); + //// _display.Write(b); + //// } + + //// Console.ReadKey(); + //// _display.Clear(); + ////} + + _textController = new LcdConsole(_display, "SplC780", false); + _textController.Clear(); + LcdCharacterEncodingFactory f = new LcdCharacterEncodingFactory(); + var cultureEncoding = f.Create(CultureInfo.CurrentCulture, "SplC780", '?', _display.NumberOfCustomCharactersSupported); + _textController.LoadEncoding(cultureEncoding); + } + + public LcdConsole Output + { + get + { + return _textController; + } + } + + public void Dispose() + { + _display.Dispose(); + _controller.Dispose(); + } + } +} From 885010eb977249aa20ef101b191939fceac19fc5 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Fri, 18 Dec 2020 18:03:50 +0100 Subject: [PATCH 14/56] Merge changes from dev branch --- src/devices/Arduino/Arduino.csproj | 2 +- src/devices/Arduino/Arduino.sln | 87 ++-------- src/devices/Arduino/ArduinoAnalogInputPin.cs | 2 +- src/devices/Arduino/ArduinoBoard.cs | 138 ++++++++++++--- .../Arduino/ArduinoGpioControllerDriver.cs | 4 +- src/devices/Arduino/ArduinoSpiDevice.cs | 2 +- src/devices/Arduino/FirmataDevice.cs | 160 +++++++++++++++--- .../Arduino/samples/Arduino.Monitor.cs | 10 +- .../Arduino/samples/Arduino.Monitor.csproj | 1 + src/devices/Arduino/samples/Arduino.sample.cs | 21 ++- .../Arduino/samples/Arduino.sample.csproj | 3 +- .../Arduino/tests/Arduino.Tests.csproj | 28 +++ .../Arduino/tests/BasicFirmataTests.cs | 127 ++++++++++++++ src/devices/Arduino/tests/Encoder7BitTest.cs | 39 +++++ .../Arduino/tests/FirmataTestFixture.cs | 71 ++++++++ src/devices/Arduino/tests/xunit.runner.json | 4 + .../System/Device/Analog/AnalogInputPin.cs | 4 +- 17 files changed, 569 insertions(+), 134 deletions(-) create mode 100644 src/devices/Arduino/tests/Arduino.Tests.csproj create mode 100644 src/devices/Arduino/tests/BasicFirmataTests.cs create mode 100644 src/devices/Arduino/tests/Encoder7BitTest.cs create mode 100644 src/devices/Arduino/tests/FirmataTestFixture.cs create mode 100644 src/devices/Arduino/tests/xunit.runner.json diff --git a/src/devices/Arduino/Arduino.csproj b/src/devices/Arduino/Arduino.csproj index 45deef0af2..e04d6e7a73 100644 --- a/src/devices/Arduino/Arduino.csproj +++ b/src/devices/Arduino/Arduino.csproj @@ -14,6 +14,6 @@ - + diff --git a/src/devices/Arduino/Arduino.sln b/src/devices/Arduino/Arduino.sln index 2050c6fbef..343afa496d 100644 --- a/src/devices/Arduino/Arduino.sln +++ b/src/devices/Arduino/Arduino.sln @@ -23,6 +23,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9E5A EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Monitor", "samples\Arduino.Monitor.csproj", "{23B4B60C-9594-42BB-9D25-C54983B0F809}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{CA26B999-4C0E-4E82-A46E-A68AC1B85C10}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Tests", "tests\Arduino.Tests.csproj", "{273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HardwareMonitor", "..\HardwareMonitor\HardwareMonitor.csproj", "{D1C467D8-E6E3-4811-826B-216DCB80A16E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,112 +37,48 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU - {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU - {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Linux-Release|Any CPU.Build.0 = Release|Any CPU {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Release|Any CPU.Build.0 = Release|Any CPU - {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU - {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU - {0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Windows-Release|Any CPU.Build.0 = Release|Any CPU {0B90F9D4-7353-4172-A317-714471A06781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B90F9D4-7353-4172-A317-714471A06781}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B90F9D4-7353-4172-A317-714471A06781}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B90F9D4-7353-4172-A317-714471A06781}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU - {0B90F9D4-7353-4172-A317-714471A06781}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU - {0B90F9D4-7353-4172-A317-714471A06781}.Linux-Release|Any CPU.Build.0 = Release|Any CPU {0B90F9D4-7353-4172-A317-714471A06781}.Release|Any CPU.ActiveCfg = Release|Any CPU {0B90F9D4-7353-4172-A317-714471A06781}.Release|Any CPU.Build.0 = Release|Any CPU - {0B90F9D4-7353-4172-A317-714471A06781}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B90F9D4-7353-4172-A317-714471A06781}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU - {0B90F9D4-7353-4172-A317-714471A06781}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU - {0B90F9D4-7353-4172-A317-714471A06781}.Windows-Release|Any CPU.Build.0 = Release|Any CPU {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU - {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU - {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Linux-Release|Any CPU.Build.0 = Release|Any CPU {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Release|Any CPU.ActiveCfg = Release|Any CPU {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Release|Any CPU.Build.0 = Release|Any CPU - {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU - {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU - {2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Windows-Release|Any CPU.Build.0 = Release|Any CPU {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU - {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU - {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Linux-Release|Any CPU.Build.0 = Release|Any CPU {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Release|Any CPU.ActiveCfg = Release|Any CPU {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Release|Any CPU.Build.0 = Release|Any CPU - {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU - {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU - {EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Windows-Release|Any CPU.Build.0 = Release|Any CPU {26911D2A-E303-4846-96A1-5ABC92F54445}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26911D2A-E303-4846-96A1-5ABC92F54445}.Debug|Any CPU.Build.0 = Debug|Any CPU - {26911D2A-E303-4846-96A1-5ABC92F54445}.Linux-Debug|Any CPU.ActiveCfg = Linux-Debug|Any CPU - {26911D2A-E303-4846-96A1-5ABC92F54445}.Linux-Debug|Any CPU.Build.0 = Linux-Debug|Any CPU - {26911D2A-E303-4846-96A1-5ABC92F54445}.Linux-Release|Any CPU.ActiveCfg = Linux-Release|Any CPU - {26911D2A-E303-4846-96A1-5ABC92F54445}.Linux-Release|Any CPU.Build.0 = Linux-Release|Any CPU {26911D2A-E303-4846-96A1-5ABC92F54445}.Release|Any CPU.ActiveCfg = Release|Any CPU {26911D2A-E303-4846-96A1-5ABC92F54445}.Release|Any CPU.Build.0 = Release|Any CPU - {26911D2A-E303-4846-96A1-5ABC92F54445}.Windows-Debug|Any CPU.ActiveCfg = Windows-Debug|Any CPU - {26911D2A-E303-4846-96A1-5ABC92F54445}.Windows-Debug|Any CPU.Build.0 = Windows-Debug|Any CPU - {26911D2A-E303-4846-96A1-5ABC92F54445}.Windows-Release|Any CPU.ActiveCfg = Windows-Release|Any CPU - {26911D2A-E303-4846-96A1-5ABC92F54445}.Windows-Release|Any CPU.Build.0 = Windows-Release|Any CPU {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU - {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU - {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Linux-Release|Any CPU.Build.0 = Release|Any CPU {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Release|Any CPU.Build.0 = Release|Any CPU - {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU - {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU - {D47A6627-4041-4CEA-8903-A6C67C6993CF}.Windows-Release|Any CPU.Build.0 = Release|Any CPU {CD593083-8E94-4B60-86BF-DF3FB7873893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD593083-8E94-4B60-86BF-DF3FB7873893}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD593083-8E94-4B60-86BF-DF3FB7873893}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD593083-8E94-4B60-86BF-DF3FB7873893}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU - {CD593083-8E94-4B60-86BF-DF3FB7873893}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU - {CD593083-8E94-4B60-86BF-DF3FB7873893}.Linux-Release|Any CPU.Build.0 = Release|Any CPU {CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.Build.0 = Release|Any CPU - {CD593083-8E94-4B60-86BF-DF3FB7873893}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD593083-8E94-4B60-86BF-DF3FB7873893}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU - {CD593083-8E94-4B60-86BF-DF3FB7873893}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU - {CD593083-8E94-4B60-86BF-DF3FB7873893}.Windows-Release|Any CPU.Build.0 = Release|Any CPU {F3950513-A564-462F-887B-E00972D20FAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F3950513-A564-462F-887B-E00972D20FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Linux-Release|Any CPU.Build.0 = Release|Any CPU {F3950513-A564-462F-887B-E00972D20FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3950513-A564-462F-887B-E00972D20FAD}.Release|Any CPU.Build.0 = Release|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Windows-Release|Any CPU.Build.0 = Release|Any CPU {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Debug|Any CPU.Build.0 = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Release|Any CPU.ActiveCfg = Release|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Linux-Release|Any CPU.Build.0 = Release|Any CPU {23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.ActiveCfg = Release|Any CPU {23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.Build.0 = Release|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Debug|Any CPU.Build.0 = Debug|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Release|Any CPU.ActiveCfg = Release|Any CPU - {23B4B60C-9594-42BB-9D25-C54983B0F809}.Windows-Release|Any CPU.Build.0 = Release|Any CPU + {273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}.Release|Any CPU.Build.0 = Release|Any CPU + {D1C467D8-E6E3-4811-826B-216DCB80A16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -144,6 +86,7 @@ Global GlobalSection(NestedProjects) = preSolution {2670F7BF-A7C8-49EB-9A99-1719A90D0C67} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31} {23B4B60C-9594-42BB-9D25-C54983B0F809} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31} + {273C9233-8A7D-4A0B-8DB2-FB7F56FA727D} = {CA26B999-4C0E-4E82-A46E-A68AC1B85C10} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {47BF9684-1876-4AC1-8C87-04B8C7FA6C3A} diff --git a/src/devices/Arduino/ArduinoAnalogInputPin.cs b/src/devices/Arduino/ArduinoAnalogInputPin.cs index 84958c3336..dce027dca1 100644 --- a/src/devices/Arduino/ArduinoAnalogInputPin.cs +++ b/src/devices/Arduino/ArduinoAnalogInputPin.cs @@ -24,7 +24,7 @@ public ArduinoAnalogInputPin(ArduinoBoard board, AnalogController controller, Su _configuration = configuration; } - public override void EnableAnalogValueChangedEvent(GpioController masterController, int masterPin) + public override void EnableAnalogValueChangedEvent(GpioController? masterController, int masterPin) { // The pin is already open, so analog reporting is enabled, we just need to forward it. if (_autoReportingReferenceCount == 0) diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index f310e04bad..957a0b7038 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -27,11 +27,11 @@ namespace Iot.Device.Arduino /// public class ArduinoBoard : IDisposable { - private SerialPort _serialPort; - private Stream _serialPortStream; - private FirmataDevice _firmata; - private Version _firmwareVersion; - private Version _protocolVersion; + private SerialPort? _serialPort; + private Stream _dataStream; + private FirmataDevice? _firmata; + private Version? _firmwareVersion; + private Version? _protocolVersion; private string _firmwareName; private List _supportedPinConfigurations; @@ -44,8 +44,10 @@ public class ArduinoBoard : IDisposable /// A stream to an Arduino/Firmata device public ArduinoBoard(Stream serialPortStream) { - _serialPortStream = serialPortStream; + _dataStream = serialPortStream; _spiEnabled = 0; + _supportedPinConfigurations = new List(); + _firmwareName = string.Empty; } /// @@ -58,7 +60,9 @@ public ArduinoBoard(string portName, int baudRate) { _serialPort = new SerialPort(portName, baudRate); _serialPort.Open(); - _serialPortStream = _serialPort.BaseStream; + _dataStream = _serialPort.BaseStream; + _supportedPinConfigurations = new List(); + _firmwareName = string.Empty; } /// @@ -76,7 +80,75 @@ public PinNumberingScheme DefaultPinNumberingScheme /// /// Attach to this event to retrieve log messages /// - public event Action LogMessages; + public event Action? LogMessages; + + /// + /// Searches the given list of com ports for a firmata device. + /// + /// List of com ports. See . + /// List of baud rates to test. . + /// A board, already open and initialized. Null if none was found. + public static ArduinoBoard? FindBoard(List comPorts, List baudRates) + { + foreach (var port in comPorts) + { + foreach (var baud in baudRates) + { + ArduinoBoard? b = null; + try + { + b = new ArduinoBoard(port, baud); + b.Initialize(); + return b; + } + catch (Exception x) when (x is NotSupportedException || x is TimeoutException || x is IOException || x is UnauthorizedAccessException) + { + b?.Dispose(); + } + } + } + + return null; + } + + /// + /// Searches all available com ports for an Arduino device + /// + /// A board, already open and initialized. Null if none was found. + public static ArduinoBoard? FindBoard() + { + return FindBoard(GetSerialPortNames(), CommonBaudRates()); + } + + /// + /// Returns the list of available serial ports + /// + /// A list of available serial ports + /// There was an error retrieving the list + public static List GetSerialPortNames() + { + return SerialPort.GetPortNames().ToList(); + } + + /// + /// Returns a list of commonly used baud rates. + /// + public static List CommonBaudRates() + { + return new List() + { + 9600, + 19200, + 18400, + 57600, + 115200, + 230400, + 250000, + 500000, + 1000000, + 2000000, + }; + } /// /// Initialize the board connection. This must be called before any other methods of this class. @@ -84,13 +156,18 @@ public PinNumberingScheme DefaultPinNumberingScheme /// The Firmata firmware on the connected board is too old. public virtual void Initialize() { + if (_firmata != null) + { + throw new InvalidOperationException("Board already initialized"); + } + _firmata = new FirmataDevice(); - _firmata.Open(_serialPortStream); + _firmata.Open(_dataStream); _firmata.OnError += FirmataOnError; _protocolVersion = _firmata.QueryFirmataVersion(); if (_protocolVersion < _firmata.QuerySupportedFirmataVersion()) { - throw new NotSupportedException($"Firmata version on board is {_protocolVersion}. Expected at least {_firmata.QuerySupportedFirmataVersion()}."); + throw new NotSupportedException($"Firmata version on board is {_protocolVersion}. Expected {_firmata.QuerySupportedFirmataVersion()}. They must be equal."); } Log($"Firmata version on board is {_protocolVersion}."); @@ -119,7 +196,7 @@ public Version FirmwareVersion { get { - return _firmwareVersion; + return _firmwareVersion ?? new Version(); } } @@ -130,7 +207,23 @@ public string FirmwareName { get { - return _firmwareName; + return _firmwareName ?? string.Empty; + } + } + + /// + /// Firmata version found on the board. + /// + public Version FirmataVersion + { + get + { + if (_protocolVersion == null) + { + return new Version(); + } + + return _protocolVersion; } } @@ -138,11 +231,14 @@ internal FirmataDevice Firmata { get { - return _firmata; + return _firmata ?? throw new ObjectDisposedException(nameof(ArduinoBoard)); } } - internal List SupportedPinConfigurations + /// + /// Returns the list of capabilities per pin + /// + public List SupportedPinConfigurations { get { @@ -155,7 +251,7 @@ internal void Log(string message) LogMessages?.Invoke(message, null); } - private void FirmataOnError(string message, Exception innerException) + private void FirmataOnError(string message, Exception? innerException) { LogMessages?.Invoke(message, innerException); } @@ -270,11 +366,11 @@ public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, protected virtual void Dispose(bool disposing) { // Do this first, to force any blocking read operations to end - if (_serialPortStream != null) + if (_dataStream != null) { - _serialPortStream.Close(); - _serialPortStream.Dispose(); - _serialPortStream = null; + _dataStream.Close(); + _dataStream.Dispose(); + _dataStream = null!; } if (_serialPort != null) @@ -307,7 +403,7 @@ internal void EnableSpi() _spiEnabled++; if (_spiEnabled == 1) { - _firmata.EnableSpi(); + _firmata?.EnableSpi(); } } @@ -316,7 +412,7 @@ internal void DisableSpi() _spiEnabled--; if (_spiEnabled == 0) { - _firmata.DisableSpi(); + _firmata?.DisableSpi(); } } } diff --git a/src/devices/Arduino/ArduinoGpioControllerDriver.cs b/src/devices/Arduino/ArduinoGpioControllerDriver.cs index 947afa53ac..8a03e4b73d 100644 --- a/src/devices/Arduino/ArduinoGpioControllerDriver.cs +++ b/src/devices/Arduino/ArduinoGpioControllerDriver.cs @@ -231,7 +231,7 @@ protected override void RemoveCallbackForPinValueChangedEvent(int pinNumber, Pin private void FirmataOnDigitalPortValueUpdated(int pin, PinValue newvalue) { - CallbackContainer cb = null; + CallbackContainer? cb = null; PinEventTypes eventTypeToFire = PinEventTypes.None; lock (_callbackContainersLock) { @@ -277,7 +277,7 @@ public CallbackContainer(int pinNumber, PinEventTypes eventTypes) EventTypes = eventTypes; } - public event PinChangeEventHandler OnPinChanged; + public event PinChangeEventHandler? OnPinChanged; public int PinNumber { get; } diff --git a/src/devices/Arduino/ArduinoSpiDevice.cs b/src/devices/Arduino/ArduinoSpiDevice.cs index cec33728b5..343c588889 100644 --- a/src/devices/Arduino/ArduinoSpiDevice.cs +++ b/src/devices/Arduino/ArduinoSpiDevice.cs @@ -67,7 +67,7 @@ protected override void Dispose(bool disposing) { Board.DisableSpi(); // To make sure this is called only once (and any further attempts to use this instance fail) - Board = null; + Board = null!; } } diff --git a/src/devices/Arduino/FirmataDevice.cs b/src/devices/Arduino/FirmataDevice.cs index 577bf0d505..28242ca3b7 100644 --- a/src/devices/Arduino/FirmataDevice.cs +++ b/src/devices/Arduino/FirmataDevice.cs @@ -29,8 +29,9 @@ internal sealed class FirmataDevice : IDisposable { private const byte FIRMATA_PROTOCOL_MAJOR_VERSION = 2; private const byte FIRMATA_PROTOCOL_MINOR_VERSION = 5; // 2.5 works, but 2.6 is recommended - private const int FIRMATA_INIT_TIMEOUT_SECONDS = 4; + private const int FIRMATA_INIT_TIMEOUT_SECONDS = 2; private static readonly TimeSpan DefaultReplyTimeout = TimeSpan.FromMilliseconds(500); + private static readonly TimeSpan ProgrammingTimeout = TimeSpan.FromMinutes(2); private byte _firmwareVersionMajor; private byte _firmwareVersionMinor; @@ -40,8 +41,8 @@ internal sealed class FirmataDevice : IDisposable private int _lastRequestId; private string _firmwareName; - private Stream _firmataStream; - private Thread _inputThread; + private Stream? _firmataStream; + private Thread? _inputThread; private bool _inputThreadShouldExit; private List _supportedPinConfigurations; private IList _lastResponse; @@ -52,16 +53,14 @@ internal sealed class FirmataDevice : IDisposable private object _synchronisationLock; private Queue _dataQueue; - private byte _lastIlExecutionError; - // Event used when waiting for answers (i.e. after requesting firmware version) private AutoResetEvent _dataReceived; - public event DigitalPinValueChanged DigitalPortValueUpdated; + public event DigitalPinValueChanged? DigitalPortValueUpdated; - public event AnalogPinValueUpdated AnalogPinValueUpdated; + public event AnalogPinValueUpdated? AnalogPinValueUpdated; - public event Action OnError; + public event Action? OnError; public FirmataDevice() { @@ -77,8 +76,10 @@ public FirmataDevice() _lastAnalogValues = new Dictionary(); _lastAnalogValueLock = new object(); _dataQueue = new Queue(); + _lastResponse = new List(); _lastRequestId = 1; _lastIlExecutionError = 0; + _firmwareName = string.Empty; } internal List PinConfigurations @@ -91,6 +92,13 @@ internal List PinConfigurations public void Open(Stream stream) { + lock (_synchronisationLock) + { + if (_firmataStream != null) + { + throw new InvalidOperationException("The device is already open"); + } + _firmataStream = stream; if (_firmataStream.CanRead && _firmataStream.CanWrite) { @@ -101,10 +109,12 @@ public void Open(Stream stream) throw new NotSupportedException("Need a read-write stream to the hardware device"); } } + } public void Close() { - StopThread(); + _inputThreadShouldExit = true; + lock (_synchronisationLock) { if (_firmataStream != null) @@ -115,10 +125,12 @@ public void Close() _firmataStream = null; } + StopThread(); + if (_dataReceived != null) { _dataReceived.Dispose(); - _dataReceived = null; + _dataReceived = null!; } } @@ -127,6 +139,11 @@ public void Close() /// private void SendString(byte command, string message) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + byte[] bytes = Encoding.Unicode.GetBytes(message); lock (_synchronisationLock) { @@ -140,6 +157,11 @@ private void SendString(byte command, string message) private void StartListening() { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + if (_inputThread != null && _inputThread.IsAlive) { return; @@ -552,7 +574,13 @@ private string PrintfFromByteStream(string fmt, in Span bytesReceived, int private bool FillQueue() { - Span rawData = stackalloc byte[32]; + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + + Span rawData = stackalloc byte[100]; + int bytesRead = _firmataStream.Read(rawData); for (int i = 0; i < bytesRead; i++) { @@ -579,6 +607,11 @@ private void InputThread() public Version QueryFirmataVersion() { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + // Try 3 times (because we have to make sure the receiver's input queue is properly synchronized) for (int i = 0; i < 3; i++) { @@ -590,6 +623,8 @@ public Version QueryFirmataVersion() bool result = _dataReceived.WaitOne(TimeSpan.FromSeconds(FIRMATA_INIT_TIMEOUT_SECONDS)); if (result == false) { + // Attempt to send a SYSTEM_RESET command + _firmataStream.WriteByte(0xFF); continue; } @@ -607,6 +642,11 @@ public Version QuerySupportedFirmataVersion() public Version QueryFirmwareVersion(out string firmwareName) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + // Try 3 times (because we have to make sure the receiver's input queue is properly synchronized) for (int i = 0; i < 3; i++) { @@ -632,6 +672,11 @@ public Version QueryFirmwareVersion(out string firmwareName) public void QueryCapabilities() { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + lock (_synchronisationLock) { _dataReceived.Reset(); @@ -668,7 +713,7 @@ private void StopThread() private T PerformRetries(int numberOfRetries, Func operation) { - Exception lastException = null; + Exception? lastException = null; while (numberOfRetries-- > 0) { try @@ -688,6 +733,11 @@ private T PerformRetries(int numberOfRetries, Func operation) public void SetPinMode(int pin, SupportedMode firmataMode) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + for (int i = 0; i < 3; i++) { lock (_synchronisationLock) @@ -709,6 +759,11 @@ public void SetPinMode(int pin, SupportedMode firmataMode) public SupportedMode GetPinMode(int pinNumber) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + return PerformRetries(3, () => { lock (_synchronisationLock) @@ -748,6 +803,11 @@ public SupportedMode GetPinMode(int pinNumber) /// public void EnableDigitalReporting() { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + int numPorts = (int)Math.Ceiling(PinConfigurations.Count / 8.0); lock (_synchronisationLock) { @@ -770,6 +830,11 @@ public PinValue GetDigitalPinState(int pinNumber) public void WriteDigitalPin(int pin, PinValue value) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + lock (_synchronisationLock) { _firmataStream.WriteByte((byte)FirmataCommand.SET_DIGITAL_VALUE); @@ -781,6 +846,11 @@ public void WriteDigitalPin(int pin, PinValue value) public void SendI2cConfigCommand() { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + lock (_synchronisationLock) { // The command is mandatory, even if the argument is typically ignored @@ -795,6 +865,11 @@ public void SendI2cConfigCommand() public void WriteReadI2cData(int slaveAddress, ReadOnlySpan writeData, Span replyData) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + // See documentation at https://github.com/firmata/protocol/blob/master/i2c.md lock (_synchronisationLock) { @@ -855,6 +930,11 @@ public void WriteReadI2cData(int slaveAddress, ReadOnlySpan writeData, Sp public void SetPwmChannel(int pin, double dutyCycle) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + lock (_synchronisationLock) { _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); @@ -877,6 +957,11 @@ public void SetPwmChannel(int pin, double dutyCycle) /// public void EnableAnalogReporting(int pinNumber) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + lock (_synchronisationLock) { _lastAnalogValues[pinNumber] = 0; // to make sure this entry exists @@ -887,6 +972,11 @@ public void EnableAnalogReporting(int pinNumber) public void DisableAnalogReporting(int pinNumber) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + lock (_synchronisationLock) { _firmataStream.WriteByte((byte)((int)FirmataCommand.REPORT_ANALOG_PIN + pinNumber)); @@ -896,6 +986,11 @@ public void DisableAnalogReporting(int pinNumber) public void EnableSpi() { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + lock (_synchronisationLock) { _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); @@ -908,6 +1003,11 @@ public void EnableSpi() public void DisableSpi() { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + lock (_synchronisationLock) { _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); @@ -954,6 +1054,11 @@ public void SpiTransfer(int csPin, ReadOnlySpan writeBytes, Span rea private byte SpiWrite(int csPin, FirmataSpiCommand command, ReadOnlySpan writeBytes) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + byte requestId = (byte)(_lastRequestId++ & 0x7F); _firmataStream.WriteByte((byte)FirmataCommand.START_SYSEX); _firmataStream.WriteByte((byte)FirmataSysexCommand.SPI_DATA); @@ -969,6 +1074,11 @@ private byte SpiWrite(int csPin, FirmataSpiCommand command, ReadOnlySpan w public void SetSamplingInterval(TimeSpan interval) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + int millis = (int)interval.TotalMilliseconds; lock (_synchronisationLock) { @@ -984,6 +1094,11 @@ public void SetSamplingInterval(TimeSpan interval) public void ConfigureSpiDevice(SpiConnectionSettings connectionSettings) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + if (connectionSettings.ChipSelectLine >= 15) { // this is currently because we derive the device id from the CS line, and that one has only 4 bits @@ -1014,6 +1129,11 @@ public void ConfigureSpiDevice(SpiConnectionSettings connectionSettings) public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, out Ratio humidity) { + if (_firmataStream == null) + { + throw new ObjectDisposedException(nameof(FirmataDevice)); + } + temperature = default; humidity = default; lock (_synchronisationLock) @@ -1061,22 +1181,14 @@ public uint GetAnalogRawValue(int pinNumber) } } - private void WaitAndHandleIlCommandReply() + + private void SendValuesAsTwo7bitBytes(ReadOnlySpan values) { - bool result = _dataReceived.WaitOne(DefaultReplyTimeout); - if (result == false) - { - throw new TimeoutException("Arduino failed to accept code sequence."); - } - - if (_lastIlExecutionError != 0) + if (_firmataStream == null) { - throw new TaskSchedulerException($"Task scheduler method returned state {_lastIlExecutionError}."); + throw new ObjectDisposedException(nameof(FirmataDevice)); } - } - private void SendValuesAsTwo7bitBytes(ReadOnlySpan values) - { for (int i = 0; i < values.Length; i++) { _firmataStream.WriteByte((byte)(values[i] & (uint)sbyte.MaxValue)); diff --git a/src/devices/Arduino/samples/Arduino.Monitor.cs b/src/devices/Arduino/samples/Arduino.Monitor.cs index fc69fc315b..dce0638778 100644 --- a/src/devices/Arduino/samples/Arduino.Monitor.cs +++ b/src/devices/Arduino/samples/Arduino.Monitor.cs @@ -75,7 +75,7 @@ public static void Main(string[] args) } } - private static void BoardOnLogMessages(string message, Exception exception) + private static void BoardOnLogMessages(string message, Exception? exception) { Console.WriteLine("Log message: " + message); if (exception != null) @@ -88,7 +88,7 @@ public static void DisplayModes(ArduinoBoard board) { const int Gpio2 = 2; const int MaxMode = 10; - const double StationAltitude = 650; + Length stationAltitude = Length.FromMeters(650); int mode = 0; var gpioController = board.CreateGpioController(PinNumberingScheme.Board); gpioController.OpenPin(Gpio2); @@ -114,7 +114,7 @@ void ChangeMode(object sender, PinValueChangedEventArgs pinValueChangedEventArgs gpioController.RegisterCallbackForPinValueChangedEvent(Gpio2, PinEventTypes.Falling, ChangeMode); var device = board.CreateI2cDevice(new I2cConnectionSettings(0, Bmp280.DefaultI2cAddress)); - Bmp280 bmp; + Bmp280? bmp; try { bmp = new Bmp280(device); @@ -169,7 +169,7 @@ void ChangeMode(object sender, PinValueChangedEventArgs pinValueChangedEventArgs modeName = "Temperature / Barometric Pressure"; if (bmp != null && bmp.TryReadTemperature(out Temperature temp) && bmp.TryReadPressure(out Pressure p2)) { - Pressure p3 = WeatherHelper.CalculateBarometricPressure(p2, temp, StationAltitude); + Pressure p3 = WeatherHelper.CalculateBarometricPressure(p2, temp, stationAltitude); disp.Output.ReplaceLine(1, string.Format(CultureInfo.CurrentCulture, "{0:s1} {1:s1}", temp, p3)); } else @@ -195,7 +195,7 @@ void ChangeMode(object sender, PinValueChangedEventArgs pinValueChangedEventArgs modeName = "Dew point"; if (bmp != null && bmp.TryReadPressure(out p2) && board.TryReadDht(3, 11, out temp, out humidity)) { - Temperature dewPoint = WeatherHelper.CalculateDewPoint(temp, humidity.Percent); + Temperature dewPoint = WeatherHelper.CalculateDewPoint(temp, humidity); disp.Output.ReplaceLine(1, dewPoint.ToString("s1", CultureInfo.CurrentCulture)); } else diff --git a/src/devices/Arduino/samples/Arduino.Monitor.csproj b/src/devices/Arduino/samples/Arduino.Monitor.csproj index f43f0e066e..16df7ee763 100644 --- a/src/devices/Arduino/samples/Arduino.Monitor.csproj +++ b/src/devices/Arduino/samples/Arduino.Monitor.csproj @@ -23,6 +23,7 @@ + diff --git a/src/devices/Arduino/samples/Arduino.sample.cs b/src/devices/Arduino/samples/Arduino.sample.cs index 0ad669ead6..13ac2e986c 100644 --- a/src/devices/Arduino/samples/Arduino.sample.cs +++ b/src/devices/Arduino/samples/Arduino.sample.cs @@ -278,7 +278,7 @@ public static void TestAnalogIn(ArduinoBoard board) { // Use Pin 6 const int gpio = 6; - const int analogPin = 15; + int analogPin = GetAnalogPin1(board); var gpioController = board.CreateGpioController(PinNumberingScheme.Board); var analogController = board.CreateAnalogController(0); @@ -305,7 +305,7 @@ public static void TestAnalogIn(ArduinoBoard board) public static void TestAnalogCallback(ArduinoBoard board) { - const int analogPin = 15; + int analogPin = GetAnalogPin1(board); var analogController = board.CreateAnalogController(0); var pin = analogController.OpenPin(analogPin); @@ -332,6 +332,21 @@ public static void TestAnalogCallback(ArduinoBoard board) analogController.Dispose(); } + private static int GetAnalogPin1(ArduinoBoard board) + { + int analogPin = 15; + foreach (var pin in board.SupportedPinConfigurations) + { + if (pin.AnalogPinNumber == 1) + { + analogPin = pin.Pin; + break; + } + } + + return analogPin; + } + public static void TestInput(ArduinoBoard board) { const int gpio = 2; @@ -483,7 +498,7 @@ public static void TestDht(ArduinoBoard board) Console.WriteLine("Unable to read DHT11"); } - Thread.Sleep(2000); + Thread.Sleep(2500); } Console.ReadKey(); diff --git a/src/devices/Arduino/samples/Arduino.sample.csproj b/src/devices/Arduino/samples/Arduino.sample.csproj index 230f89760f..9560501daf 100644 --- a/src/devices/Arduino/samples/Arduino.sample.csproj +++ b/src/devices/Arduino/samples/Arduino.sample.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.1 + net5.0;netcoreapp2.1 Iot.Device.Arduino.Sample false @@ -17,7 +17,6 @@ - diff --git a/src/devices/Arduino/tests/Arduino.Tests.csproj b/src/devices/Arduino/tests/Arduino.Tests.csproj new file mode 100644 index 0000000000..9fed937e42 --- /dev/null +++ b/src/devices/Arduino/tests/Arduino.Tests.csproj @@ -0,0 +1,28 @@ + + + + net5.0 + false + latest + false + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + PreserveNewest + + + + diff --git a/src/devices/Arduino/tests/BasicFirmataTests.cs b/src/devices/Arduino/tests/BasicFirmataTests.cs new file mode 100644 index 0000000000..07a3b51868 --- /dev/null +++ b/src/devices/Arduino/tests/BasicFirmataTests.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Device.Gpio; +using System.IO; +using System.IO.Ports; +using System.Linq; +using System.Threading; +using Arduino.Tests; +using Xunit; + +namespace Iot.Device.Arduino.Tests +{ + /// + /// Basic firmata tests. These tests require functional hardware, that is an Arduino, loaded with a matching firmata firmware. It can also + /// run against the *ExtendedConfigurableFirmata" simulator + /// + public sealed class BasicFirmataTests : IClassFixture + { + private readonly FirmataTestFixture _fixture; + + public BasicFirmataTests(FirmataTestFixture fixture) + { + _fixture = fixture; + Board = _fixture.Board; + } + + public ArduinoBoard Board + { + get; + } + + [Fact] + public void CheckFirmwareVersion() + { + Assert.False(string.IsNullOrWhiteSpace(Board.FirmwareName)); + Assert.NotEqual(new Version(), Board.FirmwareVersion); + Assert.True(Board.FirmwareVersion >= Version.Parse("2.11")); + } + + [Fact] + public void CheckFirmataVersion() + { + Assert.NotNull(Board.FirmataVersion); + Assert.True(Board.FirmataVersion >= Version.Parse("2.5")); + } + + /// + /// Verifies the pin capability message. Also verifies that the arduino is configured properly for these tests + /// + [Fact] + public void CheckBoardFeatures() + { + var caps = Board.SupportedPinConfigurations; + Assert.NotNull(caps); + Assert.True(caps.Count > 0); + // These are minimum numbers all Arduinos should support, so this test should also pass on hardware (when all required modules are present) + Assert.True(caps.Count(x => x.PinModes.Contains(SupportedMode.DIGITAL_OUTPUT)) > 10); + Assert.True(caps.Count(x => x.PinModes.Contains(SupportedMode.DIGITAL_INPUT)) > 10); + Assert.True(caps.Count(x => x.PinModes.Contains(SupportedMode.ANALOG_INPUT)) > 5); + Assert.True(caps.Count(x => x.PinModes.Contains(SupportedMode.I2C)) >= 2); + Assert.True(caps.Count(x => x.PinModes.Contains(SupportedMode.SPI)) >= 3); + } + + [Fact] + public void CanBlink() + { + var ctrl = Board.CreateGpioController(PinNumberingScheme.Logical); + Assert.NotNull(ctrl); + ctrl.OpenPin(6, PinMode.Output); + ctrl.SetPinMode(6, PinMode.Output); + ctrl.Write(6, PinValue.High); + Thread.Sleep(100); + ctrl.Write(6, PinValue.Low); + ctrl.SetPinMode(6, PinMode.Input); + ctrl.ClosePin(6); + } + + [Fact] + public void SetPinMode() + { + var ctrl = Board.CreateGpioController(PinNumberingScheme.Logical); + Assert.NotNull(ctrl); + ctrl.OpenPin(6, PinMode.Output); + ctrl.SetPinMode(6, PinMode.Output); + Assert.Equal(PinMode.Output, ctrl.GetPinMode(6)); + ctrl.SetPinMode(6, PinMode.Input); + Assert.Equal(PinMode.Input, ctrl.GetPinMode(6)); + + ctrl.SetPinMode(6, PinMode.InputPullUp); + Assert.Equal(PinMode.InputPullUp, ctrl.GetPinMode(6)); + + Assert.Throws(() => ctrl.SetPinMode(6, PinMode.InputPullDown)); + ctrl.ClosePin(6); + } + + [Fact] + public void ReadAnalog() + { + int pinNumber = GetFirstAnalogPin(Board); + var ctrl = Board.CreateAnalogController(0); + var pin = ctrl.OpenPin(pinNumber); + Assert.NotNull(pin); + var result = pin.ReadVoltage(); + Assert.True(result >= 0 && result <= 5.1); + ctrl.Dispose(); + } + + private static int GetFirstAnalogPin(ArduinoBoard board) + { + int analogPin = 14; + foreach (var pin in board.SupportedPinConfigurations) + { + if (pin.AnalogPinNumber == 0) + { + analogPin = pin.Pin; + break; + } + } + + return analogPin; + } + } +} diff --git a/src/devices/Arduino/tests/Encoder7BitTest.cs b/src/devices/Arduino/tests/Encoder7BitTest.cs new file mode 100644 index 0000000000..0c85b29b3e --- /dev/null +++ b/src/devices/Arduino/tests/Encoder7BitTest.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Iot.Device.Arduino; +using Xunit; + +namespace Arduino.Tests +{ + public class Encoder7BitTest + { + [Fact] + public void EncodeDecode1() + { + byte[] dataToEncode = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + var encoded = Encoder7Bit.Encode(dataToEncode); + + Assert.True(encoded.Length > dataToEncode.Length); + + var decoded = Encoder7Bit.Decode(encoded); + + Assert.Equal(dataToEncode, decoded); + } + + [Fact] + public void EncodeDecode2() + { + byte[] dataToEncode = new byte[] { 0, 0, 0, 0, 0, 0xFF }; + + var encoded = Encoder7Bit.Encode(dataToEncode); + + Assert.True(encoded.Length > dataToEncode.Length); + + var decoded = Encoder7Bit.Decode(encoded); + + Assert.Equal(dataToEncode, decoded); + } + } +} diff --git a/src/devices/Arduino/tests/FirmataTestFixture.cs b/src/devices/Arduino/tests/FirmataTestFixture.cs new file mode 100644 index 0000000000..7433948884 --- /dev/null +++ b/src/devices/Arduino/tests/FirmataTestFixture.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using Iot.Device.Arduino; + +namespace Arduino.Tests +{ + public class FirmataTestFixture : IDisposable + { + private NetworkStream? _networkStream; + private Socket? _socket; + + public FirmataTestFixture() + { + try + { + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _socket.Connect(IPAddress.Loopback, 27016); + _socket.NoDelay = true; + _networkStream = new NetworkStream(_socket, true); + Board = new ArduinoBoard(_networkStream); + Board.Initialize(); + Board.LogMessages += (x, y) => Console.WriteLine(x); + + return; + } + catch (SocketException) + { + Console.WriteLine("Unable to connect to simulator, trying hardware..."); + } + + var b = ArduinoBoard.FindBoard(ArduinoBoard.GetSerialPortNames(), new List() { 115200 }); + if (b == null) + { + throw new NotSupportedException("No board found"); + } + + Board = b; + Board.LogMessages += (x, y) => Console.WriteLine(x); + } + + public ArduinoBoard Board + { + get; + } + + protected virtual void Dispose(bool disposing) + { + Board.Dispose(); + if (_networkStream != null) + { + _networkStream.Dispose(); + _networkStream = null; + } + + if (_socket != null) + { + _socket.Close(); + _socket.Dispose(); + _socket = null; + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/devices/Arduino/tests/xunit.runner.json b/src/devices/Arduino/tests/xunit.runner.json new file mode 100644 index 0000000000..809e880c71 --- /dev/null +++ b/src/devices/Arduino/tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "parallelizeAssembly": false, + "parallelizeTestCollections": false +} diff --git a/src/devices/Common/System/Device/Analog/AnalogInputPin.cs b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs index 729036b92c..ed3c5bfc7b 100644 --- a/src/devices/Common/System/Device/Analog/AnalogInputPin.cs +++ b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs @@ -10,14 +10,14 @@ namespace System.Device.Analog { /// - /// Driver vor analog input pins + /// Driver for analog input pins /// public abstract class AnalogInputPin : IDisposable { /// /// Fires when a value is changed for which a trigger is registered /// - public event ValueChangedEventHandler ValueChanged; + public event ValueChangedEventHandler? ValueChanged; /// /// Construct an instance of an analog pin. From 84f3ec1c578e4c6627af215badf7e58f7682f401 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Fri, 18 Dec 2020 19:03:39 +0100 Subject: [PATCH 15/56] Not yet part of project --- src/devices/Arduino/tests/Encoder7BitTest.cs | 39 -------------------- 1 file changed, 39 deletions(-) delete mode 100644 src/devices/Arduino/tests/Encoder7BitTest.cs diff --git a/src/devices/Arduino/tests/Encoder7BitTest.cs b/src/devices/Arduino/tests/Encoder7BitTest.cs deleted file mode 100644 index 0c85b29b3e..0000000000 --- a/src/devices/Arduino/tests/Encoder7BitTest.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Iot.Device.Arduino; -using Xunit; - -namespace Arduino.Tests -{ - public class Encoder7BitTest - { - [Fact] - public void EncodeDecode1() - { - byte[] dataToEncode = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - var encoded = Encoder7Bit.Encode(dataToEncode); - - Assert.True(encoded.Length > dataToEncode.Length); - - var decoded = Encoder7Bit.Decode(encoded); - - Assert.Equal(dataToEncode, decoded); - } - - [Fact] - public void EncodeDecode2() - { - byte[] dataToEncode = new byte[] { 0, 0, 0, 0, 0, 0xFF }; - - var encoded = Encoder7Bit.Encode(dataToEncode); - - Assert.True(encoded.Length > dataToEncode.Length); - - var decoded = Encoder7Bit.Decode(encoded); - - Assert.Equal(dataToEncode, decoded); - } - } -} From e40340bec2012a1f7a998bcd91a2afa9799659f2 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Fri, 18 Dec 2020 19:04:19 +0100 Subject: [PATCH 16/56] Nullability and other fixes --- src/devices/Arduino/ArduinoBoard.cs | 3 ++- src/devices/Arduino/FirmataDevice.cs | 24 +++++++++---------- src/devices/Arduino/SupportedMode.cs | 2 +- .../Arduino/SupportedPinConfiguration.cs | 18 +++++++++++--- .../Arduino/samples/Arduino.Monitor.cs | 1 + .../Arduino/samples/Arduino.Monitor.csproj | 4 ++-- src/devices/Arduino/samples/Arduino.sample.cs | 2 +- .../Arduino/tests/BasicFirmataTests.cs | 2 +- .../Arduino/tests/FirmataTestFixture.cs | 2 +- .../System/Device/Analog/AnalogInputPin.cs | 2 +- 10 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index 957a0b7038..d6e26abc53 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Device.Analog; using System.Text; using System.Device.Gpio; @@ -348,7 +349,7 @@ public AnalogController CreateAnalogController(int chip) /// Temperature /// Relative humidity /// True on success, false otherwise - public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, out Ratio humidity) + public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, out RelativeHumidity humidity) { if (!_supportedPinConfigurations[pinNumber].PinModes.Contains(SupportedMode.DHT)) { diff --git a/src/devices/Arduino/FirmataDevice.cs b/src/devices/Arduino/FirmataDevice.cs index 28242ca3b7..494d813ddf 100644 --- a/src/devices/Arduino/FirmataDevice.cs +++ b/src/devices/Arduino/FirmataDevice.cs @@ -78,7 +78,6 @@ public FirmataDevice() _dataQueue = new Queue(); _lastResponse = new List(); _lastRequestId = 1; - _lastIlExecutionError = 0; _firmwareName = string.Empty; } @@ -99,17 +98,17 @@ public void Open(Stream stream) throw new InvalidOperationException("The device is already open"); } - _firmataStream = stream; - if (_firmataStream.CanRead && _firmataStream.CanWrite) - { - StartListening(); - } - else - { - throw new NotSupportedException("Need a read-write stream to the hardware device"); + _firmataStream = stream; + if (_firmataStream.CanRead && _firmataStream.CanWrite) + { + StartListening(); + } + else + { + throw new NotSupportedException("Need a read-write stream to the hardware device"); + } } } - } public void Close() { @@ -1127,7 +1126,7 @@ public void ConfigureSpiDevice(SpiConnectionSettings connectionSettings) } } - public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, out Ratio humidity) + public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, out RelativeHumidity humidity) { if (_firmataStream == null) { @@ -1167,7 +1166,7 @@ public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, int h = _lastResponse[5] | _lastResponse[6] << 7; temperature = Temperature.FromDegreesCelsius(t / 10.0); - humidity = Ratio.FromPercent(h / 10.0); + humidity = RelativeHumidity.FromPercent(h / 10.0); } return true; @@ -1181,7 +1180,6 @@ public uint GetAnalogRawValue(int pinNumber) } } - private void SendValuesAsTwo7bitBytes(ReadOnlySpan values) { if (_firmataStream == null) diff --git a/src/devices/Arduino/SupportedMode.cs b/src/devices/Arduino/SupportedMode.cs index dd6e89c623..19f5392f07 100644 --- a/src/devices/Arduino/SupportedMode.cs +++ b/src/devices/Arduino/SupportedMode.cs @@ -8,7 +8,7 @@ namespace Iot.Device.Arduino /// Mode bits for the Firmata protocol. /// These are used both for capability reporting as well as to set a mode /// - internal enum SupportedMode + public enum SupportedMode { /// /// The pin supports digital input diff --git a/src/devices/Arduino/SupportedPinConfiguration.cs b/src/devices/Arduino/SupportedPinConfiguration.cs index e1d25a122a..5744a6de24 100644 --- a/src/devices/Arduino/SupportedPinConfiguration.cs +++ b/src/devices/Arduino/SupportedPinConfiguration.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; @@ -10,9 +9,12 @@ namespace Iot.Device.Arduino { - internal class SupportedPinConfiguration + /// + /// Describes the capabilities of a pin + /// + public class SupportedPinConfiguration { - public SupportedPinConfiguration(int pin) + internal SupportedPinConfiguration(int pin) { Pin = pin; PinModes = new List(); @@ -21,16 +23,25 @@ public SupportedPinConfiguration(int pin) AnalogPinNumber = 127; // = Not an analog pin } + /// + /// The pin number + /// public int Pin { get; } + /// + /// The list of supported modes for this pin + /// public List PinModes { get; } + /// + /// The width of the PWM register, typical value is 10 (the maximum value is then 1023) + /// public int PwmResolutionBits { get; @@ -55,6 +66,7 @@ public byte AnalogPinNumber internal set; } + /// public override string ToString() { string pinModes = String.Join(", ", PinModes); diff --git a/src/devices/Arduino/samples/Arduino.Monitor.cs b/src/devices/Arduino/samples/Arduino.Monitor.cs index dce0638778..ef89ad5473 100644 --- a/src/devices/Arduino/samples/Arduino.Monitor.cs +++ b/src/devices/Arduino/samples/Arduino.Monitor.cs @@ -21,6 +21,7 @@ using Iot.Device.Bmxx80.PowerMode; using Iot.Device.Common; using Iot.Device.CpuTemperature; +using Iot.Device.HardwareMonitor; using UnitsNet; namespace Arduino.Samples diff --git a/src/devices/Arduino/samples/Arduino.Monitor.csproj b/src/devices/Arduino/samples/Arduino.Monitor.csproj index 16df7ee763..51f259193b 100644 --- a/src/devices/Arduino/samples/Arduino.Monitor.csproj +++ b/src/devices/Arduino/samples/Arduino.Monitor.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.1 + net5.0 Iot.Device.Arduino.Sample false @@ -13,7 +13,7 @@ - + diff --git a/src/devices/Arduino/samples/Arduino.sample.cs b/src/devices/Arduino/samples/Arduino.sample.cs index 13ac2e986c..a485656f1e 100644 --- a/src/devices/Arduino/samples/Arduino.sample.cs +++ b/src/devices/Arduino/samples/Arduino.sample.cs @@ -81,7 +81,7 @@ public static void Main(string[] args) Debug.Write(logFile.ToString()); } - private static void BoardOnLogMessages(string message, Exception exception) + private static void BoardOnLogMessages(string message, Exception? exception) { Console.WriteLine("Log message: " + message); if (exception != null) diff --git a/src/devices/Arduino/tests/BasicFirmataTests.cs b/src/devices/Arduino/tests/BasicFirmataTests.cs index 07a3b51868..87b2180908 100644 --- a/src/devices/Arduino/tests/BasicFirmataTests.cs +++ b/src/devices/Arduino/tests/BasicFirmataTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. diff --git a/src/devices/Arduino/tests/FirmataTestFixture.cs b/src/devices/Arduino/tests/FirmataTestFixture.cs index 7433948884..15746cfa8e 100644 --- a/src/devices/Arduino/tests/FirmataTestFixture.cs +++ b/src/devices/Arduino/tests/FirmataTestFixture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; diff --git a/src/devices/Common/System/Device/Analog/AnalogInputPin.cs b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs index ed3c5bfc7b..2046fc1072 100644 --- a/src/devices/Common/System/Device/Analog/AnalogInputPin.cs +++ b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs @@ -126,7 +126,7 @@ protected virtual double ConvertToVoltage(uint rawValue) /// /// If an external interrupt handler is required, it can be provided here. Can be null if another interrupt feature is available /// Input pin on the master controller. - public abstract void EnableAnalogValueChangedEvent(GpioController masterController, int masterPin); + public abstract void EnableAnalogValueChangedEvent(GpioController? masterController, int masterPin); /// /// Disables the event callback From fde0791ab88bd75ddb5f865b79f2be9034e222d7 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Fri, 18 Dec 2020 19:10:37 +0100 Subject: [PATCH 17/56] Remove unused project from solution - causes weird conflicts --- src/devices/Arduino/Arduino.sln | 6 ------ src/devices/Arduino/samples/Arduino.Monitor.cs | 1 - src/devices/Arduino/samples/Arduino.Monitor.csproj | 1 - 3 files changed, 8 deletions(-) diff --git a/src/devices/Arduino/Arduino.sln b/src/devices/Arduino/Arduino.sln index 343afa496d..fd1eefa76f 100644 --- a/src/devices/Arduino/Arduino.sln +++ b/src/devices/Arduino/Arduino.sln @@ -17,8 +17,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CharacterLcd", "..\Characte EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "..\Common\CommonHelpers.csproj", "{CD593083-8E94-4B60-86BF-DF3FB7873893}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CpuTemperature", "..\CpuTemperature\CpuTemperature.csproj", "{F3950513-A564-462F-887B-E00972D20FAD}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9E5A25ED-9839-4C1A-9B27-993437D1CB31}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Monitor", "samples\Arduino.Monitor.csproj", "{23B4B60C-9594-42BB-9D25-C54983B0F809}" @@ -63,10 +61,6 @@ Global {CD593083-8E94-4B60-86BF-DF3FB7873893}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.Build.0 = Release|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3950513-A564-462F-887B-E00972D20FAD}.Release|Any CPU.Build.0 = Release|Any CPU {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.Build.0 = Debug|Any CPU {23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/devices/Arduino/samples/Arduino.Monitor.cs b/src/devices/Arduino/samples/Arduino.Monitor.cs index ef89ad5473..7620d43179 100644 --- a/src/devices/Arduino/samples/Arduino.Monitor.cs +++ b/src/devices/Arduino/samples/Arduino.Monitor.cs @@ -20,7 +20,6 @@ using Iot.Device.Bmxx80; using Iot.Device.Bmxx80.PowerMode; using Iot.Device.Common; -using Iot.Device.CpuTemperature; using Iot.Device.HardwareMonitor; using UnitsNet; diff --git a/src/devices/Arduino/samples/Arduino.Monitor.csproj b/src/devices/Arduino/samples/Arduino.Monitor.csproj index 51f259193b..03a947ec52 100644 --- a/src/devices/Arduino/samples/Arduino.Monitor.csproj +++ b/src/devices/Arduino/samples/Arduino.Monitor.csproj @@ -22,7 +22,6 @@ - From f2bfaac09122fcc46de7cc336dd8b6aaf788ab80 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Fri, 22 Jan 2021 16:27:56 +0100 Subject: [PATCH 18/56] Ignore tests instead of failing them if no hardware is available --- .../Arduino/tests/Arduino.Tests.csproj | 1 + ...sts.cs => BasicFirmataIntegrationTests.cs} | 21 ++++++++++--------- .../Arduino/tests/FirmataTestFixture.cs | 8 ++++--- 3 files changed, 17 insertions(+), 13 deletions(-) rename src/devices/Arduino/tests/{BasicFirmataTests.cs => BasicFirmataIntegrationTests.cs} (86%) diff --git a/src/devices/Arduino/tests/Arduino.Tests.csproj b/src/devices/Arduino/tests/Arduino.Tests.csproj index 9fed937e42..ab71cf8f32 100644 --- a/src/devices/Arduino/tests/Arduino.Tests.csproj +++ b/src/devices/Arduino/tests/Arduino.Tests.csproj @@ -16,6 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/devices/Arduino/tests/BasicFirmataTests.cs b/src/devices/Arduino/tests/BasicFirmataIntegrationTests.cs similarity index 86% rename from src/devices/Arduino/tests/BasicFirmataTests.cs rename to src/devices/Arduino/tests/BasicFirmataIntegrationTests.cs index 87b2180908..bd038ca210 100644 --- a/src/devices/Arduino/tests/BasicFirmataTests.cs +++ b/src/devices/Arduino/tests/BasicFirmataIntegrationTests.cs @@ -15,16 +15,17 @@ namespace Iot.Device.Arduino.Tests { /// - /// Basic firmata tests. These tests require functional hardware, that is an Arduino, loaded with a matching firmata firmware. It can also - /// run against the *ExtendedConfigurableFirmata" simulator + /// Basic firmata integrations tests. These tests require functional hardware, that is an Arduino, loaded with a matching firmata firmware. It can also + /// run against the *ExtendedConfigurableFirmata" simulator. If neither is found, the test is marked as "Inconclusive". /// - public sealed class BasicFirmataTests : IClassFixture + public sealed class BasicFirmataIntegrationTests : IClassFixture { private readonly FirmataTestFixture _fixture; - public BasicFirmataTests(FirmataTestFixture fixture) + public BasicFirmataIntegrationTests(FirmataTestFixture fixture) { _fixture = fixture; + Skip.If(_fixture.Board == null, "No Board found"); Board = _fixture.Board; } @@ -33,7 +34,7 @@ public ArduinoBoard Board get; } - [Fact] + [SkippableFact] public void CheckFirmwareVersion() { Assert.False(string.IsNullOrWhiteSpace(Board.FirmwareName)); @@ -41,7 +42,7 @@ public void CheckFirmwareVersion() Assert.True(Board.FirmwareVersion >= Version.Parse("2.11")); } - [Fact] + [SkippableFact] public void CheckFirmataVersion() { Assert.NotNull(Board.FirmataVersion); @@ -51,7 +52,7 @@ public void CheckFirmataVersion() /// /// Verifies the pin capability message. Also verifies that the arduino is configured properly for these tests /// - [Fact] + [SkippableFact] public void CheckBoardFeatures() { var caps = Board.SupportedPinConfigurations; @@ -65,7 +66,7 @@ public void CheckBoardFeatures() Assert.True(caps.Count(x => x.PinModes.Contains(SupportedMode.SPI)) >= 3); } - [Fact] + [SkippableFact] public void CanBlink() { var ctrl = Board.CreateGpioController(PinNumberingScheme.Logical); @@ -79,7 +80,7 @@ public void CanBlink() ctrl.ClosePin(6); } - [Fact] + [SkippableFact] public void SetPinMode() { var ctrl = Board.CreateGpioController(PinNumberingScheme.Logical); @@ -97,7 +98,7 @@ public void SetPinMode() ctrl.ClosePin(6); } - [Fact] + [SkippableFact] public void ReadAnalog() { int pinNumber = GetFirstAnalogPin(Board); diff --git a/src/devices/Arduino/tests/FirmataTestFixture.cs b/src/devices/Arduino/tests/FirmataTestFixture.cs index 15746cfa8e..7b72bc8058 100644 --- a/src/devices/Arduino/tests/FirmataTestFixture.cs +++ b/src/devices/Arduino/tests/FirmataTestFixture.cs @@ -4,6 +4,7 @@ using System.Net.Sockets; using System.Text; using Iot.Device.Arduino; +using Xunit; namespace Arduino.Tests { @@ -34,21 +35,22 @@ public FirmataTestFixture() var b = ArduinoBoard.FindBoard(ArduinoBoard.GetSerialPortNames(), new List() { 115200 }); if (b == null) { - throw new NotSupportedException("No board found"); + Board = null; + return; } Board = b; Board.LogMessages += (x, y) => Console.WriteLine(x); } - public ArduinoBoard Board + public ArduinoBoard? Board { get; } protected virtual void Dispose(bool disposing) { - Board.Dispose(); + Board?.Dispose(); if (_networkStream != null) { _networkStream.Dispose(); From f50bfc1ea7e08f06a985200454a4ba102d2cf12e Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Fri, 22 Jan 2021 16:28:31 +0100 Subject: [PATCH 19/56] Documentation update --- src/devices/Arduino/ArduinoBoard.cs | 15 ++++++++------- src/devices/Arduino/FirmataDevice.cs | 3 +++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index d6e26abc53..c142211f00 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -271,7 +271,7 @@ public GpioController CreateGpioController() /// /// Pin numbering scheme to use for this controller /// An instance of GpioController, using an Arduino-Enabled driver - public GpioController CreateGpioController(PinNumberingScheme pinNumberingScheme) + public virtual GpioController CreateGpioController(PinNumberingScheme pinNumberingScheme) { return new GpioController(pinNumberingScheme, new ArduinoGpioControllerDriver(this, _supportedPinConfigurations)); } @@ -283,7 +283,7 @@ public GpioController CreateGpioController(PinNumberingScheme pinNumberingScheme /// 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 I2cDevice CreateI2cDevice(I2cConnectionSettings connectionSettings) + public virtual I2cDevice CreateI2cDevice(I2cConnectionSettings connectionSettings) { if (!SupportedPinConfigurations.Any(x => x.PinModes.Contains(SupportedMode.I2C))) { @@ -294,12 +294,13 @@ public I2cDevice CreateI2cDevice(I2cConnectionSettings connectionSettings) } /// - /// Firmata has no support for SPI, even though the Arduino basically has an SPI interface. - /// This therefore returns a Software SPI device for the default Arduino SPI port on pins 11, 12 and 13. + /// Connect to a device connected to the primary SPI bus on the Arduino + /// Firmata's default implementation has no SPI support, so this first checks whether it's available at all. /// /// Spi Connection settings /// An instance. - public SpiDevice CreateSpiDevice(SpiConnectionSettings settings) + /// The Bus number is not 0, or the SPI component has not been enabled in the firmware. + public virtual SpiDevice CreateSpiDevice(SpiConnectionSettings settings) { if (settings.BusId != 0) { @@ -322,7 +323,7 @@ public SpiDevice CreateSpiDevice(SpiConnectionSettings settings) /// This value is ignored /// The duty cycle as a fraction. /// - public PwmChannel CreatePwmChannel( + public virtual PwmChannel CreatePwmChannel( int chip, int channel, int frequency = 400, @@ -336,7 +337,7 @@ public PwmChannel CreatePwmChannel( /// /// Must be 0 /// An instance - public AnalogController CreateAnalogController(int chip) + public virtual AnalogController CreateAnalogController(int chip) { return new ArduinoAnalogController(this, SupportedPinConfigurations, PinNumberingScheme.Logical); } diff --git a/src/devices/Arduino/FirmataDevice.cs b/src/devices/Arduino/FirmataDevice.cs index 494d813ddf..758a6b498f 100644 --- a/src/devices/Arduino/FirmataDevice.cs +++ b/src/devices/Arduino/FirmataDevice.cs @@ -25,6 +25,9 @@ namespace Iot.Device.Arduino internal delegate void AnalogPinValueUpdated(int pin, uint rawValue); + /// + /// Low-level communication layer for the firmata protocol. Creates the binary command stream for the different commands and returns back results. + /// internal sealed class FirmataDevice : IDisposable { private const byte FIRMATA_PROTOCOL_MAJOR_VERSION = 2; From 39e1a5aac85a4bcd6fe8b0f047def14d938b3d75 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Fri, 22 Jan 2021 16:59:45 +0100 Subject: [PATCH 20/56] Remove stuff not yet supported from template --- .../ConfigurableFirmata.ino | 128 +----------------- 1 file changed, 6 insertions(+), 122 deletions(-) diff --git a/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino b/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino index a8e5325bfa..659117a186 100644 --- a/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino +++ b/src/devices/Arduino/ConfigurableFirmata/ConfigurableFirmata.ino @@ -4,9 +4,12 @@ // Use these defines to easily enable or disable certain modules -/* Note: Currently no client support by dotnet/iot for these, so they're disabled by default */ +/* Note 1: Currently no client support by dotnet/iot for the following modules, so they're disabled by default */ /* Enabling all modules on the smaller Arduino boards (such as the UNO or the Nano) won't work anyway, as there is both * not enough flash as well as not enough RAM. + * + * Note 2: If you get compiler errors or want to test experimental features, it might be better to use the example + * file in the ConfigurableFirmata library (default path C:\Users\\Documents\Arduino\libraries\ConfigurableFirmata\examples\ConfigurableFirmata) */ //#define ENABLE_ONE_WIRE //#define ENABLE_SERVO @@ -98,115 +101,6 @@ AccelStepperFirmata accelStepper; DhtFirmata dhtFirmata; #endif -#ifdef ENABLE_IL_EXECUTOR -#include "FirmataIlExecutor.h" -FirmataIlExecutor ilExecutor; -#endif -#ifdef DEBUG_STREAM -const byte SimulatedInput[] PROGMEM = -{ -0xFF, 0xF9, 0xF0, 0x79, 0xF7, 0xF0, 0x6B, 0xF7, 0xF0, 0x69, 0xF7, 0xD0, 0x01, 0xD1, 0x01, 0xD2, -0x01, 0xF0, 0x7B, 0xFF, 0x05, 0x01, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x00, 0x01, 0x08, 0x02, 0x17, -0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x00, 0x04, 0x00, 0x00, -0x00, 0x02, 0x00, 0x03, 0x00, 0x58, 0x00, 0x2A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, -0xF0, 0x7B, 0xFF, 0x04, 0x00, 0x7F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x01, 0x01, 0x08, 0x02, 0x1A, 0x00, -0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x01, 0x05, 0x00, 0x00, 0x00, -0x02, 0x00, 0x03, 0x00, 0x7E, 0x01, 0x01, 0x00, 0x2A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x01, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, -0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x02, 0x01, 0x08, 0x02, 0x13, -0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x02, 0x1B, 0x00, 0x00, -0x00, 0x03, 0x00, 0x1F, 0x00, 0x14, 0x00, 0x32, 0x00, 0x02, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x02, -0x00, 0x03, 0x00, 0x31, 0x00, 0x02, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x03, 0x00, 0x18, 0x00, 0x2E, -0x00, 0x02, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x02, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x02, 0x1B, -0x00, 0x14, 0x00, 0x16, 0x00, 0x30, 0x00, 0x02, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x17, 0x00, 0x2A, -0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, -0xF0, 0x7B, 0xFF, 0x05, 0x01, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x00, 0x04, 0x04, 0x01, 0x49, 0x01, -0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x01, 0x0C, 0x01, 0x03, 0x4A, -0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x02, 0x0C, 0x02, 0x03, -0x4B, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x03, 0x04, 0x03, -0x02, 0x4C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x04, 0x0C, -0x05, 0x02, 0x4D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x05, -0x04, 0x06, 0x01, 0x4E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, -0x06, 0x0C, 0x07, 0x02, 0x4F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, -0x01, 0x07, 0x09, 0x03, 0x02, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF7, 0xF0, 0x7B, -0xFF, 0x03, 0x07, 0x3E, 0x00, 0x00, 0x00, 0x03, 0x00, 0x16, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x02, -0x00, 0x20, 0x00, 0x2A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x4F, 0x01, 0x00, -0x00, 0x00, 0x00, 0x06, 0x00, 0x2A, 0x00, 0x02, 0x00, 0x6F, 0x00, 0x49, 0x01, 0x00, 0x00, 0xF7, -0xF0, 0x7B, 0xFF, 0x03, 0x07, 0x3E, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x06, -0x00, 0x03, 0x00, 0x58, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x07, 0x00, 0x31, 0x00, 0x1A, 0x00, 0x06, -0x00, 0x0C, 0x00, 0x2B, 0x00, 0x09, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x02, 0x00, 0x6F, 0x00, 0x49, -0x01, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x07, 0x3E, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, -0x00, 0x0A, 0x00, 0x08, 0x00, 0x06, 0x00, 0x32, 0x00, 0x73, 0x01, 0x2B, 0x00, 0x07, 0x00, 0x02, -0x00, 0x6F, 0x00, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, -0x00, 0x30, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x07, 0x3E, 0x00, 0x3C, 0x00, 0x75, 0x01, 0x2A, -0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x01, 0x08, 0x01, 0x07, 0x02, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, -0x06, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x02, 0x08, 0x0C, 0x00, 0x00, 0x00, 0x4A, 0x01, 0x00, 0x00, -0x00, 0x00, 0x06, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x4B, 0x01, 0x00, 0x00, -0x00, 0x00, 0x06, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, -0x02, 0x08, 0x0C, 0x00, 0x04, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x4F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x01, -0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x02, 0x08, 0x0C, 0x00, 0x08, 0x00, -0x4C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, -0x4E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, -0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x00, 0x00, 0x16, 0x00, 0x0B, 0x00, 0x16, 0x00, -0x0C, 0x00, 0x1F, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x20, 0x00, 0x50, 0x01, 0x07, 0x00, 0x00, 0x00, -0x00, 0x00, 0x13, 0x00, 0x04, 0x00, 0x02, 0x00, 0x03, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7E, 0x00, -0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x14, 0x00, 0x00, 0x00, 0x0A, 0x00, -0x02, 0x00, 0x03, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, -0x02, 0x00, 0x09, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, -0x02, 0x00, 0x09, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x28, 0x00, 0x16, 0x00, -0x6F, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x1F, 0x00, 0x14, 0x00, -0x28, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x16, 0x00, -0x6F, 0x00, 0x7F, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x3C, 0x00, -0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x1F, 0x00, 0x14, 0x00, 0x28, 0x00, 0x00, 0x01, 0x00, 0x00, -0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x19, 0x00, 0x6F, 0x00, 0x7E, 0x00, 0x00, 0x00, -0x00, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x04, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, -0x50, 0x00, 0x0A, 0x00, 0x2B, 0x00, 0x14, 0x00, 0x06, 0x00, 0x25, 0x00, 0x17, 0x00, 0x59, 0x00, -0x0A, 0x00, 0x2D, 0x00, 0x0D, 0x00, 0x02, 0x00, 0x20, 0x00, 0x2A, 0x01, 0x2A, 0x01, 0x00, 0x00, -0x00, 0x00, 0x6F, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, -0x28, 0x02, 0x64, 0x00, 0x0A, 0x00, 0x16, 0x00, 0x2A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x6F, 0x00, -0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x2C, 0x00, 0x63, 0x01, 0x11, 0x00, 0x04, 0x00, -0x0A, 0x00, 0x2B, 0x00, 0x1C, 0x00, 0x06, 0x00, 0x25, 0x00, 0x17, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, -0x03, 0x08, 0x28, 0x02, 0x78, 0x00, 0x59, 0x00, 0x0A, 0x00, 0x2D, 0x00, 0x15, 0x00, 0x02, 0x00, -0x09, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x02, 0x00, -0x20, 0x00, 0x3B, 0x01, 0x3B, 0x01, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x01, 0x01, 0xF7, 0xF0, -0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x0C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x16, 0x00, -0x2A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x6F, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, -0x17, 0x00, 0x2E, 0x00, 0x5A, 0x01, 0x02, 0x00, 0x09, 0x00, 0x17, 0x00, 0x6F, 0x00, 0x7F, 0x00, -0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, -0x16, 0x00, 0x13, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x78, 0x00, 0x11, 0x00, 0x04, 0x00, 0x0A, 0x00, -0x2B, 0x00, 0x11, 0x00, 0x06, 0x00, 0x25, 0x00, 0x17, 0x00, 0x59, 0x00, 0x0A, 0x00, 0x2D, 0x00, -0x0A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x34, 0x01, 0x02, 0x00, 0x11, 0x00, -0x05, 0x00, 0x6F, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x16, 0x00, 0x2A, 0x00, -0x02, 0x00, 0x03, 0x00, 0x6F, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x2C, 0x00, -0x66, 0x01, 0x02, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x48, 0x01, 0x6F, 0x00, -0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x13, 0x00, 0x06, 0x00, 0x11, 0x00, 0x04, 0x00, -0x0A, 0x00, 0x2B, 0x00, 0x11, 0x00, 0x06, 0x00, 0x25, 0x00, 0x17, 0x00, 0x59, 0x00, 0x0A, 0x00, -0x2D, 0x00, 0x0A, 0x00, 0x02, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, 0x5C, 0x01, -0x11, 0x00, 0x05, 0x00, 0x6F, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x16, 0x00, -0x2A, 0x00, 0x02, 0x00, 0x03, 0x00, 0x6F, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, -0x17, 0x00, 0x2E, 0x00, 0x65, 0x01, 0x02, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, 0x28, 0x02, -0x70, 0x01, 0x6F, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x06, 0x00, -0x59, 0x00, 0x07, 0x00, 0x17, 0x00, 0x62, 0x00, 0x0B, 0x00, 0x20, 0x00, 0x40, 0x00, 0x42, 0x00, -0x0F, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x20, 0x00, 0x40, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x03, 0x08, -0x28, 0x02, 0x04, 0x02, 0x42, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x1F, 0x00, 0x1E, 0x00, -0x36, 0x00, 0x04, 0x00, 0x07, 0x00, 0x17, 0x00, 0x60, 0x00, 0x0B, 0x00, 0x11, 0x00, 0x05, 0x00, -0x1F, 0x00, 0x1F, 0x00, 0x33, 0x00, 0x04, 0x00, 0x07, 0x00, 0x0C, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, -0x03, 0x08, 0x28, 0x02, 0x18, 0x02, 0x16, 0x00, 0x0B, 0x00, 0x11, 0x00, 0x05, 0x00, 0x17, 0x00, -0x58, 0x00, 0x13, 0x00, 0x05, 0x00, 0x11, 0x00, 0x05, 0x00, 0x1F, 0x00, 0x28, 0x00, 0x32, 0x00, -0x02, 0x01, 0x08, 0x00, 0x2A, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xF0, 0x7B, 0xFF, -0x06, 0x08, 0xF7, -}; - -#include -FlashMemoryStream debugStream(SimulatedInput, sizeof(SimulatedInput)); -#endif - void systemResetCallback() { for (byte i = 0; i < TOTAL_PINS; i++) { @@ -223,11 +117,8 @@ void initTransport() { // Uncomment to save a couple of seconds by disabling the startup blink sequence. // Firmata.disableBlinkVersion(); -#ifdef DEBUG_STREAM - Firmata.begin(debugStream); -#else Firmata.begin(115200); -#endif + } void initFirmata() @@ -280,10 +171,6 @@ void initFirmata() firmataExt.addFeature(dhtFirmata); #endif -#ifdef ENABLE_IL_EXECUTOR - firmataExt.addFeature(ilExecutor); -#endif - Firmata.attach(SYSTEM_RESET, systemResetCallback); } @@ -309,11 +196,8 @@ void loop() runtasks: #ifdef ENABLE_BASIC_SCHEDULER scheduler.runTasks(); -#endif -#ifdef ENABLE_IL_EXECUTOR - ilExecutor.runStep(); #else - 0 == 0; // Do something useless + 0 == 0; // Do something useless (to prevent a compiler error) #endif } From 597f3326ec5ef66333ad6daaafaa66da9f42d523 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Tue, 26 Jan 2021 07:39:10 +0100 Subject: [PATCH 21/56] Fix indentation --- src/devices/Common/CommonHelpers.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/devices/Common/CommonHelpers.csproj b/src/devices/Common/CommonHelpers.csproj index 0657bbad29..2c1bcd2c7f 100644 --- a/src/devices/Common/CommonHelpers.csproj +++ b/src/devices/Common/CommonHelpers.csproj @@ -9,10 +9,10 @@ - - - - + + + + From f008ff33e05d26e97cb703e333dc1815c755c89e Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Tue, 26 Jan 2021 17:10:05 +0100 Subject: [PATCH 22/56] 2-line license headers --- src/devices/Arduino/ArduinoAnalogController.cs | 1 - src/devices/Arduino/ArduinoAnalogInputPin.cs | 1 - src/devices/Arduino/ArduinoBoard.cs | 1 - src/devices/Arduino/ArduinoGpioControllerDriver.cs | 1 - src/devices/Arduino/ArduinoI2cDevice.cs | 1 - src/devices/Arduino/ArduinoPwmChannel.cs | 1 - src/devices/Arduino/ArduinoSpiDevice.cs | 1 - src/devices/Arduino/FirmataCommand.cs | 1 - src/devices/Arduino/FirmataDevice.cs | 1 - src/devices/Arduino/FirmataSpiCommand.cs | 1 - src/devices/Arduino/FirmataSysexCommand.cs | 1 - src/devices/Arduino/SupportedMode.cs | 1 - src/devices/Arduino/samples/Arduino.Monitor.cs | 1 - src/devices/Arduino/samples/Arduino.sample.cs | 1 - src/devices/Arduino/samples/DebugLogStream.cs | 1 - src/devices/Arduino/tests/BasicFirmataIntegrationTests.cs | 1 - src/devices/Bmxx80/ReadResult/Bme280ReadResult.cs | 1 - src/devices/Bmxx80/ReadResult/Bme680ReadResult.cs | 1 - src/devices/Bmxx80/ReadResult/Bmp280ReadResult.cs | 1 - src/devices/Common/System/Device/Analog/AnalogController.cs | 1 - src/devices/Common/System/Device/Analog/AnalogInputPin.cs | 1 - src/devices/Common/System/Device/Analog/TriggerReason.cs | 1 - src/devices/Common/System/Device/Analog/ValueChangedEventArgs.cs | 1 - .../Common/System/Device/Analog/ValueChangedEventHandler.cs | 1 - 24 files changed, 24 deletions(-) diff --git a/src/devices/Arduino/ArduinoAnalogController.cs b/src/devices/Arduino/ArduinoAnalogController.cs index effae3d133..8499f6541b 100644 --- a/src/devices/Arduino/ArduinoAnalogController.cs +++ b/src/devices/Arduino/ArduinoAnalogController.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Arduino/ArduinoAnalogInputPin.cs b/src/devices/Arduino/ArduinoAnalogInputPin.cs index dce027dca1..8812d3d1db 100644 --- a/src/devices/Arduino/ArduinoAnalogInputPin.cs +++ b/src/devices/Arduino/ArduinoAnalogInputPin.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index c142211f00..2d007ed06d 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Arduino/ArduinoGpioControllerDriver.cs b/src/devices/Arduino/ArduinoGpioControllerDriver.cs index 8a03e4b73d..495745a5e8 100644 --- a/src/devices/Arduino/ArduinoGpioControllerDriver.cs +++ b/src/devices/Arduino/ArduinoGpioControllerDriver.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Concurrent; diff --git a/src/devices/Arduino/ArduinoI2cDevice.cs b/src/devices/Arduino/ArduinoI2cDevice.cs index db41e8fda5..0ba321cb76 100644 --- a/src/devices/Arduino/ArduinoI2cDevice.cs +++ b/src/devices/Arduino/ArduinoI2cDevice.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Arduino/ArduinoPwmChannel.cs b/src/devices/Arduino/ArduinoPwmChannel.cs index 1dcf7b29d5..b6f8a61585 100644 --- a/src/devices/Arduino/ArduinoPwmChannel.cs +++ b/src/devices/Arduino/ArduinoPwmChannel.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Arduino/ArduinoSpiDevice.cs b/src/devices/Arduino/ArduinoSpiDevice.cs index 343c588889..80d2aaf3b2 100644 --- a/src/devices/Arduino/ArduinoSpiDevice.cs +++ b/src/devices/Arduino/ArduinoSpiDevice.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Arduino/FirmataCommand.cs b/src/devices/Arduino/FirmataCommand.cs index 5ea3f8af5e..197bace20f 100644 --- a/src/devices/Arduino/FirmataCommand.cs +++ b/src/devices/Arduino/FirmataCommand.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. namespace Iot.Device.Arduino { diff --git a/src/devices/Arduino/FirmataDevice.cs b/src/devices/Arduino/FirmataDevice.cs index 758a6b498f..58e65fc158 100644 --- a/src/devices/Arduino/FirmataDevice.cs +++ b/src/devices/Arduino/FirmataDevice.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections; diff --git a/src/devices/Arduino/FirmataSpiCommand.cs b/src/devices/Arduino/FirmataSpiCommand.cs index b57a90ab1e..8a3e1e7a23 100644 --- a/src/devices/Arduino/FirmataSpiCommand.cs +++ b/src/devices/Arduino/FirmataSpiCommand.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. namespace Iot.Device.Arduino { diff --git a/src/devices/Arduino/FirmataSysexCommand.cs b/src/devices/Arduino/FirmataSysexCommand.cs index ef16c7c4f3..b08a79a160 100644 --- a/src/devices/Arduino/FirmataSysexCommand.cs +++ b/src/devices/Arduino/FirmataSysexCommand.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. namespace Iot.Device.Arduino { diff --git a/src/devices/Arduino/SupportedMode.cs b/src/devices/Arduino/SupportedMode.cs index 19f5392f07..5372b887be 100644 --- a/src/devices/Arduino/SupportedMode.cs +++ b/src/devices/Arduino/SupportedMode.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. namespace Iot.Device.Arduino { diff --git a/src/devices/Arduino/samples/Arduino.Monitor.cs b/src/devices/Arduino/samples/Arduino.Monitor.cs index 7620d43179..70ba8a2cfc 100644 --- a/src/devices/Arduino/samples/Arduino.Monitor.cs +++ b/src/devices/Arduino/samples/Arduino.Monitor.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Device.Gpio; diff --git a/src/devices/Arduino/samples/Arduino.sample.cs b/src/devices/Arduino/samples/Arduino.sample.cs index a485656f1e..9a0e5e08ef 100644 --- a/src/devices/Arduino/samples/Arduino.sample.cs +++ b/src/devices/Arduino/samples/Arduino.sample.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Device.Gpio; diff --git a/src/devices/Arduino/samples/DebugLogStream.cs b/src/devices/Arduino/samples/DebugLogStream.cs index 5cfb6ae0ad..f793e43f5d 100644 --- a/src/devices/Arduino/samples/DebugLogStream.cs +++ b/src/devices/Arduino/samples/DebugLogStream.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Arduino/tests/BasicFirmataIntegrationTests.cs b/src/devices/Arduino/tests/BasicFirmataIntegrationTests.cs index bd038ca210..a89f7fc5dd 100644 --- a/src/devices/Arduino/tests/BasicFirmataIntegrationTests.cs +++ b/src/devices/Arduino/tests/BasicFirmataIntegrationTests.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Bmxx80/ReadResult/Bme280ReadResult.cs b/src/devices/Bmxx80/ReadResult/Bme280ReadResult.cs index 89dca1051c..b70627ea0a 100644 --- a/src/devices/Bmxx80/ReadResult/Bme280ReadResult.cs +++ b/src/devices/Bmxx80/ReadResult/Bme280ReadResult.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using UnitsNet; diff --git a/src/devices/Bmxx80/ReadResult/Bme680ReadResult.cs b/src/devices/Bmxx80/ReadResult/Bme680ReadResult.cs index b9e26218bc..694d7b54ad 100644 --- a/src/devices/Bmxx80/ReadResult/Bme680ReadResult.cs +++ b/src/devices/Bmxx80/ReadResult/Bme680ReadResult.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using UnitsNet; diff --git a/src/devices/Bmxx80/ReadResult/Bmp280ReadResult.cs b/src/devices/Bmxx80/ReadResult/Bmp280ReadResult.cs index 69153533fa..db737987e6 100644 --- a/src/devices/Bmxx80/ReadResult/Bmp280ReadResult.cs +++ b/src/devices/Bmxx80/ReadResult/Bmp280ReadResult.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using UnitsNet; diff --git a/src/devices/Common/System/Device/Analog/AnalogController.cs b/src/devices/Common/System/Device/Analog/AnalogController.cs index 138fff83b4..e9cae7263d 100644 --- a/src/devices/Common/System/Device/Analog/AnalogController.cs +++ b/src/devices/Common/System/Device/Analog/AnalogController.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Device.Gpio; diff --git a/src/devices/Common/System/Device/Analog/AnalogInputPin.cs b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs index 2046fc1072..49bfba7b14 100644 --- a/src/devices/Common/System/Device/Analog/AnalogInputPin.cs +++ b/src/devices/Common/System/Device/Analog/AnalogInputPin.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Common/System/Device/Analog/TriggerReason.cs b/src/devices/Common/System/Device/Analog/TriggerReason.cs index 1abff1088b..cd5765d28a 100644 --- a/src/devices/Common/System/Device/Analog/TriggerReason.cs +++ b/src/devices/Common/System/Device/Analog/TriggerReason.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; diff --git a/src/devices/Common/System/Device/Analog/ValueChangedEventArgs.cs b/src/devices/Common/System/Device/Analog/ValueChangedEventArgs.cs index 218d1df69e..e84def3d44 100644 --- a/src/devices/Common/System/Device/Analog/ValueChangedEventArgs.cs +++ b/src/devices/Common/System/Device/Analog/ValueChangedEventArgs.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. namespace System.Device.Analog { diff --git a/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs b/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs index d4e8848e07..c0b50d6fce 100644 --- a/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs +++ b/src/devices/Common/System/Device/Analog/ValueChangedEventHandler.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; From 5367c9d3c2de3d4b2514610c4ca2a4440353e090 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Tue, 26 Jan 2021 17:15:10 +0100 Subject: [PATCH 23/56] Prevent Enable/Disable count mismatch --- src/devices/Arduino/ArduinoAnalogInputPin.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/devices/Arduino/ArduinoAnalogInputPin.cs b/src/devices/Arduino/ArduinoAnalogInputPin.cs index 8812d3d1db..ad00ada520 100644 --- a/src/devices/Arduino/ArduinoAnalogInputPin.cs +++ b/src/devices/Arduino/ArduinoAnalogInputPin.cs @@ -36,6 +36,11 @@ public override void EnableAnalogValueChangedEvent(GpioController? masterControl public override void DisableAnalogValueChangedEvent() { + if (_autoReportingReferenceCount == 0) + { + throw new InvalidOperationException("Attempt to disable event when no events are connected"); + } + _autoReportingReferenceCount -= 1; if (_autoReportingReferenceCount == 0) { From c40636d6fb351eda45a9e0a63aecd5152ae55330 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Tue, 26 Jan 2021 17:20:07 +0100 Subject: [PATCH 24/56] Change return type to immutable ReadOnlyCollection<> --- src/devices/Arduino/ArduinoAnalogController.cs | 5 +++-- src/devices/Arduino/ArduinoBoard.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/devices/Arduino/ArduinoAnalogController.cs b/src/devices/Arduino/ArduinoAnalogController.cs index 8499f6541b..c2697221fb 100644 --- a/src/devices/Arduino/ArduinoAnalogController.cs +++ b/src/devices/Arduino/ArduinoAnalogController.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Device.Analog; using System.Device.Gpio; using System.Linq; @@ -14,11 +15,11 @@ namespace Iot.Device.Arduino internal class ArduinoAnalogController : AnalogController { private readonly ArduinoBoard _board; - private readonly List _supportedPinConfigurations; + private readonly ReadOnlyCollection _supportedPinConfigurations; private readonly Dictionary _callbacks; public ArduinoAnalogController(ArduinoBoard board, - List supportedPinConfigurations, PinNumberingScheme scheme) + ReadOnlyCollection supportedPinConfigurations, PinNumberingScheme scheme) : base(scheme) { _board = board ?? throw new ArgumentNullException(nameof(board)); diff --git a/src/devices/Arduino/ArduinoBoard.cs b/src/devices/Arduino/ArduinoBoard.cs index 2d007ed06d..2e851a78bd 100644 --- a/src/devices/Arduino/ArduinoBoard.cs +++ b/src/devices/Arduino/ArduinoBoard.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Device.Analog; using System.Text; @@ -238,11 +239,11 @@ internal FirmataDevice Firmata /// /// Returns the list of capabilities per pin /// - public List SupportedPinConfigurations + public ReadOnlyCollection SupportedPinConfigurations { get { - return _supportedPinConfigurations; + return _supportedPinConfigurations.AsReadOnly(); } } From a9e86c39cbd8930da53bdfb1a8b141a68fd76d0a Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 30 Jan 2021 10:07:46 +0100 Subject: [PATCH 25/56] Add missing headers --- src/devices/Arduino/FirmataCommand.cs | 2 +- src/devices/Arduino/samples/CharacterDisplay.cs | 16 ++++------------ src/devices/Arduino/tests/FirmataTestFixture.cs | 5 ++++- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/devices/Arduino/FirmataCommand.cs b/src/devices/Arduino/FirmataCommand.cs index 197bace20f..77f54858ad 100644 --- a/src/devices/Arduino/FirmataCommand.cs +++ b/src/devices/Arduino/FirmataCommand.cs @@ -6,7 +6,7 @@ namespace Iot.Device.Arduino /// /// Primary firmata commands /// - public enum FirmataCommand : byte + internal enum FirmataCommand : byte { /// /// Digital pins have changed diff --git a/src/devices/Arduino/samples/CharacterDisplay.cs b/src/devices/Arduino/samples/CharacterDisplay.cs index 17aa4b46c1..744b3bfd3a 100644 --- a/src/devices/Arduino/samples/CharacterDisplay.cs +++ b/src/devices/Arduino/samples/CharacterDisplay.cs @@ -1,4 +1,7 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; using System.Collections.Generic; using System.Device.Gpio; using System.Globalization; @@ -20,17 +23,6 @@ public CharacterDisplay(ArduinoBoard board) _display.BlinkingCursorVisible = false; _display.UnderlineCursorVisible = false; _display.Clear(); - ////for (int i = 0; i < 16; i++) - ////{ - //// for (int j = 0; j < 16; j++) - //// { - //// Span b = new Span(new byte[] { (byte)(j + i * 16) }); - //// _display.Write(b); - //// } - - //// Console.ReadKey(); - //// _display.Clear(); - ////} _textController = new LcdConsole(_display, "SplC780", false); _textController.Clear(); diff --git a/src/devices/Arduino/tests/FirmataTestFixture.cs b/src/devices/Arduino/tests/FirmataTestFixture.cs index 7b72bc8058..6939a54ee7 100644 --- a/src/devices/Arduino/tests/FirmataTestFixture.cs +++ b/src/devices/Arduino/tests/FirmataTestFixture.cs @@ -1,4 +1,7 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; From 8e82b1a790d0694b70256986b1b6b40af45a552b Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Thu, 28 Jan 2021 13:37:42 +0100 Subject: [PATCH 26/56] Simplify statement Co-authored-by: Krzysztof Wicher --- src/devices/Arduino/ArduinoPwmChannel.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/devices/Arduino/ArduinoPwmChannel.cs b/src/devices/Arduino/ArduinoPwmChannel.cs index b6f8a61585..44f89e1c03 100644 --- a/src/devices/Arduino/ArduinoPwmChannel.cs +++ b/src/devices/Arduino/ArduinoPwmChannel.cs @@ -116,14 +116,7 @@ public override void Stop() private void Update() { - if (_enabled) - { - _board.Firmata.SetPwmChannel(_pin, _dutyCycle); - } - else - { - _board.Firmata.SetPwmChannel(_pin, 0); - } + _board.Firmata.SetPwmChannel(_pin, _enabled ? _dutyCycle : 0); } protected override void Dispose(bool disposing) From 42ec91f327f16af38fb50a711226e61539dd0564 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 30 Jan 2021 10:10:27 +0100 Subject: [PATCH 27/56] Add comment --- src/devices/Arduino/samples/DebugLogStream.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/devices/Arduino/samples/DebugLogStream.cs b/src/devices/Arduino/samples/DebugLogStream.cs index f793e43f5d..38f0fcfa68 100644 --- a/src/devices/Arduino/samples/DebugLogStream.cs +++ b/src/devices/Arduino/samples/DebugLogStream.cs @@ -7,9 +7,13 @@ using System.IO; using System.Text; +// Disable missing documentation warning in sample #pragma warning disable CS1591 namespace Arduino.Samples { + /// + /// This stream saves all outgoing messages so they can be later replayed. + /// public class DebugLogStream : Stream { private readonly Stream _streamImplementation; From 5592834ec2836c71ebf9617b67f6ba77fd255d58 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 30 Jan 2021 10:40:14 +0100 Subject: [PATCH 28/56] Improve documentation --- src/devices/Arduino/README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/devices/Arduino/README.md b/src/devices/Arduino/README.md index 3714dd0e09..f0b2478f06 100644 --- a/src/devices/Arduino/README.md +++ b/src/devices/Arduino/README.md @@ -17,21 +17,23 @@ You need to upload a special sketch to the Arduino. This sketch implements the " The binding requires Firmata Version 2.6, which is implemented i.e. by the ConfigurableFirmata project. - Open the Arduino IDE - Go to the library manager and check that you have the "ConfigurableFirmata" library installed -- Open "ConfigurableFirmata.ino" from the device binding folder or go to http://firmatabuilder.com/ to create your own custom firmata firmware. Make sure you have at least the features checked that you will need. +- Open "ConfigurableFirmata.ino" from the [device binding folder](./ConfigurableFirmata/ConfigurableFirmata.ino) or go to http://firmatabuilder.com/ to create your own custom firmata firmware. Make sure you have at least the features checked that you will need. - Upload this sketch to your Arduino. -After these steps, you can start coding with Iot.Devices.Arduino and make your Arduino do whatever you want, from blinking LEDS to your personal weather station. For usage and examples see the samples folder. Note that ConfigurableFirmata uses a default UART speed of 57600 baud. It is recommended to increase it to 115200, though. +After these steps, you can start coding with Iot.Devices.Arduino and make your Arduino do whatever you want, from blinking LEDS to your personal weather station. For usage and examples see the samples folder. Note that ConfigurableFirmata uses a default UART speed of 57600 baud. It is recommended to increase it to at least 115200, though. + +When the firmware starts, the on-board-LED flashes a few times, indicating the loaded firmware version (currently 2 + 11 blinks). After that, the board will enter idle state and wait for connections. + ### Advanced features -Some of the features require extended features on the Arduino firmware. These include SPI support and DHT sensor support. These features didn't make it -into the main branch yet, therefore these additional steps are required: +Some of the features of this binding require extended features on the Arduino firmware. These include SPI support and DHT sensor support. These features didn't make it into the main Firmata branch yet, therefore these additional steps are required: - Go to C:\users\\documents\arduino\libraries and delete the "ConfigurableFirmata" folder (save any work if you've changed anything there) - Replace it with a clone of https://github.com/pgrawehr/ConfigurableFirmata and switch to branch "develop". - Make sure you have the "DHT Sensor Library" from Adafruit installed (use the library manager for that). -- You can now enable the DHT and SPI features at the beginning of the ConfigurableFirmata.ino file. +- You can now enable the DHT and SPI features at the beginning of the ConfigurableFirmata.ino file. Because the new firmware will have additional features, it is recommended to use the .ino file from the examples folder of the repository. So the best start is to open the file that now lies in C:\users\\documents\arduino\libraries\ConfigurableFirmata\examples\ConfigurableFirmata\ConfigurableFirmata.ino. The file has some comments at the top to enable or disable certain modules. - Compile and re-upload the sketch. ## Usage -See the example for advanced usage instructions. +See the examples for some advanced use cases. Basic start: ``` @@ -69,7 +71,7 @@ Basic start: } ``` -On Windows, only one application can use the serial port at a time, therefore you'll get "permission denied" errors when you try to run your program while i.e. the Serial Port Monitor of the Arduino IDE is open when you start your program. On the other hand, trying to upload a new sketch while the program runs will also fail. +On Windows, only one application can use the serial port at a time, therefore you'll get "permission denied" errors when you try to run your program while i.e. the Serial Port Monitor of the Arduino IDE is open when you start your program. On the other hand, trying to upload a new sketch while the program runs will also fail. Note that the serial port monitor is of little use when the firmata firmware is loaded, since the communication protocol uses a binary format. Most of the output therefore will look like garbage. ## Known limitations From 9e93e49723b105a478ce1788cafb971b5f1b7bc7 Mon Sep 17 00:00:00 2001 From: Patrick Grawehr Date: Sat, 30 Jan 2021 11:01:03 +0100 Subject: [PATCH 29/56] Add drawings of example hardware --- src/devices/Arduino/samples/ArduinoSample.fzz | Bin 0 -> 22059 bytes .../Arduino/samples/ArduinoSample_Monitor.png | Bin 0 -> 256389 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/devices/Arduino/samples/ArduinoSample.fzz create mode 100644 src/devices/Arduino/samples/ArduinoSample_Monitor.png diff --git a/src/devices/Arduino/samples/ArduinoSample.fzz b/src/devices/Arduino/samples/ArduinoSample.fzz new file mode 100644 index 0000000000000000000000000000000000000000..29adbe06c242fb6587f85b01728019e011aa8253 GIT binary patch literal 22059 zcma&NQ+Q-ivp*b7Y}>YNPMl08wlSSdY}i53=!9H7u}w#(`e4(@G|M9cfc06JV@2mj_BTk5x3ZFCfs%X_jgI`<}|a#z@cM;U+ZTgbMEU`jCK~ehGQ&L z=UdbEgoesHQ>nR2R0h-yV+~e?ygU2oLw4E`^jGnSe8wkF%(0s z1wD7a*|xoVJJ((H3vMWd9W2$<2>->Ex?ijE_TQS`y}Cai*|X(ON>eO!8?(p!$Pw|N zrWbjqAns1Vdf8GGd^N>q!e)>eBbl*0pk>+dDJoqIYN&TrzmR&_s>kn%sXn)<-{ivY z$uW>NGy^BPo&aW}s9X13OvngzC>~P1-Ww+}zZPnvkFi8GcqfzkDTvGn$1aWG9G+iv zdKsox3f`M>$0a^gyVJkKqEGHUYxtTim6U9Vcp0a5zy!K4P1Ohp`gNp^bP%I4Yz*J7 z$$tcSYR|mB>{RsBbcH@G*4-hy)^B*vt7k22Z3+nN^Jgj+aJF4|t1r!5xptemoZD5& zu{_<>7>GB>s2)s2S$14Ixyb2_I9O>w@UV#Iv4}f-nNnt8c?;V7JC3!SBj=Y*^mg<^ z<;{Zi#QOPiEec1^`~7O`xMDZ8M*LGcwM8WEtv>WaZTVBG2N33&!ei>&(c0FkxUlw6 z`0UOhzhBc}%};n?5Z&qe@kjA|?W5(bcZz-n_#&Ji3k2UO%m>Jw4a?UFMtEnbd0aF| zs?|?r^H21A*KL0%CAsS^+GhN?mMdTwi7lwUY1i4siLTKI>&1#zm9ZK(Y)fi4dAyit z+O$l~^5>8Wyq7=Z<)Gy4F~a1UCWM=2+1uA!6I*E}+#JB2I<9Xxx?DJJmBL`rLx+DH zp0M1*K(l+;O_i~%+ON37%-xsr&Og-1+>~C+@J{2MM7k@<%%;1TsJ#^L(bH!lUqi4Y zbAYJMY`FvGY_i03|0=?^udmu@OGI+nUU79>EN_@_XG?SYG<9Bip=j^8N&gwx_U*3) z{^Hh*>3!SkvUAt0S7Wii76DD(X6Y96jVV-tCwkh^JVr(xM{5gE7A$zeFvKZi^c@z0JznYjyI{DYcG_%JLJ)M9|MWavu2o-S~KF2akV; zUp9mk@Sl(Y1j5F;ZyA@A;GHI2FV9;B207whs zTO484*G@0bGouj8tax>f-D?Xfq8LS7=0%p(C;*d2p*LYCl zvRu=(k#u&|6-YwerH=4n1n#?b8If;mYwhE%UGRqB0PVBU8d8 z?vhecV*B>9bC6D2gq)M%9`6BxoY!WFb#uxr(7Fel0lbV#6f;X5vBj$Bah`YYUco== zzn_`^K9t}jo)LEYdmaklf7wXIjqHT|!4SD`$pn4`zE==lP#ceS99COM%Y3^@`ZKT1 zrUn8)&MhM37Jeh33w$16nGg4T%wtz>nnb}C$*0H5Ca?Ca_q*WD!nxD2G4@vf-G$Nw z-%Knrc7GM<&%4M}&=@d}AtR$wijl}lt0dK+cE)~93)eLZt=foEGUl64cZi5<+Ie~Z zn=CoYcd?WZgFIJF(i_|HYO>&xunDdwM^Mb6I)|a7ICg~I0p5U&>>MLfNG<^(JSetR zG0`eB(B$jt`Wj{|R1CX8Du6?DkB1=7oj~99@3})a2Y=s2q*Aa4%;F=!nS5tLnufEZ zI>8Gp#kJhLc`m}XWTp^qX^CqQcgL_33`>us{xQ2d zVJmXL>>#w_xhq$4hge~BS{Dhe)g5PBXG}@M^iJ3U-sgq-`_WH+|57KdRkQobjjBxF zl&~hQi4g2FJYr*mIfJ%u$x&f|8rq`Zl&KWh$R1^z%5FGiM@HmtkkF4r)u|o~)8d*c z;k>yNxz15vPQb5I4PBa5cfG51*ak&87&Y`EYqKkDGFmjPaNwp-Fx})w>sTN7hPE)_ z8H-a+S~*~FQ;Y3)3x>571Ns6u9QTdDvGcyzWbvg|Z}ANwHTIe?c*g?s`7()Nq_&TG zzvaWMF7adv!`v~$U_T0h0sVBD?p;HN=bHp%h!yW@cNO}k-D|Kt1ssgPJ^edI$Y!mu zHot`wz)`Di>V%kB!@-OW@r=xe2#*MnPt=CrfP?lKA@80^P;2Z>1Ukp+g2dW?BYYa= zkt$TL4U?%u9Ragrmf2`nkEGO>ju@Tdl=>Qs6N-}g>jFwr+wf4fz4~1j>mcAHc~+G1 zXTniSX$BxoI|?W;SDhG&=#HJv$2SUcb2Q+1tfR+fB#_W+d5fmKw7?`c(AOH27z=tN z9ak8%$9KRaI}pXmF@`KCI*WxqVmMw^TKI2h0gk0gB%4{XLj(KBTu(2tkc?^y(l}#i zH2WPgG`xeYk`kLy{FE$hC?fmaB5^2$xzo1@@F0K^F^G&LM-Cmse%(H)>YOqqO9G8p zFV;5TugXp9fXhDKYMv{aUo|C*_mBM2pQj3~o3_D|KeN*N8y8L@Mbs(o@o7hG#}i^Kb9k0?#WT@!bE380?Wf7*RQY6 z&{V_&!+1ObPK-WCm-+6LOKW)#rmMUZHvrOv&EsFBL7{6|lxC}}6f@{1U+uYR(C|1X zkAIPa$a!XFySxY0Gla4$Ygxf&{|-z<#J3DeOf)`-)`f?sjNHf?{0})3BuDLI8#A8= z)fW$!u!g@hFZxHN4Zec^Oi3A?(z3G{`|+LL5cUEda>`et69g4Bc4 z2O!s6(oz0pbAMyJEj~Opb7>m<$_cIwL1`vej=|G+@+??CBQ19e6_k<1**)z!0@?4jMDK!-FEAcRI+VxN zAKjrtcW>$pr(M}CT-uujtUj)=ng=@d$A7d&Vebd3=mtA-u8x&-x9inD`t|qQz-Ja3 z)RkO!mzOUcoV!LkxO0aV9{itp1y0Fd@nyz@y{fZw1O}h7tLQGn3=b;}y?`_vGisYQ zcZ=$}rKCABXqsI)5`v@}DD3^v-)8 ze6ja_bQ0{+7u4OZ7FKeITO`Ym1Wqm7pjDU7f5&Td|5G`_!zG4R)hZ(J)+`n^(2v7O z;g{;>R))!GpcTxbC$_2~>xw?pmEX>l5vP`**cGB~w_R!~ILj3O7r)33nNKbj;lR|7{E(>tb_kk9h zh14I57oBQ^0JF&UE&oxFuzLxJ?Kx}ztTKHkbg(o{;tq`WY%fO)P+fT0 zS@nzp_ZkTgzJhc<^;H=8gAhB z$V}q3+xLt(r$9@i;MZvxhx5C#u+rdJiMz5UA+o~cHO&*)?Z0>p4(3Ntp6#1}Oap%3 z+G4j0|64MWC%sK#=a3UP6AdwuG$1 zG1_nRV8+a+`mlqHRn@?)hhqkt6xcZGQ77;MbvinH$%7ZTO0;3NNR0^g5dKvcy)BUpqx z#lNdp__tK{t!!S`!0Avjr>6q)YqoCBjB2UF5VcqD7B4{hiJCY`31 zRZ^(aGCjx*$Qu?r(AOL~gGNJOb^i+-C79ya8X8bwYz{MJjcmw$?hjMIwsewF){Uc~ z@JQE`^XgT1U_ zInSnkuK8KTreQ7TSw)3=1C<1RT)JZ~2prF;UbG5J7&jU1UaAU37&AVGt1k?qb`1Hs zwtYODOSbA$D0v-F{UU@J9OK#zBZWKsx-C&QE)CocDj%2D-3}682Ml=8P8v6E2$jA9 z8drosTGh`9m97ICr-W#K#c;KS_2W?sYzCE&S-fpXlub(mH~tyH(o5x+adnMQayy{0 zOb9bx(X|m~2*=`W?LRDQAQmkeFIq4HTT_tkNkW%mP2SW3s~`(meR|S18aH}?*2K70 z!wlg`zb%8L&|C4Otu(GT7b-0WG#meuqFGl6GdZTY9A*fgnqcYQO5iKCM`3-?F{kB6 zf1ttr!vEIUDp8)tT2ZfES`J(eUN83B0q*)S++~I)Tl|y9>x%<@t5$`cysQMa>L}6K zG29hlYDFeuF8~(-OO_2W7ki7=g;f`Oy#SD? zyyv2q;3_{1QA(76wniv$mV;ZdItX68yg*wM6c|e<+si#8Ky!i+VOC6YJc-31fcRM-4upFUuA;APab2DtFzRbRKd_xfi9Ka^f+DC6?#Q zhtJ2Hz-NWi^})*w$BZA#{kx^FXutj0%gq?dIZWnC$9pb|!r$Wk7PoVhFy7eF*Zgy2 z!`QHuC>nibKm~%OO%0CWyjkn1!hYRm8XNg-($s|&cg`5Wb3$^1_o^HVC}3;(Tf%5FxS6xmSlO9(i(-g6{74Yj(zHZ~zY7sbb$zf! zoQZkDowP=3+FFoMKh~e$5s820?^J}mkvyKGdAr9Q)DiblcibB6<`baA>GHon639ce znNZ9)=upQC*>4Um%#V2HKjoqP{(3+a2dPPlMz(r+tY@#@Gu1xcwbouE9c^& z5H|9VD!OdA7d#+^m%}p-0vcC#EYFm#sNjrR{$!4009FjnAH-R@=1=8c2MmYLo<6ys zk6t=Zdb&@udfr}2rfWVQFH2Tt1pHoBt{6YMS8hJ1bD!RV+=AXU#j<=qhj#7_qq6Ph zo&W0he7@XU-en|dJ8tYL&QGj>A1 zp3nvFN4X?pMiTQYZL3{JU`OPFwm9F^UGW_ypZ?osP9>pen_h)c_axDuVr!& zJzZjxpx{P~b$Td852~Cu<3_5MQ%^KK&_f;s+7jThcu8}^FF4C*Gkrog6jfudhtp1~ zwQ3E7_hUMa-@>V(Wn^*E8q+7%kRg%lF)y-ep$$JmxC#cW8ckkLIlubHa+=&(QR7PJ zQlbzfmaQpRqC9I+9LKqa4sZk=sY&79Z(tgGs|7c)5)0$2<`*1jL{we13%lW7BIqzR zqRCxC>Q#!-vX!4Hs8XBJ$W9O@LnV7{!q;)}GI>?vtb>t-rVHSsn^X$j)rwbIaAXVZ zn#QDy5PuEVS2*Uh7cK{Wn7RVb5!=o( zfw0uyV;_T^ToP>+)>$Tcj-C+vHIIaRBXMqrv>7wxj43gNw!-6!M`of6`gvm2(ABGhH)1o zr`7i|&Mn$>uGe~1>bh(?;Cp01%4|L?$;isPSEYu(Tt#rjLh~LS1(542+_JU)%hjBr zDArYe2#mj6sB2FB{{&CN)UoA}P&XeCmQT_mnagL);9)WEF{4ag@Tq9hD}`C|l<}jT zQ)8hfrv!zVvjlWdw(Pgw@GSnp-TXOcYE^W!qi>l?6Z~|pk@=B=iiR7CIA^M7duwc> zutHG}D^{#)l5HgVXBI>|AsBhiFQ|PBgT0$0nC3Hn;k3#{lisSjQqk8P@A7}H-#%Qi<(i(%Kc>@D{PL7WEl1^4Gy!oJp@C9j;>wg}58WjYoV zcN680ruR)xEz9_@daDY7cr4)qn$oRL_mKLAz^WV1^AwyTMlreiU^JkS#hPi=B=lk0 z)fCY%6gRR0%Z~(pfT#A91+QCgnCH(wwe*$ripuK7`vfIHM8*A%G)B2-<60T^@>w+6+)3p`f>xT3t~AYF%zS%`YKA;OW1{ub5_?|;h;6|VdFHfN4 zFBt&WE7GOUVS=G2Lg0CCo6s(a)rR{Ej8Nh*_Pj=t!cq)6coxba`RBbbMkp2ynC3%M z47h-2pyMt;$Jx~M!ybRXI%?rVsQmrs!CEYM98Q;}tJ6r)(}7FQyhyX~=J>~!Ktuxc zo`jE~l`Vd+X_|qU7y&XGc-M`DLvi-doGG}2kSZhaOm(fWJ|iqufYY2?)^myd-(j|Q zp;(3>-3CFreUu%jhS%KhJ)fVrdv>@tS8D1uX`u!Ok~FU`OdX%a>D`{KH4wXTV^h(t z_~_(h`{`!8t+K0dHt0_u<;X`IxJz9!B%1&_#_s8y)2dM1-0OIvcE%Z6&xA39W38%o z44LluXCaFju+j5ecwqvRQd?s0<;!&u5a6bPXAJeiVeJ;3E?GOL)Q@BV9!}fbTI=rR zo&#DA+m+M#2e4b{Hl)Sd-M_-?wl8C#$*|g+x`l@V$*(JS#3 zGx~sVkWn64-h^DE?;a!^U5La%Zjqx{j%ZgQo~8eQcGxj>(!gB(`#>H zT?GKgPnSk)=vB)${5I$)wXtL0H=eQ*UeTyg1zY=rR8Xo?!&!wR|DvMbAKxjcbRd)8 z2y23)*y{k*l*ne}93(z*9seR%w8F-GkiVyYo6ZF^S^)J@=SXxzwGw1;oM!l$gaG1h zP*L1dx=c%BBH{I^2Db<m=0109Vo!{Cj(*;z|5LbUm3x(FS>#^b;^E zEQDDD&SNobc*eLVGfTcIwMFnasRPY^jvjQD%I6f;k=K+{8#VpldhLlkn+TE=eK^AO z7*~b>P(ffpWv{}$UIDdQ-zDvp0yeM?s6pp_H?CKQYo)ivUQ}V=+%HRUQKIU#X`EA{ z+O6eAu<4bwZB&C}tyFZS4_KhZxBU)LB~5`H;UZ0CZ__v?Oy#d4y_r_sKo<~HKgUdE zZ^XU!3krE$+AA6C!Y2Jv6#~&FNk}eFET)aG@}3qvv}ul*s<)PVtq=-#5yaGcc7bQB z3Q;Ks1pH$yWRmkYwr!MxBP`}#%ZJj78IE?zo&(FGx2+e0Yow3CwGlk~q<%~46aMQP?HpPiBifYXrA$wS%Ve6?n-HQAEeN?gXV_E6oUdK`4{AXp!_G-D?!xdZTy3 z4e`aauuD46A5gyliy0mB2M>Iyi*?N$3Uij)E4mi~--$x&gcSnby6{6vl+3oi1C}-~ z<_~tjd`eU8)IUmF|f27hkthMx#ZvtuKVFU5~~)<2MDPU0O5aLDO2oW1H3b z_N3^F4pL-Hm^=@5zzhdXMU>!*3?hVE!4(xeh|R=iII-zxy%#L42KR;ll$;CqIvU1NNNG-y76i!q<>b(AyX0M-eCJBn|$#u4lfeFlso;Jd+3((G4p!soPbuup)>}F}#@|^E-#Flq5>})E@?o(R$*I&WwnWQz5CEGtWiM${9 z$~9j1#^fTg7P5;Zam;Ba6q<%7KjQu}#w<8I1a;C2U7YyU{Oxw7nYg)ONZLj*C&9r~ zA{(gOx&xX;u_dpOy^dut}f$ zZn@c{Eg*zIIo)o+L!IXWK}M|_8Z!r_>DD$`JV0hk) zSO0&JM@pLHv4h1P!OzK3M_Nt4+Wn|~7-qk`}kfySQ&FgUEwfm24r)+5MV3wb(}k zD5;!Y0%ZQh_DiAJiI_F-OrJQp{SOeGn6{;!%k8Qm!Tw+DxS68faRMpEcTv6Si0^85 zWLf-%?HQcVVv*LgY_j#j(fkyWd-6Ei8*Bv6Ly{P7a#Ypgs}Z-wDu~)TV53FPaotWx zF1D7=h62JQZl;ZmEli~ajRpXd{xE>_7`z*Qqz1YHqFPmHfpv#n$MU1eSRrV>&Nne+ z=5S49M?fx@ia?eqr1->bFe!mzp0^Kgz?ZeJ5(^aYSJ8-JXLApGHGyj$LN(0Z{t9~q!yT2yh9k!{=3=cqq`52Lpm-2 z2Rs;_*&z`xVKiV87W8*a#HR%GTKcJoS-fJzhr}?h?cpbofZ~IgW^U#M2eOJSkv1lw z`+{c9#1$A_Al0)U{Ggp9dg7mqRfbZb(1wx3FI2MK!e#M|X3NrF>ic9y`bj)1VuRcc z!s1S~w@>%sg5>p`UpMOuuzsLh%@0eIV^%?{Qyt1w(g)XVHMA``4KLT*lx* zNM@@zuG;c=p^2H)?C#ySR3mPZ*!>4)V7en)vXSAnJ4(+~e7Ifg4l*Xs93Y-`TWzzl z(upD{7ktU4OGC4`~s|!u?2BYz)TqpA*qYb*X(MCw# zshmQ~V90)bS0{E@^kcdoH4wFfu<;>2G}P^D-g%XOEx`_b4Iv3XV9Z3yx7VO2IlbX$dmK) zvA`_K4Gi<)xw(@haeBky{Lq@O4QG+<|DEb15ca(I)Z`yeA-#bHxQxk=^Zz&Zc+WY9G5w=FfGnD`{3!3ZciwsLlAbH zc+2$T$6i`s7f~9NUH5N!WgCHOS^RC)4yDI3(=7LZcGu(YD}{lhX@S|78c#-!cc_lW zgN1ESID=Tz%VYKvK_hdI#84dnpe3-oapd;!?qSvXnVgA*C)z!wQ{RiItj8Ze@6FnY zNOR*|&*@S1#`xk6s=Uh2=jyM=ywEmJ48kkWyMuM%`c?~$!C~UiR!luyT!=s;3WP!q zyoeFyj4$Kn2ENr^EZ=l8gc$vh-c!y7Y)tTtWWIekG|a#dtDcQFiuB<{1cdM*!CR%H zsU1srF&rw$3gx;p{>1r0l`LYlvq`1UI#J!Gtaq~K^9#=f_UT^WP<$(Y`%R2>A9(fQ zPu{XaXaa@EZlVL=ukfdjgh*Al_s*c#{vzI(q23$yh%o4*0q)}cLN*)v$J|f&yv@T* zWI{+&ak)i~6*s0-MaTtcRNH)ElP}V%C{+&XDnJF5xWl!tK|#9CP)NDU-G$HZyjf&f@ zosupCv^quQx=eVygH6eX_NkS1o!k^{rAp#u&aeKdUOUW)9iXjf4}$~)!z zPu_GZm;F@zl^47gw>mz4wneeOUtLVl>>a)LYXwgiF(jeZpc0wBCw^!+Kj6Zh-F`O- zF`2U9PwE}<{7LPxDPBhfQ77l{WWnzzD)X<%lv8#8oxDG1UQR0&*HVVrMIL>knWz+D zvGg5ML;px_-(yb*4LRxWV$FE#TQZIMG?x+CaG!%C6ZE=&`Ct zkp+)MR_hRygddg3hU?8t6DPf;BthvhKi$XWQW%BoS8tVf*q!iwK&}57ly6#;rX4x~ zM(IOlw4%t4vFxS9>ais)#;SSxfm`^MA&j*rIT z#957|{tGr!TCiOKE9SI?U%gI26e+yWB6vrgL(b)exO=^3RLDGMGmf}tzG*4E!?oht z6ILKf1E#gSh+Bffu9_h>I3KtaE*2$e!euh$N*<>b^PCQPX%hxPFRjhs)<(poG1tDS zdF(Kk37CE2zh`GPn5Vv=W|(xO`YXYvRQn3c-TBN@4x$3p*r8#gyhpBS(Sla8J!3Z@ zI1iW-Zu~>gHAQvZ;t5zCpK0JN;c$=EfXNA?b*d zC~|>lPf1t{oW5Gh0=fpYL{ydtPCB@aUE~TFx1XVEFwS|m=rRYMDr^v(LZF~={g`L3 z^T@%(-aj4^@ryOt@Q0Lf92(C#R;Eg;P0PS%(Ut9Ji0Iz-I~TX6OZJ|*zj^-| z`F!iSm@)Zyez|l?W<*(A70?X+iK2(^;5K4MVsM;6Kl$doi4y+ab7+pvuhe>=&zHoq z@V^~MZ7)y)2_ht)aa3Us@=xkrfm>QNB(lV@3b8Oj8HpGN2C#pZtJebkdyg{(6H?KF;F6 zGCOIX_YPs}-mcWWD5d)JlR+NoAhDOFpj4q5C;$v-N@f3nW<5u)0hBATqPwSVnEzh> zTm~C?oES1F(l+E>t3p5OX$vni5W0q&7>dMUkXjw{{YRm~^xsezf#6B*g31)+Xbk*R zn){m~$fKjXsv;c~kDJPJ;&Dt!N-j(^J#>#!n;59!^(BQ zb!v6`=6WxVEWtd;E*=-b*AzPr);!q(kk?BIwXJsUNDj?{S}OpviNt~H>OFXE zg9169)XmEYAs=Hba>|uBMm^)Jk>v?T&efV;=�fgZ<|@6?u*jR}+ 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 F{{yEb1vLNw literal 0 HcmV?d00001 diff --git a/src/devices/Arduino/samples/ArduinoSample_Monitor.png b/src/devices/Arduino/samples/ArduinoSample_Monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..26ae7110a268b9a2a4c70a1ff10da4deaa7a8973 GIT binary patch literal 256389 zcmd43byQUU+cr9ifC7q0NP|dscL)q!4lsmtBi&t!ba#U^4Ba`PAYB5I64Ko;G`t(X z-``o!I?uD-Ki;#>I%hL1hFzb$@6UBz_q}(>dqt_|XoP4W5a_v#G*}q~dXfhMp?rAy z7}#SW$=Cz@d1fcA=?DVR1wQ;fqS##w2Z6{zGGI|vxAfh4S0`8dCV^zq{8r=XB_)b3X=HQW) z(o>)y5NHO|9aEp^pBqi%JzP!n&((X6fdg8ie@mE_d^mvl?*U}sK-#;14!ArVK>GK< zQ+G`Aoqx)+QUU5T^Zajl`qyF$NWF&tl=qwPnDkx!zvb-%S69FK+aM76!6V~0e{TdL ze>k5%90p6-M*@ArV{?By4m3!^_n%IY#ti@4B%o3Mo)7Y4{PMT?KnDc8L_9PL1gd`d z&wWAf@ctHq?129G#B^p`$*^3ju?LT|7!7tsy_`uOjQT;ZfrTCl2?ZnP0s ze#23n#+2)GQ02nkyVfH&u>2Yo*&1x%QhQytyfrVbvQh5x5+V`ucCTs!9%c;@rN~*l zS{@~tAR^%hn?L2gee(sQmczg3q8A%e~p4p3u!ZzKP z`R5|u(qQ6&o9>hADY$Q1XUpsTflyhn5}7q^|A^+WilMEQMTJny)Hlbgs0^ey>-iP? zDOQY>2$YhU=q$^!658Gxt;G5BA?d_~cHfN?eGT%Q3b>cKx{geNXKhI9b;)(cZ5)`B zZN>9)a9`dP6+~|R&?xQ?v=oB7DPeefaBOGXfVD1ny7DAriI&Wj={;-&DMGQgS_~|8 zu8Odpz z`(zpJ(*js%=wMDRqKlR+?fdl-DfJjM_OO-dMbBHX=K&ZZs@P!wFTKHM9N!t)Yx&J) z@n^=rMGcXaol~U#;i?02bg_`?yiEl9_JJIjkjCMo-_Ucw;MX=zTvJKO3W4EHMky*} z4T%#0Y>`Hej)PZGFIw_TXUOKTP~Pb0lCXsWb?P36`57Wn+KK?Xe9MC zQJj>XoD3o;1IFA0iX=rkUD(%>1)ZaN_)(AaOTKX`g8ahy#7d^3Hag;;&r^ zCP!c>>|oAV7ktTh_9X|Hh&Fl}I8cQ8cKXTVfe(#Su>mSnK|pqp;VMRXRX ze#Q24QCu~;8HJ|M6ltl*nEQwd84+l)L5M1Nd6|J3NHZtCLaN7V_@_xM$l1)=-YUz8 zE_9ZMJ(pw(if3o_SMx;%-I*)6w;3GC0aN=4;B4M?gL9U%brM`>r6hGop-^AlQ zQ^_z`)dq>nz<|>3f~Hn$tvZ>G`{`G+^x)>80yL76xdm$#cf@q0C&}B;92m@_#`