Skip to content

Commit 22d420c

Browse files
pgrawehrkrwq
andauthored
Arduino/Firmata device support (dotnet#1039)
* Basic firmata features Works with currently released version of ConfigurableFirmata. Should work with StandardFirmata as well. * Add analog controller base classes again Rewrote to new suggested concept with AnalogInputPin class instead of "int pinNumber" everywhere. * Sample depends on other binding Will re-add once the OpenHardwareMonitor PR is merged * Support non-continuous analog pin numbers * Remove obsolete solution configurations * Provide overload that takes a serial port name * Remove dead code * Add documentation * Update DHT protocol format. Now closer to the proposed standard at firmata/protocol@9389c89 * These should not be enabled by default * Extend Readme * Add missing copyright headers * Revert "Sample depends on other binding" This reverts commit 0f17a6c. * Merge changes from dev branch * Not yet part of project * Nullability and other fixes * Remove unused project from solution - causes weird conflicts * Ignore tests instead of failing them if no hardware is available * Documentation update * Remove stuff not yet supported from template * Fix indentation * 2-line license headers * Prevent Enable/Disable count mismatch * Change return type to immutable ReadOnlyCollection<> * Add missing headers * Simplify statement Co-authored-by: Krzysztof Wicher <[email protected]> * Add comment * Improve documentation * Add drawings of example hardware * More documentation updates * Review comments addressed * Analog interface improved (add properties, use UnitsNet, etc) * Use automatic properties * Simplify close/dispose * Add internal check * Change event type to existing type * Use correct argument * Review comments all over the place * Remove class only used for very specific testing * Update src/devices/Arduino/samples/Arduino.sample.cs Co-authored-by: Krzysztof Wicher <[email protected]> * More feedback addressed * Add a few tests * Remove overloads not currently meaningful * Automatically initialize device if it isn't yet. * Automatically call Initialize() whenever needed ... but make it protected. * Use a lock to make sure Initialize is thread safe * Use a common method for lock handling and low-level transport Except for a few special commands, a single SendCommand or SendCommandAndWait now handles the low-level communication. * Remove unused variable, fix spelling * Fix build problem with NotNullWhen attribute * Really make that Initialize call protected * Some documentation update from feedback * Update src/devices/Arduino/ArduinoGpioControllerDriver.cs Co-authored-by: Krzysztof Wicher <[email protected]> * Address review comments * A few more comments * Minor doc cleanup * Simplify init logic Co-authored-by: Krzysztof Wicher <[email protected]>
1 parent 9b99835 commit 22d420c

40 files changed

+4865
-8
lines changed

src/devices/Arduino/Arduino.csproj

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net5.0;netcoreapp2.1</TargetFrameworks>
5+
<EnableDefaultItems>false</EnableDefaultItems>
6+
<RootNamespace>Iot.Device.Arduino</RootNamespace>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<Compile Include="*.cs" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
15+
<ProjectReference Include="..\Common\CommonHelpers.csproj" />
16+
<PackageReference Include="System.IO.Ports" Version="$(SystemIOPortsPackageVersion)" />
17+
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
18+
</ItemGroup>
19+
<ItemGroup>
20+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
21+
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
22+
</AssemblyAttribute>
23+
</ItemGroup>
24+
</Project>

src/devices/Arduino/Arduino.sln

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.29905.134
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino", "Arduino.csproj", "{0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}"
7+
EndProject
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Device.Gpio", "..\..\System.Device.Gpio\System.Device.Gpio.csproj", "{0B90F9D4-7353-4172-A317-714471A06781}"
9+
EndProject
10+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.sample", "samples\Arduino.sample.csproj", "{2670F7BF-A7C8-49EB-9A99-1719A90D0C67}"
11+
EndProject
12+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bmxx80", "..\Bmxx80\Bmxx80.csproj", "{EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}"
13+
EndProject
14+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mcp3xxx", "..\Mcp3xxx\Mcp3xxx.csproj", "{26911D2A-E303-4846-96A1-5ABC92F54445}"
15+
EndProject
16+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CharacterLcd", "..\CharacterLcd\CharacterLcd.csproj", "{D47A6627-4041-4CEA-8903-A6C67C6993CF}"
17+
EndProject
18+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "..\Common\CommonHelpers.csproj", "{CD593083-8E94-4B60-86BF-DF3FB7873893}"
19+
EndProject
20+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9E5A25ED-9839-4C1A-9B27-993437D1CB31}"
21+
EndProject
22+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Monitor", "samples\Arduino.Monitor.csproj", "{23B4B60C-9594-42BB-9D25-C54983B0F809}"
23+
EndProject
24+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{CA26B999-4C0E-4E82-A46E-A68AC1B85C10}"
25+
EndProject
26+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Tests", "tests\Arduino.Tests.csproj", "{273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}"
27+
EndProject
28+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HardwareMonitor", "..\HardwareMonitor\HardwareMonitor.csproj", "{D1C467D8-E6E3-4811-826B-216DCB80A16E}"
29+
EndProject
30+
Global
31+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
32+
Debug|Any CPU = Debug|Any CPU
33+
Release|Any CPU = Release|Any CPU
34+
EndGlobalSection
35+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
36+
{0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37+
{0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
38+
{0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
39+
{0D3AA0FD-4834-4AB1-AD2A-BB12180B61B4}.Release|Any CPU.Build.0 = Release|Any CPU
40+
{0B90F9D4-7353-4172-A317-714471A06781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41+
{0B90F9D4-7353-4172-A317-714471A06781}.Debug|Any CPU.Build.0 = Debug|Any CPU
42+
{0B90F9D4-7353-4172-A317-714471A06781}.Release|Any CPU.ActiveCfg = Release|Any CPU
43+
{0B90F9D4-7353-4172-A317-714471A06781}.Release|Any CPU.Build.0 = Release|Any CPU
44+
{2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45+
{2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Debug|Any CPU.Build.0 = Debug|Any CPU
46+
{2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Release|Any CPU.ActiveCfg = Release|Any CPU
47+
{2670F7BF-A7C8-49EB-9A99-1719A90D0C67}.Release|Any CPU.Build.0 = Release|Any CPU
48+
{EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49+
{EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Debug|Any CPU.Build.0 = Debug|Any CPU
50+
{EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Release|Any CPU.ActiveCfg = Release|Any CPU
51+
{EEEB0FB8-E1ED-4970-BDF6-DA3D5E2ED074}.Release|Any CPU.Build.0 = Release|Any CPU
52+
{26911D2A-E303-4846-96A1-5ABC92F54445}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53+
{26911D2A-E303-4846-96A1-5ABC92F54445}.Debug|Any CPU.Build.0 = Debug|Any CPU
54+
{26911D2A-E303-4846-96A1-5ABC92F54445}.Release|Any CPU.ActiveCfg = Release|Any CPU
55+
{26911D2A-E303-4846-96A1-5ABC92F54445}.Release|Any CPU.Build.0 = Release|Any CPU
56+
{D47A6627-4041-4CEA-8903-A6C67C6993CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57+
{D47A6627-4041-4CEA-8903-A6C67C6993CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
58+
{D47A6627-4041-4CEA-8903-A6C67C6993CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
59+
{D47A6627-4041-4CEA-8903-A6C67C6993CF}.Release|Any CPU.Build.0 = Release|Any CPU
60+
{CD593083-8E94-4B60-86BF-DF3FB7873893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61+
{CD593083-8E94-4B60-86BF-DF3FB7873893}.Debug|Any CPU.Build.0 = Debug|Any CPU
62+
{CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.ActiveCfg = Release|Any CPU
63+
{CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.Build.0 = Release|Any CPU
64+
{23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
65+
{23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.Build.0 = Debug|Any CPU
66+
{23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.ActiveCfg = Release|Any CPU
67+
{23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.Build.0 = Release|Any CPU
68+
{273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69+
{273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}.Debug|Any CPU.Build.0 = Debug|Any CPU
70+
{273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}.Release|Any CPU.ActiveCfg = Release|Any CPU
71+
{273C9233-8A7D-4A0B-8DB2-FB7F56FA727D}.Release|Any CPU.Build.0 = Release|Any CPU
72+
{D1C467D8-E6E3-4811-826B-216DCB80A16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
73+
{D1C467D8-E6E3-4811-826B-216DCB80A16E}.Debug|Any CPU.Build.0 = Debug|Any CPU
74+
{D1C467D8-E6E3-4811-826B-216DCB80A16E}.Release|Any CPU.ActiveCfg = Release|Any CPU
75+
{D1C467D8-E6E3-4811-826B-216DCB80A16E}.Release|Any CPU.Build.0 = Release|Any CPU
76+
EndGlobalSection
77+
GlobalSection(SolutionProperties) = preSolution
78+
HideSolutionNode = FALSE
79+
EndGlobalSection
80+
GlobalSection(NestedProjects) = preSolution
81+
{2670F7BF-A7C8-49EB-9A99-1719A90D0C67} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31}
82+
{23B4B60C-9594-42BB-9D25-C54983B0F809} = {9E5A25ED-9839-4C1A-9B27-993437D1CB31}
83+
{273C9233-8A7D-4A0B-8DB2-FB7F56FA727D} = {CA26B999-4C0E-4E82-A46E-A68AC1B85C10}
84+
EndGlobalSection
85+
GlobalSection(ExtensibilityGlobals) = postSolution
86+
SolutionGuid = {47BF9684-1876-4AC1-8C87-04B8C7FA6C3A}
87+
EndGlobalSection
88+
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Collections.ObjectModel;
7+
using System.Device.Analog;
8+
using System.Device.Gpio;
9+
using System.Linq;
10+
using System.Text;
11+
using System.Threading;
12+
using UnitsNet;
13+
14+
namespace Iot.Device.Arduino
15+
{
16+
internal class ArduinoAnalogController : AnalogController
17+
{
18+
private readonly ArduinoBoard _board;
19+
private readonly IReadOnlyList<SupportedPinConfiguration> _supportedPinConfigurations;
20+
21+
public ArduinoAnalogController(ArduinoBoard board,
22+
IReadOnlyList<SupportedPinConfiguration> supportedPinConfigurations, PinNumberingScheme scheme)
23+
: base(scheme)
24+
{
25+
_board = board ?? throw new ArgumentNullException(nameof(board));
26+
_supportedPinConfigurations = supportedPinConfigurations ?? throw new ArgumentNullException(nameof(supportedPinConfigurations));
27+
PinCount = _supportedPinConfigurations.Count;
28+
29+
// Note: While the Arduino does have an external analog input reference pin, Firmata doesn't allow configuring it.
30+
VoltageReference = ElectricPotential.FromVolts(5.0);
31+
}
32+
33+
public override int PinCount
34+
{
35+
get;
36+
}
37+
38+
public override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber)
39+
{
40+
int numberAnalogPinsFound = 0;
41+
for (int i = 0; i < _supportedPinConfigurations.Count; i++)
42+
{
43+
if (_supportedPinConfigurations[i].PinModes.Contains(SupportedMode.AnalogInput))
44+
{
45+
numberAnalogPinsFound++;
46+
if (pinNumber == i)
47+
{
48+
return numberAnalogPinsFound - 1;
49+
}
50+
}
51+
}
52+
53+
throw new InvalidOperationException($"Pin {pinNumber} is not a valid analog input pin.");
54+
}
55+
56+
public override int ConvertLogicalNumberingSchemeToPinNumber(int logicalPinNumber)
57+
{
58+
int numberAnalogPinsFound = 0;
59+
for (int i = 0; i < _supportedPinConfigurations.Count; i++)
60+
{
61+
if (_supportedPinConfigurations[i].PinModes.Contains(SupportedMode.AnalogInput))
62+
{
63+
numberAnalogPinsFound++;
64+
if (logicalPinNumber == numberAnalogPinsFound - 1)
65+
{
66+
return i;
67+
}
68+
}
69+
}
70+
71+
throw new InvalidOperationException($"Pin A{logicalPinNumber} does not exist");
72+
}
73+
74+
public override bool SupportsAnalogInput(int pinNumber)
75+
{
76+
return _supportedPinConfigurations[pinNumber].PinModes.Contains(SupportedMode.AnalogInput);
77+
}
78+
79+
protected override AnalogInputPin OpenPinCore(int pinNumber)
80+
{
81+
// This method is called with the logical pin numbering (input pin A0 is 0, A1 is 1, etc)
82+
// but the SetPinMode method operates on the global numbers
83+
int fullNumber = ConvertLogicalNumberingSchemeToPinNumber(pinNumber);
84+
_board.Firmata.SetPinMode(fullNumber, SupportedMode.AnalogInput);
85+
_board.Firmata.EnableAnalogReporting(pinNumber);
86+
return new ArduinoAnalogInputPin(_board, this, _supportedPinConfigurations[fullNumber], pinNumber, VoltageReference);
87+
}
88+
89+
public override void ClosePin(AnalogInputPin pin)
90+
{
91+
_board.Firmata.DisableAnalogReporting(pin.PinNumber);
92+
}
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Device.Analog;
7+
using System.Device.Gpio;
8+
using System.Text;
9+
using UnitsNet;
10+
11+
namespace Iot.Device.Arduino
12+
{
13+
internal class ArduinoAnalogInputPin : AnalogInputPin
14+
{
15+
private readonly SupportedPinConfiguration _configuration;
16+
private int _autoReportingReferenceCount;
17+
private ArduinoBoard _board;
18+
19+
public ArduinoAnalogInputPin(ArduinoBoard board, AnalogController controller, SupportedPinConfiguration configuration,
20+
int pinNumber, ElectricPotential voltageReference)
21+
: base(controller, pinNumber, voltageReference)
22+
{
23+
_board = board;
24+
_configuration = configuration;
25+
}
26+
27+
public override void EnableAnalogValueChangedEvent(GpioController? masterController, int masterPin)
28+
{
29+
// The pin is already open, so analog reporting is enabled, we just need to forward it.
30+
if (_autoReportingReferenceCount == 0)
31+
{
32+
_board.Firmata.AnalogPinValueUpdated += FirmataOnAnalogPinValueUpdated;
33+
}
34+
35+
_autoReportingReferenceCount += 1;
36+
}
37+
38+
public override void DisableAnalogValueChangedEvent()
39+
{
40+
if (_autoReportingReferenceCount == 0)
41+
{
42+
throw new InvalidOperationException("Attempt to disable event when no events are connected");
43+
}
44+
45+
_autoReportingReferenceCount -= 1;
46+
if (_autoReportingReferenceCount == 0)
47+
{
48+
_board.Firmata.AnalogPinValueUpdated -= FirmataOnAnalogPinValueUpdated;
49+
}
50+
}
51+
52+
private void FirmataOnAnalogPinValueUpdated(int pin, uint rawvalue)
53+
{
54+
if (_autoReportingReferenceCount > 0)
55+
{
56+
int physicalPin = Controller.ConvertLogicalNumberingSchemeToPinNumber(pin);
57+
var voltage = ConvertToVoltage(rawvalue);
58+
var message = new ValueChangedEventArgs(rawvalue, voltage, physicalPin, TriggerReason.Timed);
59+
FireValueChanged(message);
60+
}
61+
}
62+
63+
public override ElectricPotential MinVoltage => ElectricPotential.Zero;
64+
65+
/// <summary>
66+
/// The arduino would theoretically allow for an external analog reference, but firmata currently doesn't support that.
67+
/// </summary>
68+
public override ElectricPotential MaxVoltage => ElectricPotential.FromVolts(5);
69+
70+
/// <summary>
71+
/// Similar here: Some boards support more than 10 bit resolution, but we'd have to extend the firmware for that.
72+
/// </summary>
73+
public override int AdcResolutionBits => 10;
74+
75+
public override uint ReadRaw()
76+
{
77+
return _board.Firmata.GetAnalogRawValue(PinNumber);
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)