Skip to content

Commit 55a4ec0

Browse files
authored
"Arduino C# Compiler" (dotnet#1785)
This "compiler" (actually a metadata converter and code analyzer) can take a compiled Iot program written using the Arduino binding and flash it as a standalone application to an ESP32 or similar 32-bit microcontroller. Test and debug your application on the PC using the Arduino binding, and when everything works as expected, just flash it to the ESP32 and run it without the PC. There are still a lot of open ends, but several more-or-less complex programs already work (see the WeatherStation example). Further documentation (both on usage as well as on internals) is still to be written. See Readme for further instructions.
1 parent 9cc3577 commit 55a4ec0

File tree

185 files changed

+27047
-537
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

185 files changed

+27047
-537
lines changed

azure-pipelines.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ stages:
4747
displayName: 'Execute Markdownlint'
4848
condition: eq(variables['build.reason'], 'PullRequest')
4949
50+
# Hint: Add /maxCpuCount:1 to disable parellelization in build. Ensures all tests results are printed to the console
5051
- script: build.cmd -ci
5152
-configuration $(BuildConfiguration)
5253
-prepareMachine
@@ -89,6 +90,28 @@ stages:
8990
artifact: config
9091
condition: eq(variables['BuildConfiguration'], 'Release')
9192

93+
- job: Windows_ArduinoIntegration
94+
displayName: Arduino Integration Tests
95+
timeoutInMinutes: 120
96+
pool:
97+
vmImage: windows-2022
98+
99+
strategy:
100+
matrix:
101+
Build_Release:
102+
BuildConfiguration: Release
103+
Build_Debug:
104+
BuildConfiguration: Debug
105+
106+
steps:
107+
- script: build.cmd -ci
108+
-configuration $(BuildConfiguration)
109+
-prepareMachine
110+
displayName: Build Iot
111+
112+
- script: eng\ArduinoCsCI.cmd $(UserProfile) $(BuildConfiguration)
113+
displayName: Build and run Arduino Integration Tests
114+
92115
- job: Linux
93116
displayName: Linux Build
94117
container: LinuxContainer

eng/ArduinoCsCI.cmd

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
echo on
2+
3+
REM First argument is the path to the user home directory (typically C:\Users\VssAdministrator)
4+
REM Second argument is either "Debug" or "Release"
5+
if %1!==! goto :usage
6+
7+
REM Defines the revision to check out in the ExtendedConfigurableFirmata repo
8+
set FIRMATA_SIMULATOR_CHECKOUT_REVISION=122ed75de8cb53e0dbd96cde71a7c9e21fe4be89
9+
set RUN_COMPILER_TESTS=FALSE
10+
11+
choco install -y --no-progress arduino-cli
12+
arduino-cli lib install "DHT sensor library"
13+
arduino-cli lib install "Servo"
14+
15+
arduino-cli config init
16+
arduino-cli config add board_manager.additional_urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
17+
arduino-cli core update-index
18+
19+
set ArduinoRootDir=%1\Documents\Arduino
20+
set acspath=%~dp0\..\tools\ArduinoCsCompiler\Frontend\bin\%2\net6.0\acs.exe
21+
22+
git clone https://github.com/firmata/ConfigurableFirmata %ArduinoRootDir%\libraries\ConfigurableFirmata
23+
git clone https://github.com/pgrawehr/ExtendedConfigurableFirmata %ArduinoRootDir%\ExtendedConfigurableFirmata
24+
arduino-cli core install esp32:esp32
25+
26+
REM Check whether any compiler files have changed - if so, enable the (long running) compiler tests
27+
git diff --name-status origin/main | find /C /I "tools/ArduinoCsCompiler"
28+
REM Find returns 1 when the string was NOT found, we want to set the variable to true when it does find something
29+
if %errorlevel%==0 set RUN_COMPILER_TESTS=TRUE
30+
31+
dir %ArduinoRootDir%
32+
33+
%acspath% --help
34+
35+
rem Write runtime data to ExtendedConfigurableFirmata directory, before building
36+
%acspath% prepare
37+
38+
rem bring msbuild into the path
39+
call "c:\program files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
40+
41+
pushd %ArduinoRootDir%\ExtendedConfigurableFirmata
42+
43+
REM checkout to the currently fixed branch
44+
git checkout -B main %FIRMATA_SIMULATOR_CHECKOUT_REVISION%
45+
46+
dir
47+
REM First build the code for the ESP32 (this just verifies that the code builds, it does no run-time checks at all)
48+
arduino-cli compile --fqbn esp32:esp32:esp32:PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none ./ExtendedConfigurableFirmata.ino --warnings default
49+
if errorlevel 1 goto error
50+
51+
REM then build the simulator code as Visual Studio C++ project
52+
msbuild /p:Configuration=%2 ExtendedConfigurableFirmataSim\ExtendedConfigurableFirmataSim.vcxproj
53+
if errorlevel 1 goto error
54+
55+
REM Start the simulator asynchronously
56+
start ExtendedConfigurableFirmataSim\%2\ExtendedConfigurableFirmataSim.exe
57+
58+
popd
59+
pushd %~dp0\..\tools\ArduinoCsCompiler\
60+
61+
REM This somehow gets disabled
62+
echo on
63+
REM and finally run the Arduino tests, now including the ones skipped previously. Set verbosity to normal to see
64+
REM information about all tests being executed (as this test run can take 30 mins or more)
65+
echo Starting basic arduino tests
66+
dotnet test -c %2 --no-build --no-restore --filter feature=firmata -l "console;verbosity=normal" -maxcpucount:1
67+
68+
echo on
69+
if %RUN_COMPILER_TESTS%==TRUE (
70+
echo Starting extended Arduino compiler tests
71+
dotnet test -c %2 --no-build --no-restore --filter feature=firmata-compiler -l "console;verbosity=normal" -maxcpucount:1
72+
)
73+
74+
if errorlevel 1 goto error
75+
76+
popd
77+
taskkill /im ExtendedConfigurableFirmataSim.exe /f
78+
79+
Echo All done!
80+
exit /b 0
81+
82+
:error
83+
echo Tests failed. Error code %errorlevel%
84+
exit /b 1
85+
:usage
86+
87+
echo Usage: ArduinoCsCI.cmd [path-to-home-directory] [Configuration]
88+

src/devices/Arduino/Arduino.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<TargetFrameworks>$(DefaultBindingTfms)</TargetFrameworks>
44
<EnableDefaultItems>false</EnableDefaultItems>
55
<RootNamespace>Iot.Device.Arduino</RootNamespace>
6+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
67
</PropertyGroup>
78
<ItemGroup>
89
<Compile Include="*.cs" />

src/devices/Arduino/Arduino.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CharacterLcd", "..\Characte
1717
EndProject
1818
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "..\Common\Common.csproj", "{CD593083-8E94-4B60-86BF-DF3FB7873893}"
1919
EndProject
20+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CpuTemperature", "..\CpuTemperature\CpuTemperature.csproj", "{F3950513-A564-462F-887B-E00972D20FAD}"
21+
EndProject
2022
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9E5A25ED-9839-4C1A-9B27-993437D1CB31}"
2123
EndProject
2224
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arduino.Monitor", "samples\Arduino.Monitor.csproj", "{23B4B60C-9594-42BB-9D25-C54983B0F809}"
@@ -65,6 +67,10 @@ Global
6567
{CD593083-8E94-4B60-86BF-DF3FB7873893}.Debug|Any CPU.Build.0 = Debug|Any CPU
6668
{CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.ActiveCfg = Release|Any CPU
6769
{CD593083-8E94-4B60-86BF-DF3FB7873893}.Release|Any CPU.Build.0 = Release|Any CPU
70+
{F3950513-A564-462F-887B-E00972D20FAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
71+
{F3950513-A564-462F-887B-E00972D20FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
72+
{F3950513-A564-462F-887B-E00972D20FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
73+
{F3950513-A564-462F-887B-E00972D20FAD}.Release|Any CPU.Build.0 = Release|Any CPU
6874
{23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
6975
{23B4B60C-9594-42BB-9D25-C54983B0F809}.Debug|Any CPU.Build.0 = Debug|Any CPU
7076
{23B4B60C-9594-42BB-9D25-C54983B0F809}.Release|Any CPU.ActiveCfg = Release|Any CPU

src/devices/Arduino/ArduinoAnalogInputPin.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private void FirmataOnAnalogPinValueUpdated(int pin, uint rawvalue)
5454
if (_autoReportingReferenceCount > 0)
5555
{
5656
int physicalPin = Controller.ConvertLogicalNumberingSchemeToPinNumber(pin);
57-
var voltage = ConvertToVoltage(rawvalue);
57+
ElectricPotential voltage = ConvertToVoltage(rawvalue);
5858
var message = new ValueChangedEventArgs(rawvalue, voltage, physicalPin, TriggerReason.Timed);
5959
FireValueChanged(message);
6060
}

src/devices/Arduino/ArduinoBoard.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ public static bool TryFindBoard(IEnumerable<string> comPorts, IEnumerable<int> b
151151
#endif
152152
out ArduinoBoard? board)
153153
{
154-
foreach (var port in comPorts)
154+
foreach (string port in comPorts)
155155
{
156-
foreach (var baud in baudRates)
156+
foreach (int baud in baudRates)
157157
{
158158
ArduinoBoard? b = null;
159159
try
@@ -179,7 +179,7 @@ public static bool TryFindBoard(IEnumerable<string> comPorts, IEnumerable<int> b
179179
/// This requires an arduino with an ethernet shield or an ESP32 with enabled WIFI support.
180180
/// </summary>
181181
/// <param name="boardAddress">The IP address of the board</param>
182-
/// <param name="port">The network port to use</param>
182+
/// <param name="port">The network port to use. The default port is 27016</param>
183183
/// <param name="board">Returns the board if successful</param>
184184
/// <returns>True on success, false otherwise</returns>
185185
public static bool TryConnectToNetworkedBoard(IPAddress boardAddress, int port,
@@ -302,7 +302,7 @@ public void AddCommandHandler<T>(T newCommandHandler)
302302
if (newCommandHandler.HandlesMode != null)
303303
{
304304
// If we already know the mode, replace its configuration with the new one (typically, this will only update the name)
305-
var m = _knownSupportedModes.FirstOrDefault(x => x.Value == newCommandHandler.HandlesMode.Value);
305+
SupportedMode? m = _knownSupportedModes.FirstOrDefault(x => x.Value == newCommandHandler.HandlesMode.Value);
306306
if (m != null)
307307
{
308308
_knownSupportedModes.Remove(m);
@@ -336,7 +336,7 @@ public void AddCommandHandler<T>(T newCommandHandler)
336336
public T? GetCommandHandler<T>()
337337
where T : ExtendedCommandHandler
338338
{
339-
foreach (var cmd in _extendedCommandHandlers)
339+
foreach (ExtendedCommandHandler cmd in _extendedCommandHandlers)
340340
{
341341
if (cmd.GetType() == typeof(T))
342342
{
@@ -358,7 +358,7 @@ public override PinUsage DetermineCurrentPinUsage(int pinNumber)
358358
_commandHandlersLock.EnterReadLock();
359359
try
360360
{
361-
var m = _knownSupportedModes.FirstOrDefault(x => x.Value == mode);
361+
SupportedMode? m = _knownSupportedModes.FirstOrDefault(x => x.Value == mode);
362362
if (m == null)
363363
{
364364
return PinUsage.Unknown;
@@ -430,7 +430,7 @@ protected override void Initialize()
430430

431431
Logger.LogInformation($"Firmata version on board is {_firmataVersion}.");
432432

433-
_firmwareVersion = _firmata.QueryFirmwareVersion(out var firmwareName);
433+
_firmwareVersion = _firmata.QueryFirmwareVersion(out string firmwareName);
434434
_firmwareName = firmwareName;
435435

436436
Logger.LogInformation($"Firmware version on board is {_firmwareVersion}");
@@ -440,14 +440,14 @@ protected override void Initialize()
440440
_supportedPinConfigurations = _firmata.PinConfigurations.AsReadOnly();
441441

442442
Logger.LogInformation("Device capabilities: ");
443-
foreach (var pin in _supportedPinConfigurations)
443+
foreach (SupportedPinConfiguration pin in _supportedPinConfigurations)
444444
{
445445
Logger.LogInformation(pin.ToString());
446446
}
447447

448448
_firmata.EnableDigitalReporting();
449449

450-
foreach (var e in _extendedCommandHandlers)
450+
foreach (ExtendedCommandHandler e in _extendedCommandHandlers)
451451
{
452452
e.Registered(_firmata, this);
453453
e.OnConnected();
@@ -575,7 +575,7 @@ private void FirmataOnError(string message, Exception? exception)
575575
_commandHandlersLock.EnterReadLock();
576576
try
577577
{
578-
foreach (var handler in _extendedCommandHandlers)
578+
foreach (ExtendedCommandHandler handler in _extendedCommandHandlers)
579579
{
580580
handler.OnErrorMessage(message, exception);
581581
}
@@ -613,7 +613,7 @@ public SupportedMode GetPinMode(int pinNumber)
613613
_commandHandlersLock.EnterReadLock();
614614
try
615615
{
616-
var m = _knownSupportedModes.FirstOrDefault(x => x.Value == mode);
616+
SupportedMode? m = _knownSupportedModes.FirstOrDefault(x => x.Value == mode);
617617
if (m == null)
618618
{
619619
return new SupportedMode(mode, $"Unknown mode {mode}");
@@ -729,7 +729,7 @@ public override int[] GetDefaultPinAssignmentForI2c(int busId)
729729
throw new NotSupportedException("Only bus number 0 is currently supported");
730730
}
731731

732-
var pins = _supportedPinConfigurations.Where(x => x.PinModes.Contains(SupportedMode.I2c)).Select(y => y.Pin);
732+
IEnumerable<int> pins = _supportedPinConfigurations.Where(x => x.PinModes.Contains(SupportedMode.I2c)).Select(y => y.Pin);
733733

734734
return pins.ToArray();
735735
}
@@ -742,7 +742,7 @@ public override int[] GetDefaultPinAssignmentForSpi(SpiConnectionSettings connec
742742
throw new NotSupportedException("Only bus number 0 is currently supported");
743743
}
744744

745-
var pins = _supportedPinConfigurations.Where(x => x.PinModes.Contains(SupportedMode.Spi)).Select(y => y.Pin);
745+
IEnumerable<int> pins = _supportedPinConfigurations.Where(x => x.PinModes.Contains(SupportedMode.Spi)).Select(y => y.Pin);
746746

747747
return pins.ToArray();
748748
}
@@ -785,7 +785,7 @@ public void SoftwareReset()
785785
/// </summary>
786786
protected override void Dispose(bool disposing)
787787
{
788-
foreach (var e in _extendedCommandHandlers)
788+
foreach (ExtendedCommandHandler e in _extendedCommandHandlers)
789789
{
790790
try
791791
{
@@ -872,7 +872,7 @@ public List<TimeSpan> Ping(int number)
872872
try
873873
{
874874
_firmata.QueryFirmwareVersion(out _);
875-
var elapsed = sw.Elapsed;
875+
TimeSpan elapsed = sw.Elapsed;
876876
ret.Add(elapsed);
877877
_logger.LogInformation($"Round trip time: {elapsed.TotalMilliseconds}ms");
878878
}

src/devices/Arduino/ArduinoGpioControllerDriver.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ protected override void SetPinMode(int pinNumber, PinMode mode)
8282
throw new NotSupportedException($"Mode {mode} is not supported for this operation");
8383
}
8484

85-
var pinConfig = _supportedPinConfigurations.FirstOrDefault(x => x.Pin == pinNumber);
85+
SupportedPinConfiguration? pinConfig = _supportedPinConfigurations.FirstOrDefault(x => x.Pin == pinNumber);
8686
if (pinConfig == null || !pinConfig.PinModes.Contains(firmataMode))
8787
{
8888
_logger.LogError($"Mode {mode} is not supported on Pin {pinNumber}");
@@ -98,12 +98,12 @@ protected override void SetPinMode(int pinNumber, PinMode mode)
9898

9999
protected override PinMode GetPinMode(int pinNumber)
100100
{
101-
if (_pinModes.TryGetValue(pinNumber, out var existingValue))
101+
if (_pinModes.TryGetValue(pinNumber, out PinMode existingValue))
102102
{
103103
return existingValue;
104104
}
105105

106-
var mode = _device.GetPinMode(pinNumber);
106+
byte mode = _device.GetPinMode(pinNumber);
107107

108108
PinMode ret;
109109
if (mode == SupportedMode.DigitalOutput.Value)
@@ -146,7 +146,7 @@ protected override bool IsPinModeSupported(int pinNumber, PinMode mode)
146146
return false;
147147
}
148148

149-
var pinConfig = _supportedPinConfigurations.FirstOrDefault(x => x.Pin == pinNumber);
149+
SupportedPinConfiguration? pinConfig = _supportedPinConfigurations.FirstOrDefault(x => x.Pin == pinNumber);
150150
return pinConfig != null && pinConfig.PinModes.Contains(firmataMode);
151151
}
152152

@@ -220,14 +220,14 @@ protected override void AddCallbackForPinValueChangedEvent(int pinNumber, PinEve
220220
{
221221
lock (_callbackContainersLock)
222222
{
223-
if (_callbackContainers.TryGetValue(pinNumber, out var cb))
223+
if (_callbackContainers.TryGetValue(pinNumber, out CallbackContainer? cb))
224224
{
225225
cb.EventTypes = cb.EventTypes | eventTypes;
226226
cb.OnPinChanged += callback;
227227
}
228228
else
229229
{
230-
var cb2 = new CallbackContainer(pinNumber, eventTypes);
230+
CallbackContainer cb2 = new CallbackContainer(pinNumber, eventTypes);
231231
cb2.OnPinChanged += callback;
232232
_callbackContainers.Add(pinNumber, cb2);
233233
}
@@ -238,7 +238,7 @@ protected override void RemoveCallbackForPinValueChangedEvent(int pinNumber, Pin
238238
{
239239
lock (_callbackContainersLock)
240240
{
241-
if (_callbackContainers.TryGetValue(pinNumber, out var cb))
241+
if (_callbackContainers.TryGetValue(pinNumber, out CallbackContainer? cb))
242242
{
243243
cb.OnPinChanged -= callback;
244244
if (cb.NoEventsConnected)
@@ -319,7 +319,7 @@ public bool NoEventsConnected
319319
public void FireOnPinChanged(PinEventTypes eventType)
320320
{
321321
// Copy event instance, prevents problems when elements are added or removed at the same time
322-
var threadSafeCopy = OnPinChanged;
322+
PinChangeEventHandler? threadSafeCopy = OnPinChanged;
323323
threadSafeCopy?.Invoke(PinNumber, new PinValueChangedEventArgs(eventType, PinNumber));
324324
}
325325
}

src/devices/Arduino/ArduinoPwmChannel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public ArduinoPwmChannel(ArduinoBoard board,
4141

4242
Channel = channel;
4343
_pin = channel;
44-
var caps = board.SupportedPinConfigurations.FirstOrDefault(x => x.Pin == _pin);
44+
SupportedPinConfiguration? caps = board.SupportedPinConfigurations.FirstOrDefault(x => x.Pin == _pin);
4545
if (caps == null || !caps.PinModes.Contains(SupportedMode.Pwm))
4646
{
4747
throw new NotSupportedException($"Pin {_pin} does not support PWM");

src/devices/Arduino/BlockingConcurrentBag.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public bool TryRemoveElement(Func<T?, bool> predicate, TimeSpan timeout, out T?
7171
{
7272
// Cannot use FirstOrDefault here, because we need to be able to distinguish between
7373
// finding nothing and finding an empty (null, default) element
74-
for (var index = 0; index < _container.Count; index++)
74+
for (int index = 0; index < _container.Count; index++)
7575
{
7676
T? elem = _container[index];
7777
if (predicate(elem))

src/devices/Arduino/DhtSensor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public DhtSensor()
3535
/// <returns>True on success, false otherwise</returns>
3636
public bool TryReadDht(int pinNumber, int dhtType, out Temperature temperature, out RelativeHumidity humidity)
3737
{
38-
var pinConfiguration = Board.SupportedPinConfigurations;
38+
IReadOnlyList<SupportedPinConfiguration> pinConfiguration = Board.SupportedPinConfigurations;
3939
if (!pinConfiguration[pinNumber].PinModes.Contains(SupportedMode.Dht))
4040
{
4141
temperature = default;
@@ -56,7 +56,7 @@ private bool TryReadDhtInternal(int pinNumber, int dhtType, out Temperature temp
5656
dhtCommandSequence.WriteByte((byte)dhtType);
5757
dhtCommandSequence.WriteByte((byte)pinNumber);
5858
dhtCommandSequence.WriteByte((byte)FirmataCommand.END_SYSEX);
59-
var reply = SendCommandAndWait(dhtCommandSequence);
59+
byte[] reply = SendCommandAndWait(dhtCommandSequence);
6060

6161
// Command, pin number and 2x2 bytes data (+ END_SYSEX byte)
6262
if (reply.Length < 7)

0 commit comments

Comments
 (0)