Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arduino/Firmata device support #1039

Merged
merged 56 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
b7bf82f
Basic firmata features
pgrawehr Oct 30, 2020
76ae7b7
Add analog controller base classes again
pgrawehr Oct 31, 2020
6c6b038
Sample depends on other binding
pgrawehr Oct 31, 2020
5a1c017
Support non-continuous analog pin numbers
pgrawehr Oct 31, 2020
7b5e5c7
Remove obsolete solution configurations
pgrawehr Oct 31, 2020
3500082
Provide overload that takes a serial port name
pgrawehr Oct 31, 2020
7f517fc
Remove dead code
pgrawehr Oct 31, 2020
a67114d
Add documentation
pgrawehr Oct 31, 2020
bdd284f
Update DHT protocol format.
pgrawehr Nov 1, 2020
45976f6
These should not be enabled by default
pgrawehr Nov 1, 2020
d45a6d4
Extend Readme
pgrawehr Nov 1, 2020
80e5775
Add missing copyright headers
pgrawehr Nov 1, 2020
7aecd85
Revert "Sample depends on other binding"
pgrawehr Dec 18, 2020
885010e
Merge changes from dev branch
pgrawehr Dec 18, 2020
84f3ec1
Not yet part of project
pgrawehr Dec 18, 2020
e40340b
Nullability and other fixes
pgrawehr Dec 18, 2020
fde0791
Remove unused project from solution - causes weird conflicts
pgrawehr Dec 18, 2020
f2bfaac
Ignore tests instead of failing them if no hardware is available
pgrawehr Jan 22, 2021
f50bfc1
Documentation update
pgrawehr Jan 22, 2021
39e1a5a
Remove stuff not yet supported from template
pgrawehr Jan 22, 2021
597f332
Fix indentation
pgrawehr Jan 26, 2021
f008ff3
2-line license headers
pgrawehr Jan 26, 2021
5367c9d
Prevent Enable/Disable count mismatch
pgrawehr Jan 26, 2021
c40636d
Change return type to immutable ReadOnlyCollection<>
pgrawehr Jan 26, 2021
a9e86c3
Add missing headers
pgrawehr Jan 30, 2021
8e82b1a
Simplify statement
pgrawehr Jan 28, 2021
42ec91f
Add comment
pgrawehr Jan 30, 2021
5592834
Improve documentation
pgrawehr Jan 30, 2021
9e93e49
Add drawings of example hardware
pgrawehr Jan 30, 2021
981a081
More documentation updates
pgrawehr Jan 30, 2021
0cfbb65
Review comments addressed
pgrawehr Feb 3, 2021
3a6fbfa
Analog interface improved (add properties, use UnitsNet, etc)
pgrawehr Feb 3, 2021
c1b4675
Use automatic properties
pgrawehr Feb 3, 2021
f62c701
Simplify close/dispose
pgrawehr Feb 3, 2021
8b80004
Add internal check
pgrawehr Feb 3, 2021
21d95f6
Change event type to existing type
pgrawehr Feb 3, 2021
9a18c11
Use correct argument
pgrawehr Feb 3, 2021
cae9a89
Review comments all over the place
pgrawehr Feb 5, 2021
0059d40
Remove class only used for very specific testing
pgrawehr Feb 5, 2021
fe5dc63
Update src/devices/Arduino/samples/Arduino.sample.cs
pgrawehr Feb 5, 2021
355fe6d
More feedback addressed
pgrawehr Feb 5, 2021
9662766
Add a few tests
pgrawehr Feb 5, 2021
4123a77
Remove overloads not currently meaningful
pgrawehr Feb 5, 2021
8062ef9
Automatically initialize device if it isn't yet.
pgrawehr Feb 11, 2021
26a4282
Automatically call Initialize() whenever needed
pgrawehr Feb 18, 2021
498961f
Use a lock to make sure Initialize is thread safe
pgrawehr Feb 18, 2021
3228dda
Use a common method for lock handling and low-level transport
pgrawehr Feb 20, 2021
700b7a3
Remove unused variable, fix spelling
pgrawehr Feb 21, 2021
fd1c144
Fix build problem with NotNullWhen attribute
pgrawehr Feb 21, 2021
736a038
Really make that Initialize call protected
pgrawehr Feb 22, 2021
abc7171
Some documentation update from feedback
pgrawehr Feb 22, 2021
d5234e8
Update src/devices/Arduino/ArduinoGpioControllerDriver.cs
pgrawehr Feb 22, 2021
176694e
Address review comments
pgrawehr Feb 22, 2021
22b35ec
A few more comments
pgrawehr Feb 22, 2021
82d1321
Minor doc cleanup
pgrawehr Feb 24, 2021
d1654c8
Simplify init logic
pgrawehr Feb 25, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/devices/Arduino/Arduino.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<EnableDefaultItems>false</EnableDefaultItems>
<RootNamespace>Iot.Device.Arduino</RootNamespace>
</PropertyGroup>

<ItemGroup>
<Compile Include="*.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
<ProjectReference Include="..\Common\CommonHelpers.csproj" />
<PackageReference Include="System.IO.Ports" Version="$(SystemIOPortsPackageVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
88 changes: 88 additions & 0 deletions src/devices/Arduino/Arduino.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@

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\CommonHelpers.csproj", "{CD593083-8E94-4B60-86BF-DF3FB7873893}"
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
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
Release|Any CPU = 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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B90F9D4-7353-4172-A317-714471A06781}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26911D2A-E303-4846-96A1-5ABC92F54445}.Release|Any CPU.Build.0 = 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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D47A6627-4041-4CEA-8903-A6C67C6993CF}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD593083-8E94-4B60-86BF-DF3FB7873893}.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
{23B4B60C-9594-42BB-9D25-C54983B0F809}.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
EndGlobalSection
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}
EndGlobalSection
EndGlobal
101 changes: 101 additions & 0 deletions src/devices/Arduino/ArduinoAnalogController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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.Collections.ObjectModel;
using System.Device.Analog;
using System.Device.Gpio;
using System.Linq;
using System.Text;
using System.Threading;

namespace Iot.Device.Arduino
{
internal class ArduinoAnalogController : AnalogController
{
private readonly ArduinoBoard _board;
private readonly IReadOnlyList<SupportedPinConfiguration> _supportedPinConfigurations;
private readonly Dictionary<int, ValueChangedEventHandler> _callbacks;

public ArduinoAnalogController(ArduinoBoard board,
IReadOnlyList<SupportedPinConfiguration> supportedPinConfigurations, PinNumberingScheme scheme)
: base(scheme)
{
_board = board ?? throw new ArgumentNullException(nameof(board));
_supportedPinConfigurations = supportedPinConfigurations ?? throw new ArgumentNullException(nameof(supportedPinConfigurations));
_callbacks = new Dictionary<int, ValueChangedEventHandler>();
PinCount = _supportedPinConfigurations.Count;

// Note: While the Arduino does have an external analog input reference pin, Firmata doesn't allow configuring it.
VoltageReference = 5.0;
}

public override int PinCount
{
get;
}

public override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber)
{
int numberAnalogPinsFound = 0;
for (int i = 0; i < _supportedPinConfigurations.Count; i++)
{
if (_supportedPinConfigurations[i].PinModes.Contains(SupportedMode.AnalogInput))
{
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)
{
int numberAnalogPinsFound = 0;
for (int i = 0; i < _supportedPinConfigurations.Count; i++)
{
if (_supportedPinConfigurations[i].PinModes.Contains(SupportedMode.AnalogInput))
{
numberAnalogPinsFound++;
if (logicalPinNumber == numberAnalogPinsFound - 1)
{
return i;
}
}
}

throw new InvalidOperationException($"Pin A{logicalPinNumber} is not existing");
}

public override bool SupportsAnalogInput(int pinNumber)
{
return _supportedPinConfigurations[pinNumber].PinModes.Contains(SupportedMode.AnalogInput);
}

protected override AnalogInputPin OpenPinCore(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.AnalogInput);
_board.Firmata.EnableAnalogReporting(pinNumber);
return new ArduinoAnalogInputPin(_board, this, _supportedPinConfigurations[fullNumber], pinNumber, VoltageReference);
}

public override void ClosePin(AnalogInputPin pin)
{
_board.Firmata.DisableAnalogReporting(pin.PinNumber);
}

protected override void Dispose(bool disposing)
{
_callbacks.Clear();
base.Dispose(disposing);
}
}
}
80 changes: 80 additions & 0 deletions src/devices/Arduino/ArduinoAnalogInputPin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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.Analog;
using System.Device.Gpio;
using System.Text;
using UnitsNet;

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)
Copy link
Member

@krwq krwq Feb 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused on what this does and when it's used. Why there is no event as part of this class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This event is the link to the underlying transport connection and therefore binding-specific. The public event that you're looking for is in the base class. Forwarding takes place further down in FirmataOnAnalogPinValueUpdated. The base class has no direct knowledge about the source of the event.

{
// 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()
{
if (_autoReportingReferenceCount == 0)
{
throw new InvalidOperationException("Attempt to disable event when no events are connected");
}

_autoReportingReferenceCount -= 1;
if (_autoReportingReferenceCount == 0)
{
_board.Firmata.AnalogPinValueUpdated -= FirmataOnAnalogPinValueUpdated;
}
}

private void FirmataOnAnalogPinValueUpdated(int pin, uint rawvalue)
{
if (_autoReportingReferenceCount > 0)
{
int physicalPin = Controller.ConvertLogicalNumberingSchemeToPinNumber(pin);
var voltage = ConvertToVoltage(rawvalue);
var message = new ValueChangedEventArgs(rawvalue, voltage, physicalPin, TriggerReason.Timed);
FireValueChanged(message);
}
}

public override ElectricPotential MinVoltage => ElectricPotential.Zero;

/// <summary>
/// The arduino would theoretically allow for an external analog reference, but firmata currently doesn't support that.
/// </summary>
public override ElectricPotential MaxVoltage => ElectricPotential.FromVolts(5);

/// <summary>
/// Similar here: Some boards support more than 10 bit resolution, but we'd have to extend the firmware for that.
/// </summary>
public override int AdcResolutionBits => 10;

public override uint ReadRaw()
{
return _board.Firmata.GetAnalogRawValue(PinNumber);
}
}
}
Loading