diff --git a/.github/workflows/develop-ci.yml b/.github/workflows/develop-ci.yml
index ea9b70b2c..60977bc40 100644
--- a/.github/workflows/develop-ci.yml
+++ b/.github/workflows/develop-ci.yml
@@ -15,57 +15,57 @@ jobs:
steps:
- name: Checkout Meadow.Logging
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: WildernessLabs/Meadow.Logging
path: Meadow.Logging
ref: develop
- name: Checkout Meadow.Units
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: WildernessLabs/Meadow.Units
path: Meadow.Units
ref: develop
- name: Checkout Meadow.Contracts
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: WildernessLabs/Meadow.Contracts
path: Meadow.Contracts
ref: develop
- name: Checkout Meadow.Modbus
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: WildernessLabs/Meadow.Modbus
path: Meadow.Modbus
ref: develop
- name: Checkout Meadow.Foundation
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: WildernessLabs/Meadow.Foundation
path: Meadow.Foundation
ref: develop
- name: Checkout MQTTnet fork
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: WildernessLabs/MQTTnet
path: MQTTnet
ref: develop
- name: Checkout Meadow.Core
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
path: Meadow.Core
- name: Setup .NET SDK
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version:
- 7.0.x
+ 8.0.x
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml
index 22dc899e8..c31329aad 100644
--- a/.github/workflows/main-ci.yml
+++ b/.github/workflows/main-ci.yml
@@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout Meadow.Core
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
path: Meadow.Core
diff --git a/.github/workflows/nuget-core.yml b/.github/workflows/nuget-core.yml
index 4796be9b2..5f71a4b7c 100644
--- a/.github/workflows/nuget-core.yml
+++ b/.github/workflows/nuget-core.yml
@@ -10,7 +10,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Set VERSION variable from tag
run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV
diff --git a/.github/workflows/nuget-f7.yml b/.github/workflows/nuget-f7.yml
index 84be7c61e..af7f50f24 100644
--- a/.github/workflows/nuget-f7.yml
+++ b/.github/workflows/nuget-f7.yml
@@ -9,7 +9,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Set VERSION variable from tag
run: echo "VERSION=0.98.0" >> $GITHUB_ENV
diff --git a/.github/workflows/nuget-linux.yml b/.github/workflows/nuget-linux.yml
index eacd7ea27..acfb28033 100644
--- a/.github/workflows/nuget-linux.yml
+++ b/.github/workflows/nuget-linux.yml
@@ -9,7 +9,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Set VERSION variable from tag
run: echo "VERSION=0.98.0" >> $GITHUB_ENV
diff --git a/.github/workflows/nuget-windows.yml b/.github/workflows/nuget-windows.yml
index dbde59d34..6ce436040 100644
--- a/.github/workflows/nuget-windows.yml
+++ b/.github/workflows/nuget-windows.yml
@@ -9,7 +9,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Set VERSION variable from tag
run: echo "VERSION=0.98.0" >> $GITHUB_ENV
diff --git a/source/Meadow.Core.sln b/source/Meadow.Core.sln
index 3c07e9e00..11a3311af 100644
--- a/source/Meadow.Core.sln
+++ b/source/Meadow.Core.sln
@@ -15,6 +15,11 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Meadow.Simulation", "implementations\simulation\Meadow.Simulation\Meadow.Simulation.csproj", "{5543D2D5-FA80-43CE-8844-40510A7DBED6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "linux", "linux", "{B187C117-A16E-4A75-B862-3CA7DCA464B8}"
+ ProjectSection(SolutionItems) = preProject
+ implementations\linux\beaglebone.md = implementations\linux\beaglebone.md
+ implementations\linux\raspberrypi.md = implementations\linux\raspberrypi.md
+ implementations\linux\README.md = implementations\linux\README.md
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Meadow.Linux", "implementations\linux\Meadow.Linux\Meadow.Linux.csproj", "{4DEA563C-50EE-4729-ACA4-C77A251655BC}"
EndProject
diff --git a/source/Meadow.Core/Cloud/MeadowCloudConnectionService.cs b/source/Meadow.Core/Cloud/MeadowCloudConnectionService.cs
index b0d0b4816..92edc6a14 100644
--- a/source/Meadow.Core/Cloud/MeadowCloudConnectionService.cs
+++ b/source/Meadow.Core/Cloud/MeadowCloudConnectionService.cs
@@ -150,11 +150,6 @@ private void LogAndRaiseOnErrorOccurredEvent(string message, Exception? ex = nul
var raisedException = new MeadowCloudException(message, ex);
- if (ex != null && ex.HResult == -76)
- {
- ReportFatalErrorToReliabilityService(raisedException);
- }
-
ErrorOccurred?.Invoke(this, raisedException);
}
diff --git a/source/Meadow.Core/Cloud/MeadowCloudUpdateService.cs b/source/Meadow.Core/Cloud/MeadowCloudUpdateService.cs
index 4818f2a0c..6b9d9e07d 100644
--- a/source/Meadow.Core/Cloud/MeadowCloudUpdateService.cs
+++ b/source/Meadow.Core/Cloud/MeadowCloudUpdateService.cs
@@ -202,6 +202,7 @@ private async Task DownloadProc(UpdateMessage message)
if (!string.IsNullOrEmpty(message.OsVersion)
&& Resolver.Device.PlatformOS.OSVersion != message.OsVersion)
{
+ Resolver.Log.Trace($"This OTA requires an OS update.");
destination = message.MpakWithOsDownloadUrl;
}
@@ -230,31 +231,46 @@ private async Task DownloadProc(UpdateMessage message)
// Note: this is infrequently called, so we don't want to follow the advice of "use one instance for all calls"
using (var httpClient = new HttpClient())
{
- if (_connectionService.Settings.UseAuthentication)
+ using (var request = new HttpRequestMessage(HttpMethod.Get, destination))
{
- httpClient.DefaultRequestHeaders.Authorization = _connectionService.CreateAuthenticationHeaderValue();
- }
+ if (_connectionService.Settings.UseAuthentication)
+ {
+ request.Headers.Authorization = _connectionService.CreateAuthenticationHeaderValue();
+ }
- // Configure the HTTP range header to indicate resumption of partial download, starting from
- // the 'totalBytesDownloaded' byte position and extending to the end of the content.
- httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(totalBytesDownloaded, null);
+ // Configure the HTTP range header to indicate resumption of partial download, starting from
+ // the 'totalBytesDownloaded' byte position and extending to the end of the content.
+ request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(totalBytesDownloaded, null);
- using (var stream = await httpClient.GetStreamAsync(destination))
- {
- using (var fileStream = Store.GetUpdateFileStream(message.ID))
+ // Get the actual update file size, which might be larger than the expected
+ // if this OTA requires an OS update
+ var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
+ var contentLength = response.Content.Headers.ContentLength;
+ if (contentLength.HasValue && retryCount == 0)
{
- byte[] buffer = new byte[1024 * 64]; // TODO: make this configurable/platform dependent
- int bytesRead;
+ // Content-Length indicates the remaining bytes to download, as the Range header may change after retries.
+ // Then, to determine the total file size, the Content-Length from the first download attempt is used.
+ message.FileSize = contentLength.Value;
+ Resolver.Log.Trace($"Starting download... File size: {message.FileSize.ToString("N0")} bytes.");
+ }
- while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
+ using (var stream = await response.Content.ReadAsStreamAsync())
+ {
+ using (var fileStream = Store.GetUpdateFileStream(message.ID))
{
- await fileStream.WriteAsync(buffer, 0, bytesRead);
- totalBytesDownloaded += bytesRead;
+ byte[] buffer = new byte[1024 * 64]; // TODO: make this configurable/platform dependent
+ int bytesRead;
+
+ while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
+ {
+ await fileStream.WriteAsync(buffer, 0, bytesRead);
+ totalBytesDownloaded += bytesRead;
- message.DownloadProgress = totalBytesDownloaded;
+ message.DownloadProgress = totalBytesDownloaded;
- RetrieveProgress?.Invoke(this, message);
- Resolver.Log.Trace($"Download progress: {totalBytesDownloaded:N0} bytes downloaded");
+ RetrieveProgress?.Invoke(this, message);
+ Resolver.Log.Trace($"Download progress: {totalBytesDownloaded:N0} bytes downloaded");
+ }
}
}
}
diff --git a/source/Meadow.Core/Gateways/Bluetooth/Definitions/Definition.cs b/source/Meadow.Core/Gateways/Bluetooth/Definitions/Definition.cs
index 7c427ebfa..d44400745 100644
--- a/source/Meadow.Core/Gateways/Bluetooth/Definitions/Definition.cs
+++ b/source/Meadow.Core/Gateways/Bluetooth/Definitions/Definition.cs
@@ -10,6 +10,11 @@ public class Definition : IDefinition
///
public string DeviceName { get; }
+ ///
+ /// Gets the company ID.
+ ///
+ public ushort? CompanyId { get; }
+
///
/// Gets the collection of services associated with this device definition.
///
@@ -23,6 +28,21 @@ public class Definition : IDefinition
public Definition(string deviceName, params IService[] services)
{
DeviceName = deviceName;
+ CompanyId = null;
+ Services = new ServiceCollection();
+ Services.AddRange(services);
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified device name and services.
+ ///
+ /// The company ID (assigned number).
+ /// The name of the device.
+ /// The services associated with the device.
+ public Definition(ushort companyId, string deviceName, params IService[] services)
+ {
+ DeviceName = deviceName;
+ CompanyId = companyId;
Services = new ServiceCollection();
Services.AddRange(services);
}
@@ -38,6 +58,11 @@ public string ToJson()
""deviceName"":""{DeviceName}""
";
+ if (CompanyId != null)
+ {
+ json += $@", ""companyIdentifier"":{CompanyId}";
+ }
+
if (Services != null && Services.Count > 0)
{
json += ", \"services\": [";
@@ -58,4 +83,4 @@ public string ToJson()
json += "}";
return json;
}
-}
+}
\ No newline at end of file
diff --git a/source/Meadow.Core/Hardware/BiDirectionalInterruptPort.cs b/source/Meadow.Core/Hardware/BiDirectionalInterruptPort.cs
index a76027869..6b03077d6 100644
--- a/source/Meadow.Core/Hardware/BiDirectionalInterruptPort.cs
+++ b/source/Meadow.Core/Hardware/BiDirectionalInterruptPort.cs
@@ -293,13 +293,10 @@ private void OnInterrupt(IPin pin, bool state)
{
if (pin == this.Pin)
{
- var capturedLastTime = LastEventTime; // note: doing this for latency reasons. kind of. sort of. bad time good time. all time.
- this.LastEventTime = DateTime.Now;
- // BC 2021.05.21 b5.0: Changed this to the new result type.
- // assuming that old state is just an inversion of the new state, yeah?
+ var capturedLastTime = LastEventTime;
+ this.LastEventTime = DateTime.UtcNow;
RaiseChangedAndNotify(new DigitalPortResult(new DigitalState(state, this.LastEventTime), new DigitalState(!state, capturedLastTime)));
- //RaiseChangedAndNotify(new DigitalInputPortChangeResult(state, this.LastEventTime, capturedLastTime));
}
}
}
-}
+}
\ No newline at end of file
diff --git a/source/Meadow.Core/Hardware/Counter.cs b/source/Meadow.Core/Hardware/Counter.cs
index 8dca17aee..9512c6ce0 100644
--- a/source/Meadow.Core/Hardware/Counter.cs
+++ b/source/Meadow.Core/Hardware/Counter.cs
@@ -17,10 +17,11 @@ public class Counter : ICounter, IDisposable
/// Enables or disables the counter
///
public bool Enabled { get; set; }
+
///
/// Returns the current Counter value
///
- public long Count => count;
+ public ulong Count => (uint)count;
///
/// Creates a Counter instance
diff --git a/source/Meadow.Core/Hardware/DigitalInterruptPort.cs b/source/Meadow.Core/Hardware/DigitalInterruptPort.cs
index adfd0ac74..ea8f62e87 100644
--- a/source/Meadow.Core/Hardware/DigitalInterruptPort.cs
+++ b/source/Meadow.Core/Hardware/DigitalInterruptPort.cs
@@ -14,6 +14,7 @@ public class DigitalInterruptPort : DigitalInterruptPortBase
private DigitalPortResult _interruptResult = new DigitalPortResult();
private DigitalState _newState = new DigitalState(false, DateTime.MinValue);
private DigitalState _oldState = new DigitalState(false, DateTime.MinValue);
+ private InterruptMode? _interruptMode;
///
protected IMeadowIOController IOController { get; set; }
@@ -38,7 +39,7 @@ protected DigitalInterruptPort(
ResistorMode resistorMode,
TimeSpan debounceDuration,
TimeSpan glitchDuration
- ) : base(pin, channel, interruptMode)
+ ) : base(pin, channel)
{
// DEVELOPER NOTE:
// Debounce recognizes the first state transition and then ignores anything after that for a period of time.
@@ -61,10 +62,7 @@ TimeSpan glitchDuration
{
// make sure the pin is configured as a digital input with the proper state
ioController.ConfigureInput(pin, resistorMode, interruptMode, debounceDuration, glitchDuration);
- if (interruptMode != InterruptMode.None)
- {
- IOController.WireInterrupt(pin, interruptMode, resistorMode, debounceDuration, glitchDuration);
- }
+ InterruptMode = interruptMode;
}
else
{
@@ -127,6 +125,29 @@ public override ResistorMode Resistor
}
}
+ ///
+ public override InterruptMode InterruptMode
+ {
+ get => _interruptMode ?? InterruptMode.None;
+ set
+ {
+ if (InterruptMode == value) { return; }
+
+ if (InterruptMode != InterruptMode.None)
+ {
+ // if we're already set up for an interrupt, disable all interrupts and reconnect
+ IOController.WireInterrupt(Pin, InterruptMode.None, Resistor, DebounceDuration, GlitchDuration);
+ }
+
+ // don't call WireInterrupt if it's no interrupt and we're coming from the ctor
+ if (_interruptMode != null || value != InterruptMode.None)
+ {
+ IOController.WireInterrupt(Pin, value, Resistor, DebounceDuration, GlitchDuration);
+ }
+ _interruptMode = value;
+ }
+ }
+
private void OnInterrupt(IPin pin, bool state)
{
if (pin == this.Pin)
@@ -135,7 +156,7 @@ private void OnInterrupt(IPin pin, bool state)
{
// this is all to prevent new-ing up (and thereby preventing GC stuff)
_oldState.Time = LastEventTime; // note: doing this for latency reasons. kind of. sort of. bad time good time. all time.
- _newState.Time = this.LastEventTime = DateTime.Now;
+ _newState.Time = LastEventTime = DateTime.UtcNow;
_oldState.State = !state;
_newState.State = state;
_interruptResult.Old = (LastEventTime == DateTime.MinValue) ? null : _oldState;
@@ -226,4 +247,4 @@ public override TimeSpan GlitchDuration
this.IOController.WireInterrupt(Pin, InterruptMode, _resistorMode, _debounceDuration, _glitchDuration);
}
}
-}
\ No newline at end of file
+}
diff --git a/source/Meadow.Core/Meadow.Core.csproj b/source/Meadow.Core/Meadow.Core.csproj
index 457722c1b..31ae889eb 100644
--- a/source/Meadow.Core/Meadow.Core.csproj
+++ b/source/Meadow.Core/Meadow.Core.csproj
@@ -28,11 +28,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/source/Meadow.Core/MeadowOS.FileSystem.cs b/source/Meadow.Core/MeadowOS.FileSystem.cs
index 61c36cab9..f0e35acf3 100644
--- a/source/Meadow.Core/MeadowOS.FileSystem.cs
+++ b/source/Meadow.Core/MeadowOS.FileSystem.cs
@@ -60,6 +60,6 @@ public static string UserFileSystemRoot
///
/// Gets the full path to the file used to store crash reports from unhandled runtime exceptions
///
- public static string RuntimeCrashFile => Path.Combine(CrashReportDirectory, "meadow.error");
+ public static string RuntimeCrashFile => Path.Combine(CrashReportDirectory, "mono_error.txt");
}
-}
\ No newline at end of file
+}
diff --git a/source/Meadow.Core/MeadowOS.cs b/source/Meadow.Core/MeadowOS.cs
index 2c17dcab2..521a48c1b 100644
--- a/source/Meadow.Core/MeadowOS.cs
+++ b/source/Meadow.Core/MeadowOS.cs
@@ -497,6 +497,7 @@ private static (Type appType, Type deviceType, Type? hardwareProviderType)? Find
switch (devicetype.DeviceType.FullName)
{
+ case "Meadow.BeagleBoneBlack":
case "Meadow.RaspberryPi":
case "Meadow.JetsonNano":
case "Meadow.JetsonXavierAgx":
diff --git a/source/Meadow.Core/NtpClientBase.cs b/source/Meadow.Core/NtpClientBase.cs
new file mode 100644
index 000000000..e4c1c68f4
--- /dev/null
+++ b/source/Meadow.Core/NtpClientBase.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using static Meadow.Logging.Logger;
+
+namespace Meadow;
+
+///
+/// base Client for Network Time Protocol
+///
+public abstract class NtpClientBase : INtpClient
+{
+ private int _ntpLock = 1;
+
+ ///
+ public event TimeChangedEventHandler TimeChanged = default!;
+
+ ///
+ public abstract bool Enabled { get; }
+ ///
+ public abstract TimeSpan PollPeriod { get; set; }
+
+ ///
+ /// Raises the TimeChanged event with a given time
+ ///
+ /// The new time
+ protected void RaiseTimeChanged(DateTime utcTime) => TimeChanged?.Invoke(utcTime);
+
+ ///
+ public Task Synchronize(string? ntpServer = null)
+ {
+ if (ntpServer == null)
+ {
+ if (Resolver.Device.PlatformOS.NtpServers.Length == 0)
+ {
+ ntpServer = "0.pool.ntp.org";
+ Resolver.Log.Info($"No configured NTP servers. Defaulting to {ntpServer}", MessageGroup.Core);
+ }
+ else
+ {
+ ntpServer = Resolver.Device.PlatformOS.NtpServers[0];
+ }
+ }
+
+ if (Interlocked.Exchange(ref _ntpLock, 0) == 1)
+ {
+ try
+ {
+ var m_ntpPacket = new byte[48];
+ //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
+ m_ntpPacket[0] = 0x1B;
+
+ UdpClient client = new UdpClient();
+ client.Connect(ntpServer, 123);
+ client.Send(m_ntpPacket, m_ntpPacket.Length);
+ IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
+ byte[] data = client.Receive(ref ep);
+
+ // receive date data is at offset 32
+ // Data is 64 bits - first 32 is seconds
+ // it is not in an endian order, so we must rearrange
+ byte[] endianSeconds = new byte[4];
+ endianSeconds[0] = data[32 + 3];
+ endianSeconds[1] = data[32 + 2];
+ endianSeconds[2] = data[32 + 1];
+ endianSeconds[3] = data[32 + 0];
+ uint seconds = BitConverter.ToUInt32(endianSeconds, 0);
+
+ // second 32 is fraction of a second
+ endianSeconds[0] = data[32 + 7];
+ endianSeconds[1] = data[32 + 6];
+ endianSeconds[2] = data[32 + 5];
+ endianSeconds[3] = data[32 + 4];
+
+ uint fraction = BitConverter.ToUInt32(endianSeconds, 0);
+
+ var s = double.Parse($"{seconds}.{fraction}");
+
+ var dt = new DateTime(1900, 1, 1).AddSeconds(s);
+ Resolver.Device.PlatformOS.SetClock(dt);
+ TimeChanged?.Invoke(dt);
+ return Task.FromResult(true);
+ }
+ catch (Exception ex)
+ {
+ Resolver.Log.Error($"Failed to query NTP Server: '{ex.Message}'.", MessageGroup.Core);
+ return Task.FromResult(false);
+ }
+ finally
+ {
+ Interlocked.Exchange(ref _ntpLock, 1);
+ }
+
+ }
+
+ return Task.FromResult(false);
+ }
+}
diff --git a/source/Meadow.Core/ReliabilityServiceBase.cs b/source/Meadow.Core/ReliabilityServiceBase.cs
index e39a77a0e..ee05076a0 100644
--- a/source/Meadow.Core/ReliabilityServiceBase.cs
+++ b/source/Meadow.Core/ReliabilityServiceBase.cs
@@ -81,6 +81,8 @@ public ReliabilityServiceBase()
{
_uptimeStopwatch.Start();
+ EnsureCrashReportDirectoryExists();
+
_reportFiles = new string[]
{
MeadowOS.FileSystem.AppCrashFile,
@@ -105,6 +107,21 @@ protected virtual void ProcessSystemError(MeadowSystemErrorInfo errorInfo, out b
recommendReset = false;
}
+ private void EnsureCrashReportDirectoryExists()
+ {
+ if (!Directory.Exists(MeadowOS.FileSystem.CrashReportDirectory))
+ {
+ try
+ {
+ Directory.CreateDirectory(MeadowOS.FileSystem.CrashReportDirectory);
+ }
+ catch (Exception ex)
+ {
+ Resolver.Log.Error($"Unable to create crash report directory: {ex.Message}");
+ }
+ }
+ }
+
///
/// Writes a system error to the App Crash file
///
@@ -114,6 +131,8 @@ protected void LogSystemError(MeadowSystemErrorInfo error, bool recommendedReset
{
try
{
+ EnsureCrashReportDirectoryExists();
+
var fi = new FileInfo(MeadowOS.FileSystem.AppCrashFile);
if (!fi.Exists)
{
@@ -134,6 +153,8 @@ private void ResetDueToSystemError()
Resolver.Log.Error($"Resetting due to a system error.");
try
{
+ EnsureCrashReportDirectoryExists();
+
var fi = new FileInfo(MeadowOS.FileSystem.AppCrashFile);
if (!fi.Exists)
{
diff --git a/source/Tests/Core.Unit.Tests/Core.Unit.Tests.csproj b/source/Tests/Core.Unit.Tests/Core.Unit.Tests.csproj
index f6b0cfc2c..f1ce64bb0 100644
--- a/source/Tests/Core.Unit.Tests/Core.Unit.Tests.csproj
+++ b/source/Tests/Core.Unit.Tests/Core.Unit.Tests.csproj
@@ -10,9 +10,11 @@
false
-
-
-
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -22,7 +24,7 @@
-
+
diff --git a/source/Tests/Core.Unit.Tests/UpdateServiceTests/ProcessPublishedCommandTests.cs b/source/Tests/Core.Unit.Tests/UpdateServiceTests/ProcessPublishedCommandTests.cs
index 2d3b4817f..7cf540a64 100644
--- a/source/Tests/Core.Unit.Tests/UpdateServiceTests/ProcessPublishedCommandTests.cs
+++ b/source/Tests/Core.Unit.Tests/UpdateServiceTests/ProcessPublishedCommandTests.cs
@@ -428,7 +428,7 @@ public class TestCommandWithExtensionData : IMeadowCommand
{
public bool ArgProperty { get; set; }
- public Dictionary AdditionalData { get; set; }
+ public Dictionary AdditionalData { get; set; } = new();
}
public class TestLogProvider : ILogProvider
diff --git a/source/implementations/desktop/Meadow.Desktop/Desktop.cs b/source/implementations/desktop/Meadow.Desktop/Desktop.cs
index 9d92518ee..0298a2cf0 100644
--- a/source/implementations/desktop/Meadow.Desktop/Desktop.cs
+++ b/source/implementations/desktop/Meadow.Desktop/Desktop.cs
@@ -1,5 +1,6 @@
using Meadow.Hardware;
using Meadow.Peripherals.Displays;
+using Meadow.Pinouts;
using Meadow.Units;
using System;
@@ -47,7 +48,7 @@ public void Initialize(MeadowPlatform detectedPlatform)
_implementation = detectedPlatform switch
{
MeadowPlatform.OSX => new Mac(),
- MeadowPlatform.DesktopLinux => new Linux(),
+ MeadowPlatform.DesktopLinux => new DesktopLinux(),
MeadowPlatform.Windows => new Windows(),
_ => throw new ArgumentException($"Desktop cannot run on {detectedPlatform}"),
};
@@ -130,6 +131,9 @@ public void WatchdogReset()
///
public ICounter CreateCounter(IPin pin, InterruptMode edge)
=> _implementation.CreateCounter(pin, edge);
+ ///
+ public IDigitalSignalAnalyzer CreateDigitalSignalAnalyzer(IPin pin, bool captureDutyCycle)
+ => _implementation.CreateDigitalSignalAnalyzer(pin, captureDutyCycle);
}
diff --git a/source/implementations/desktop/Meadow.Desktop/Meadow.Desktop.csproj b/source/implementations/desktop/Meadow.Desktop/Meadow.Desktop.csproj
index b8a961d2e..7f6130090 100644
--- a/source/implementations/desktop/Meadow.Desktop/Meadow.Desktop.csproj
+++ b/source/implementations/desktop/Meadow.Desktop/Meadow.Desktop.csproj
@@ -21,11 +21,17 @@
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
diff --git a/source/implementations/f7/Meadow.F7/Devices/Definitions/F7CoreCompute.Pinout.cs b/source/implementations/f7/Meadow.F7/Devices/Definitions/F7CoreCompute.Pinout.cs
index 6e9800e1e..3078d0601 100644
--- a/source/implementations/f7/Meadow.F7/Devices/Definitions/F7CoreCompute.Pinout.cs
+++ b/source/implementations/f7/Meadow.F7/Devices/Definitions/F7CoreCompute.Pinout.cs
@@ -216,9 +216,10 @@ internal Pinout() { }
);
///
- public IPin PB4 => _pb4 ??= new Pin(
+ public IPin PB4 => _pb4 ??= new F7Pin(
Controller,
"PB4", "PB4",
+ Core.Interop.STM32.GpioPort.PortB, 4,
new List {
new DigitalChannelInfo("PB4", interruptGroup: 4),
new PwmChannelInfo("TIM3_CH1", 3, 1),
@@ -236,9 +237,10 @@ internal Pinout() { }
);
///
- public IPin PB6 => _pb6 ??= new Pin(
+ public IPin PB6 => _pb6 ??= new F7Pin(
Controller,
"PB6", "PB6",
+ Core.Interop.STM32.GpioPort.PortB, 6,
new List {
new DigitalChannelInfo("PB6", interruptGroup: 6),
new PwmChannelInfo("TIM4_CH1", 4, 1),
@@ -247,9 +249,10 @@ internal Pinout() { }
);
///
- public IPin PB7 => _pb7 ??= new Pin(
+ public IPin PB7 => _pb7 ??= new F7Pin(
Controller,
"PB7", "PB7",
+ Core.Interop.STM32.GpioPort.PortB, 7,
new List {
new DigitalChannelInfo("PB7", interruptGroup: 7),
new PwmChannelInfo("TIM4_CH2", 4, 2),
@@ -258,9 +261,10 @@ internal Pinout() { }
);
///
- public IPin PB8 => _pb8 ??= new Pin(
+ public IPin PB8 => _pb8 ??= new F7Pin(
Controller,
"PB8", "PB8",
+ Core.Interop.STM32.GpioPort.PortB, 8,
new List {
new DigitalChannelInfo("PB8", interruptGroup: 8),
new PwmChannelInfo("TIM4_CH3", 4, 3)
@@ -268,9 +272,10 @@ internal Pinout() { }
);
///
- public IPin PB9 => _pb9 ??= new Pin(
+ public IPin PB9 => _pb9 ??= new F7Pin(
Controller,
"PB9", "PB9",
+ Core.Interop.STM32.GpioPort.PortB, 9,
new List {
new DigitalChannelInfo("PB9", interruptGroup: 9),
new PwmChannelInfo("TIM4_CH4", 4, 4),
@@ -305,9 +310,10 @@ internal Pinout() { }
);
///
- public IPin PB14 => _pb14 ??= new Pin(
+ public IPin PB14 => _pb14 ??= new F7Pin(
Controller,
"PB14", "PB14",
+ Core.Interop.STM32.GpioPort.PortB, 14,
new List {
new DigitalChannelInfo("PB14", interruptGroup: 14),
new PwmChannelInfo("TIM12_CH1", 12, 1),
@@ -316,9 +322,10 @@ internal Pinout() { }
);
///
- public IPin PB15 => _pb15 ??= new Pin(
+ public IPin PB15 => _pb15 ??= new F7Pin(
Controller,
"PB15", "PB15",
+ Core.Interop.STM32.GpioPort.PortB, 15,
new List {
new DigitalChannelInfo("PB15", interruptGroup: 15),
new PwmChannelInfo("TIM12_CH2", 12, 2),
@@ -373,9 +380,10 @@ internal Pinout() { }
);
///
- public IPin PC6 => _pc6 ??= new Pin(
+ public IPin PC6 => _pc6 ??= new F7Pin(
Controller,
"PC6", "PC6",
+ Core.Interop.STM32.GpioPort.PortC, 6,
new List {
new DigitalChannelInfo("PC6", interruptGroup: 6),
new PwmChannelInfo("TIM8_CH1", 8, 1), // or TIM3_CH1 (see D05)
@@ -383,9 +391,10 @@ internal Pinout() { }
);
///
- public IPin PC7 => _pc7 ??= new Pin(
+ public IPin PC7 => _pc7 ??= new F7Pin(
Controller,
"PC7", "PC7",
+ Core.Interop.STM32.GpioPort.PortC, 7,
new List {
new DigitalChannelInfo("PC7", interruptGroup: 7),
new PwmChannelInfo("TIM3_CH2", 3, 2) // or TIM8_CH2
@@ -402,9 +411,10 @@ internal Pinout() { }
);
///
- public IPin PC9 => _pc9 ??= new Pin(
+ public IPin PC9 => _pc9 ??= new F7Pin(
Controller,
"PC9", "PC9",
+ Core.Interop.STM32.GpioPort.PortC, 9,
new List {
new DigitalChannelInfo("PC9", interruptGroup: 9),
new PwmChannelInfo("TIM8_CH4", 8, 4)
@@ -572,9 +582,10 @@ internal Pinout() { }
);
///
- public IPin PH10 => _ph10 ??= new Pin(
+ public IPin PH10 => _ph10 ??= new F7Pin(
Controller,
"PH10", "PH10",
+ Core.Interop.STM32.GpioPort.PortH, 10,
new List {
new DigitalChannelInfo("PH10", interruptCapable: false ),
new PwmChannelInfo("TIM5_CH1", 5, 1)
diff --git a/source/implementations/f7/Meadow.F7/Devices/Definitions/F7FeatherV2.Pinout.cs b/source/implementations/f7/Meadow.F7/Devices/Definitions/F7FeatherV2.Pinout.cs
index 8be2f14a2..74b0bec1a 100644
--- a/source/implementations/f7/Meadow.F7/Devices/Definitions/F7FeatherV2.Pinout.cs
+++ b/source/implementations/f7/Meadow.F7/Devices/Definitions/F7FeatherV2.Pinout.cs
@@ -221,9 +221,10 @@ internal Pinout() { }
// D02
// TIM5_CH1, I2C4_SMBA, FMC_D18, DCMI_D1, LCD_R4, EVENTOUT
///
- public IPin D02 => _d02 ??= new Pin(
+ public IPin D02 => _d02 ??= new F7Pin(
Controller,
"D02", "PH10",
+ Core.Interop.STM32.GpioPort.PortH, 10,
new List {
new DigitalChannelInfo("PH10", interruptCapable: false),
new PwmChannelInfo("TIM5_CH1", 5, 1)
@@ -232,9 +233,10 @@ internal Pinout() { }
// D03
// I2C4_SCL, TIM4_CH3, TIM10_CH1, I2C1_SCL, DFSDM_CKIN7, UART5_RX, CAN1_RX, SDMMC2_D4, ETH_MII_TXD3, SDMMC1_D4, DCMI_D6, LCD_B6, EVENTOUT
///
- public IPin D03 => _d03 ??= new Pin(
+ public IPin D03 => _d03 ??= new F7Pin(
Controller,
"D03", "PB8",
+ Core.Interop.STM32.GpioPort.PortB, 8,
new List {
new DigitalChannelInfo("PB8", interruptGroup: 8),
new PwmChannelInfo("TIM4_CH3", 4, 3),
@@ -244,9 +246,10 @@ internal Pinout() { }
// D04
// I2C4_SDA, TIM4_CH4, TIM11_CH1, I2C1_SDA, SPI2_NSS/I2S2_WS, DFSDM_DATIN7, UART5_TX, CAN1_TX, SDMMC2_D5, I2C4_SMBA, SDMMC1_D5, DCMI_D7, LCD_B7, EVENTOUT
///
- public IPin D04 => _d04 ??= new Pin(
+ public IPin D04 => _d04 ??= new F7Pin(
Controller,
"D04", "PB9",
+ Core.Interop.STM32.GpioPort.PortB, 9,
new List {
new DigitalChannelInfo("PB9", interruptGroup: 9),
new PwmChannelInfo("TIM4_CH4", 4, 4),
@@ -257,41 +260,17 @@ internal Pinout() { }
//==== Right Header
// D05 //NOTE: we can expose UART_7 if we want here
- // F7v2.a
- // JTDI, TIM2_CH1/TIM2_ETR, HDMI_CEC, SPI1_NSS/I2S1_WS, SPI3_NSS/I2S3_WS, SPI6_NSS, UART4_RTS, CAN3_TX, UART7_TX, EVENTOUT
- //public IPin D05 => new Pin(
- // "D05", "PA15",
- // new List {
- // new DigitalChannelInfo("PA15", interruptGroup: 15),
- // //new PwmChannelInfo("TIM2_CH1", 1, 1), // Reserved for OnboardLED
- // new UartChannelInfo("UART7_RX", SerialDirectionType.Receive)
- // }
- //);
- // F7v2.b
// NJTRST, TIM3_CH1, SPI1_MISO, SPI3_MISO, SPI2_NSS/I2S2_WS, SPI6_MISO, SDMMC2_D3, CAN3_TX, UART7_TX, EVENTOUT
///
public IPin D05 => _d05 ??= new Pin(
Controller,
"D05", "PB4",
new List {
- new DigitalChannelInfo("PB4", interruptGroup: 4),
- new PwmChannelInfo("TIM3_CH1", 3, 1),
- //new UartChannelInfo("UART7_TX", SerialDirectionType.Transmit)
+ new DigitalChannelInfo("PB4", interruptGroup: 4)
}
);
- // D06 //NOTE: we can expose UART_7 if we want here
- // F7v2.a
- // JTDO/TRACESWO, TIM2_CH2, SPI1_SCK/I2S1_CK, SPI3_SCK/I2S3_CK, SPI6_SCK, SDMMC2_D2, CAN3_RX, UART7_RX, EVENTOUT
- //public IPin D06 => new Pin(
- // "D06", "PB3",
- // new List {
- // new DigitalChannelInfo("PB3", interruptGroup: 3),
- // //new PwmChannelInfo("TIM2_CH2", 2, 2), // Reserved for OnboardLED
- // new UartChannelInfo("UART7_TX", SerialDirectionType.Transmit)
- // }
- //);
- // F7v2.b
+ // D06
// I2C2_SMBA, SPI5_SCK, TIM12_CH1, ETH_MII_RXD2, FMC_SDNE1, DCMI_D8, EVENTOUT
///
public IPin D06 => _d06 ??= new Pin(
@@ -299,17 +278,16 @@ internal Pinout() { }
"D06", "PB13",
new List {
new DigitalChannelInfo("PB13", interruptGroup: 13),
- //new PwmChannelInfo("TIM1_CH1", 1, 1),
- //new UartChannelInfo("UART7_RX", SerialDirectionType.Receive)
}
);
// D07
// TIM4_CH2, I2C1_SDA, DFSDM_CKIN5, USART1_RX, I2C4_SDA, FMC_NL, DCMI_VSYNC, EVENTOUT
///
- public IPin D07 => _d07 ??= new Pin(
+ public IPin D07 => _d07 ??= new F7Pin(
Controller,
"D07", "PB7",
+ Core.Interop.STM32.GpioPort.PortB, 7,
new List {
new DigitalChannelInfo("PB7", interruptGroup: 7),
new PwmChannelInfo("TIM4_CH2", 4, 2),
@@ -321,9 +299,10 @@ internal Pinout() { }
// D08
// UART5_TX, TIM4_CH1, HDMI_CEC, I2C1_SCL, DFSDM_DATIN5, USART1_TX, CAN2_TX, QUADSPI_BK1_NCS, I2C4_SCL, FMC_SDNE1, DCMI_D5, EVENTOUT
///
- public IPin D08 => _d08 ??= new Pin(
+ public IPin D08 => _d08 ??= new F7Pin(
Controller,
"D08", "PB6",
+ Core.Interop.STM32.GpioPort.PortB, 6,
new List {
new DigitalChannelInfo("PB6", interruptGroup: 6),
new PwmChannelInfo("TIM4_CH1", 4, 1),
@@ -333,9 +312,10 @@ internal Pinout() { }
// D09
// TIM3_CH1, TIM8_CH1, I2S2_MCK, DFSDM_CKIN3, USART6_TX, FMC_NWAIT, SDMMC2_D6, SDMMC1_D6, DCMI_D0, LCD_HSYNC, EVENTOUT
///
- public IPin D09 => _d09 ??= new Pin(
+ public IPin D09 => _d09 ??= new F7Pin(
Controller,
"D09", "PC6",
+ Core.Interop.STM32.GpioPort.PortC, 6,
new List {
new DigitalChannelInfo("PC6", interruptGroup: 6),
new PwmChannelInfo("TIM8_CH1", 8, 1), // or TIM3_CH1 (see D05)
@@ -344,9 +324,10 @@ internal Pinout() { }
// D10
// TIM3_CH2, TIM8_CH2, I2S3_MCK, DFSDM_DATIN3, USART6_RX, FMC_NE1, SDMMC2_D7, SDMMC1_D7, DCMI_D1, LCD_G6, EVENTOUT
///
- public IPin D10 => _d10 ??= new Pin(
+ public IPin D10 => _d10 ??= new F7Pin(
Controller,
"D10", "PC7",
+ Core.Interop.STM32.GpioPort.PortC, 7,
new List {
new DigitalChannelInfo("PC7", interruptGroup: 7),
new PwmChannelInfo("TIM3_CH2", 3, 2) // or TIM8_CH2
@@ -355,9 +336,10 @@ internal Pinout() { }
// D11
// MCO2, TIM3_CH4, TIM8_CH4, I2C3_SDA, I2S_CKIN, UART5_CTS, QUADSPI_BK1_IO0, LCD_G3, SDMMC1_D1, DCMI_D3, LCD_B2, EVENTOUT
///
- public IPin D11 => _d11 ??= new Pin(
+ public IPin D11 => _d11 ??= new F7Pin(
Controller,
"D11", "PC9",
+ Core.Interop.STM32.GpioPort.PortC, 9,
new List {
new DigitalChannelInfo("PC9", interruptGroup: 9),
new PwmChannelInfo("TIM8_CH4", 8, 4)
@@ -366,9 +348,10 @@ internal Pinout() { }
// D12
// TIM1_CH2N, TIM8_CH2N, USART1_TX, SPI2_MISO, DFSDM_DATIN2, USART3_RTS, UART4_RTS, TIM12_CH1, SDMMC2_D0, OTG_HS_DM, EVENTOUT
///
- public IPin D12 => _d12 ??= new Pin(
+ public IPin D12 => _d12 ??= new F7Pin(
Controller,
"D12", "PB14",
+ Core.Interop.STM32.GpioPort.PortB, 14,
new List {
new DigitalChannelInfo("PB14", interruptGroup: 14),
new PwmChannelInfo("TIM12_CH1", 12, 1),
@@ -378,9 +361,10 @@ internal Pinout() { }
// D13
// RTC_REFIN, TIM1_CH3N, TIM8_CH3N, USART1_RX, SPI2_MOSI/I2S2_SD, DFSDM_CKIN2, UART4_CTS, TIM12_CH2, SDMMC2_D1, OTG_HS_DP, EVENTOUT
///
- public IPin D13 => _d13 ??= new Pin(
+ public IPin D13 => _d13 ??= new F7Pin(
Controller,
"D13", "PB15",
+ Core.Interop.STM32.GpioPort.PortB, 15,
new List {
new DigitalChannelInfo("PB15", interruptGroup: 15),
new PwmChannelInfo("TIM12_CH2", 12, 2),
@@ -390,9 +374,10 @@ internal Pinout() { }
// D14
// TIM1_BKIN, I2C2_SMBA, SPI2_NSS/I2S2_WS, DFSDM_DATIN1, USART3_CK, UART5_RX, CAN2_RX, OTG_HS_ULPI_D5, ETH_MII_TXD0/ETH_RMII _TXD0, OTG_HS_ID, EVENTOUT
///
- public IPin D14 => _d14 ??= new Pin(
+ public IPin D14 => _d14 ??= new F7Pin(
Controller,
"D14", "PB12",
+ Core.Interop.STM32.GpioPort.PortB, 12,
new List {
new DigitalChannelInfo("PB12", interruptCapable: false),
//new PwmChannelInfo("TIM12_CH2", 12, 2),
diff --git a/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/Esp32Coprocessor.cs b/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/Esp32Coprocessor.cs
index 84c19090b..8fdc9a3ca 100644
--- a/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/Esp32Coprocessor.cs
+++ b/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/Esp32Coprocessor.cs
@@ -2,6 +2,7 @@
using Meadow.Devices.Esp32.MessagePayloads;
using Meadow.Gateways;
using System;
+using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
@@ -29,9 +30,36 @@ public partial class Esp32Coprocessor : ICoprocessor
internal event EventHandler<(WiFiFunction fn, StatusCodes status, byte[] data)>? WiFiMessageReceived = default!;
internal event EventHandler<(CellFunction fn, StatusCodes status, byte[] data)>? CellMessageReceived = default!;
- internal event EventHandler<(EthernetFunction fn, StatusCodes status, byte[] data)>? EthernetMessageReceived = default!;
internal event EventHandler<(SystemFunction fn, StatusCodes status)>? SystemMessageReceived = default!;
+ private EventHandler<(EthernetFunction fn, StatusCodes status, byte[] data)>? _ethernetMessageHandlers;
+ private Queue _queuedEthernetEvents = new();
+
+ private record RawEventData
+ {
+ public Esp32Interfaces Interface { get; set; }
+ public uint Function { get; set; }
+ public uint StatusCode { get; set; }
+ public byte[]? Payload { get; set; }
+ }
+
+ internal event EventHandler<(EthernetFunction fn, StatusCodes status, byte[] data)>? EthernetMessageReceived
+ {
+ add
+ {
+ _ethernetMessageHandlers += value;
+
+ lock (_queuedEthernetEvents)
+ {
+ foreach (var evt in _queuedEthernetEvents)
+ {
+ _ethernetMessageHandlers?.Invoke(this, new((EthernetFunction)evt.Function, (StatusCodes)evt.StatusCode, evt.Payload ?? EmptyPayload));
+ }
+ _queuedEthernetEvents.Clear();
+ }
+ }
+ remove => _ethernetMessageHandlers -= value;
+ }
///
/// Possible debug levels.
@@ -106,7 +134,7 @@ internal StatusCodes SendCommand(byte where, UInt32 function, bool block, byte[]
/// StatusCodes enum indicating if the command was successful or if an error occurred.
protected StatusCodes SendBluetoothCommand(BluetoothFunction function, bool block, byte[]? encodedRequest, byte[]? encodedResult)
{
- return (SendCommand((byte)Esp32Interfaces.BlueTooth, (uint)function, block, encodedRequest, encodedResult));
+ return SendCommand((byte)Esp32Interfaces.BlueTooth, (uint)function, block, encodedRequest, encodedResult);
}
@@ -182,7 +210,7 @@ internal StatusCodes SendCommand(byte destination, uint function, bool block, by
resultGcHandle.Free();
}
}
- return (result);
+ return result;
}
///
@@ -229,7 +257,7 @@ private StatusCodes GetEventData(EventData eventData, out byte[]? payload)
resultGcHandle.Free();
}
}
- return (result);
+ return result;
}
///
@@ -274,7 +302,23 @@ private void EventHandlerServiceThread(object o)
switch ((Esp32Interfaces)eventData.Interface)
{
case Esp32Interfaces.WiredEthernet:
- EthernetMessageReceived?.Invoke(this, ((EthernetFunction)eventData.Function, (StatusCodes)eventData.StatusCode, payload ?? EmptyPayload));
+ lock (_queuedEthernetEvents)
+ {
+ if (_ethernetMessageHandlers == null)
+ {
+ _queuedEthernetEvents.Enqueue(new RawEventData
+ {
+ Interface = Esp32Interfaces.WiredEthernet,
+ Function = eventData.Function,
+ StatusCode = eventData.StatusCode,
+ Payload = payload
+ });
+ }
+ else
+ {
+ _ethernetMessageHandlers.Invoke(this, ((EthernetFunction)eventData.Function, (StatusCodes)eventData.StatusCode, payload ?? EmptyPayload));
+ }
+ }
break;
case Esp32Interfaces.WiFi:
WiFiMessageReceived?.Invoke(this, ((WiFiFunction)eventData.Function, (StatusCodes)eventData.StatusCode, payload ?? EmptyPayload));
@@ -289,7 +333,8 @@ private void EventHandlerServiceThread(object o)
SystemMessageReceived?.Invoke(this, ((SystemFunction)eventData.Function, (StatusCodes)eventData.StatusCode));
break;
default:
- throw new NotImplementedException($"Events not implemented for interface {eventData.Interface}");
+ Resolver.Log.Warn($"Received an ESP32 event for interface {eventData.Interface}. Ignored");
+ break;
}
}).RethrowUnhandledExceptions();
}
@@ -325,6 +370,6 @@ public double GetBatteryLevel()
GetBatteryChargeLevelResponse response = Encoders.ExtractGetBatteryChargeLevelResponse(result, 0);
voltage = response.Level / 1000f;
}
- return (voltage);
+ return voltage;
}
}
\ No newline at end of file
diff --git a/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/Esp32SystemErrorInfo.cs b/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/Esp32SystemErrorInfo.cs
index 3e70f75f5..6b41d5702 100644
--- a/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/Esp32SystemErrorInfo.cs
+++ b/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/Esp32SystemErrorInfo.cs
@@ -24,7 +24,10 @@ internal Esp32SystemErrorInfo(int function, StatusCodes statusCode)
StatusCodes.InvalidConfigurationFile => "Invalid configuration file",
StatusCodes.InvalidWiFiConfigurationFile => "Invalid WiFi configuration file",
StatusCodes.InvalidCellConfigurationFile => "Invalid Cell configuration file",
- StatusCodes.NetworkDeadlock => "Network deadlock detected. Device reboot is required to reconnect",
+ StatusCodes.NetworkDeadlock => "Possible network deadlock detected. Device reboot is advised to reconnect",
+ StatusCodes.NetworkCloseDeadlock => "Possible network deadlock on close() detected. Device reboot is advised to reconnect",
+ StatusCodes.NetworkPollDeadlock => "Possible network deadlock on poll() detected. Device reboot is advised to reconnect",
+ StatusCodes.NetworkSocketDeadlock => "Possible network deadlock on socket() detected. Device reboot is advised to reconnect",
_ => "ESP32 Error"
},
SystemErrorNumber.CoprocessorError)
diff --git a/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/SharedEnums.cs b/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/SharedEnums.cs
index 5930f10e0..c420fec4b 100644
--- a/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/SharedEnums.cs
+++ b/source/implementations/f7/Meadow.F7/Devices/Esp32Coprocessor/SharedEnums.cs
@@ -190,9 +190,22 @@ public enum StatusCodes
///
InvalidCellConfigurationFile = 45,
///
- /// Network deadlock detected.
+ /// Possible network deadlock detected.
///
NetworkDeadlock = 46,
+ ///
+ /// Possible network deadlock detected on close().
+ ///
+ NetworkCloseDeadlock = 47,
+ ///
+ /// Possible network deadlock detected on poll().
+ ///
+ NetworkPollDeadlock = 48,
+ ///
+ /// Possible network deadlock detected on socket().
+ ///
+ NetworkSocketDeadlock = 49,
+
///
/// ESP reset for unknown reason
///
@@ -531,25 +544,49 @@ internal enum WiFiFunction
public enum BluetoothFunction
{
///
- /// BluetoothFunction - Start
+ /// Start the Bluetooth service.
///
Start = 0,
///
- /// BluetoothFunction - Stop
+ /// Stop the Bluetooth service.
///
Stop = 1,
///
- /// BluetoothFunction - GetHandles
+ /// Get the handles for the service and characteristics.
///
GetHandles = 2,
///
- /// BluetoothFunction - ServerDataSet
+ /// BSet a characteristic value.
///
ServerDataSet = 3,
///
- /// BluetoothFunction - ClientWriteRequestEvent
+ /// Client has written to a characteristic value.
+ ///
+ ClientWriteRequestEvent = 4,
+ ///
+ /// Bluetooth service is starting event.
+ ///
+ BluetoothStartingEvent = 5,
+ ///
+ /// Bluetooth has started event.
+ ///
+ BluetoothStartedEvent = 6,
+ ///
+ /// Bluetooth is stopping event.
+ ///
+ BluetoothStoppingEvent = 7,
+ ///
+ /// Bluetooth has stopped event.
+ ///
+ BluetoothStoppedEvent = 8,
+ ///
+ /// Bluetooth client connected event.
+ ///
+ ClientConnectedEvent = 9,
+ ///
+ /// Bluetooth client disconnected event.
///
- ClientWriteRequestEvent = 4
+ ClientDisconnectedEvent = 10,
};
///
@@ -577,6 +614,15 @@ public enum CellFunction
/// CellFunction - NtpUpdateEvent
///
NtpUpdateEvent = 5,
+ ///
+ /// CellFunction - NetworkConnectingEvent
+ ///
+ NetworkConnectingEvent = 8,
+
+ ///
+ /// CellFunction - NetworkRetryExceededEvent
+ ///
+ NetworkRetryExceededEvent = 9,
};
///
diff --git a/source/implementations/f7/Meadow.F7/Devices/F7CellNetworkAdapter.cs b/source/implementations/f7/Meadow.F7/Devices/F7CellNetworkAdapter.cs
index be43fa43a..7d9556a28 100644
--- a/source/implementations/f7/Meadow.F7/Devices/F7CellNetworkAdapter.cs
+++ b/source/implementations/f7/Meadow.F7/Devices/F7CellNetworkAdapter.cs
@@ -178,6 +178,14 @@ protected void InvokeEvent(CellFunction eventId, StatusCodes statusCode, byte[]
case CellFunction.NtpUpdateEvent:
RaiseNtpTimeChangedEvent();
break;
+ case CellFunction.NetworkConnectingEvent:
+ Resolver.Log.Trace("Cell connecting event triggered!");
+ RaiseNetworkConnecting();
+ break;
+ case CellFunction.NetworkRetryExceededEvent:
+ Resolver.Log.Trace("Cell retry exceeded event triggered!");
+ RaiseConnectFailed();
+ break;
default:
Resolver.Log.Trace("Event type not found");
break;
diff --git a/source/implementations/f7/Meadow.F7/Devices/F7DigitalSignalAnalyzer.cs b/source/implementations/f7/Meadow.F7/Devices/F7DigitalSignalAnalyzer.cs
new file mode 100644
index 000000000..ab0160d28
--- /dev/null
+++ b/source/implementations/f7/Meadow.F7/Devices/F7DigitalSignalAnalyzer.cs
@@ -0,0 +1,121 @@
+using Meadow.Core;
+using Meadow.Hardware;
+using Meadow.Units;
+using System;
+using System.Linq;
+using static Meadow.Core.Interop;
+using static Meadow.Core.Interop.Nuttx;
+
+namespace Meadow.Devices;
+
+internal class F7DigitalSignalAnalyzer : IDigitalSignalAnalyzer, IDisposable
+{
+ private Nuttx.AnalyzerConfig _analyzerConfig;
+ private Nuttx.AnalyzerData _analyzerData = new();
+
+ public bool IsDisposed { get; private set; }
+
+ internal F7DigitalSignalAnalyzer(F7Pin pin, bool captureDutyCycle = true)
+ {
+ // these use the same pins as the PWMs, so we can just use the existing PWM info to extract port, pin, and timer info
+ var pwmInfo = pin.SupportedChannels.FirstOrDefault(i => i is PwmChannelInfo) as PwmChannelInfo;
+
+ if (pwmInfo == null)
+ {
+ Resolver.Log.Warn($"Pin {pin.Name} does not have Timer support.");
+ throw new ArgumentException();
+ }
+
+ _analyzerConfig = new Interop.Nuttx.AnalyzerConfig
+ {
+ TimerNumber = (int)pwmInfo.Timer,
+ ChannelNumber = (int)pwmInfo.TimerChannel,
+ Port = (int)pin.ProcessorPort,
+ Pin = pin.ProcessorPin,
+ Type = captureDutyCycle ? Nuttx.AnalyzerType.WithDutyCycle : Nuttx.AnalyzerType.NoDutyCycle
+ };
+
+ var result = Nuttx.meadow_measure_freq_configure(ref _analyzerConfig);
+
+ switch (result)
+ {
+ case Nuttx.AnalyzerCallStatus.ConfigureSuccess:
+ break;
+ default:
+ Resolver.Log.Warn($"Configure analyzer pin returned {result}");
+ throw new Exception();
+ }
+ }
+
+ ///
+ public ulong GetCount()
+ {
+ var data = ReadData();
+ return (ulong)data.GpioCount;
+ }
+
+ ///
+ public double GetDutyCycle()
+ {
+ var data = ReadData();
+ return data.DutyCycle1k / 1000d;
+ }
+
+ ///
+ public Frequency GetFrequency()
+ {
+ var data = ReadData();
+ return new Frequency(data.Frequency1k / 1000d, Frequency.UnitType.Hertz);
+ }
+
+ ///
+ public Frequency GetMeanFrequency()
+ {
+ var data = ReadData();
+ return new Frequency(data.AvgFrequency1k / 1000d, Frequency.UnitType.Hertz);
+ }
+
+ private AnalyzerData ReadData()
+ {
+ _analyzerData.TimerNumber = _analyzerConfig.TimerNumber;
+ _analyzerData.ChannelNumber = _analyzerConfig.ChannelNumber;
+
+ var result = Nuttx.meadow_measure_freq_return_freq_info(ref _analyzerData);
+
+ switch (result)
+ {
+ case AnalyzerCallStatus.ReadSuccess:
+ return _analyzerData;
+ case AnalyzerCallStatus.FrequencyInputNotDetected:
+ // no input signal - don't throw an exception, make sure data is just zero
+ _analyzerData.Frequency1k = 0;
+ _analyzerData.DutyCycle1k = 0;
+ _analyzerData.AvgFrequency1k = 0;
+ return _analyzerData;
+ default:
+ throw new Exception($"Analyzer read failed. Returned {result} (T:{_analyzerData.TimerNumber} C:{_analyzerData.ChannelNumber} )");
+ }
+ }
+
+ private void Unconfigure()
+ {
+ _analyzerConfig.Type = AnalyzerType.Unconfigure;
+ var result = Nuttx.meadow_measure_freq_configure(ref _analyzerConfig);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!IsDisposed)
+ {
+ Unconfigure();
+ IsDisposed = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
\ No newline at end of file
diff --git a/source/implementations/f7/Meadow.F7/Devices/F7MicroBase.IDigitalInputOutputController.cs b/source/implementations/f7/Meadow.F7/Devices/F7MicroBase.IDigitalInputOutputController.cs
index fb880a487..808a40e5d 100644
--- a/source/implementations/f7/Meadow.F7/Devices/F7MicroBase.IDigitalInputOutputController.cs
+++ b/source/implementations/f7/Meadow.F7/Devices/F7MicroBase.IDigitalInputOutputController.cs
@@ -1,92 +1,125 @@
using Meadow.Hardware;
using System;
-namespace Meadow.Devices
+namespace Meadow.Devices;
+
+///
+/// Represents the base class for Meadow F7 Microcontroller.
+///
+public abstract partial class F7MicroBase
{
///
- /// Represents the base class for Meadow F7 Microcontroller.
+ /// Creates a digital output port with the specified parameters.
///
- public abstract partial class F7MicroBase
+ /// The pin for the digital output port.
+ /// The initial state of the digital output port.
+ /// The initial output type of the digital output port.
+ /// The created digital output port.
+ public IDigitalOutputPort CreateDigitalOutputPort(
+ IPin pin,
+ bool initialState = false,
+ OutputType initialOutputType = OutputType.PushPull)
{
- ///
- /// Creates a digital output port with the specified parameters.
- ///
- /// The pin for the digital output port.
- /// The initial state of the digital output port.
- /// The initial output type of the digital output port.
- /// The created digital output port.
- public IDigitalOutputPort CreateDigitalOutputPort(
- IPin pin,
- bool initialState = false,
- OutputType initialOutputType = OutputType.PushPull)
- {
- return DigitalOutputPort.From(pin, this.IoController, initialState, initialOutputType);
- }
+ return DigitalOutputPort.From(pin, this.IoController, initialState, initialOutputType);
+ }
- ///
- /// Creates a digital input port with the specified pin and default resistor mode.
- ///
- /// The pin for the digital input port.
- /// The created digital input port.
- public IDigitalInputPort CreateDigitalInputPort(
- IPin pin)
- {
- return CreateDigitalInputPort(pin, ResistorMode.Disabled);
- }
+ ///
+ /// Creates a digital input port with the specified pin and default resistor mode.
+ ///
+ /// The pin for the digital input port.
+ /// The created digital input port.
+ public IDigitalInputPort CreateDigitalInputPort(
+ IPin pin)
+ {
+ return CreateDigitalInputPort(pin, ResistorMode.Disabled);
+ }
- ///
- /// Creates a digital input port with the specified pin, interrupt mode, and resistor mode.
- ///
- /// The pin for the digital input port.
- /// The interrupt mode for the digital input port.
- /// The resistor mode for the digital input port.
- /// The created digital input port.
- public IDigitalInputPort CreateDigitalInputPort(
- IPin pin,
- InterruptMode interruptMode = InterruptMode.None,
- ResistorMode resistorMode = ResistorMode.Disabled)
- {
- return DigitalInterruptPort.From(pin, this.IoController, interruptMode, resistorMode, TimeSpan.Zero, TimeSpan.Zero);
- }
+ ///
+ /// Creates a digital input port with the specified pin, interrupt mode, and resistor mode.
+ ///
+ /// The pin for the digital input port.
+ /// The interrupt mode for the digital input port.
+ /// The resistor mode for the digital input port.
+ /// The created digital input port.
+ public IDigitalInputPort CreateDigitalInputPort(
+ IPin pin,
+ InterruptMode interruptMode = InterruptMode.None,
+ ResistorMode resistorMode = ResistorMode.Disabled)
+ {
+ return DigitalInterruptPort.From(pin, this.IoController, interruptMode, resistorMode, TimeSpan.Zero, TimeSpan.Zero);
+ }
- ///
- /// Creates a digital input port with the specified pin and resistor mode.
- ///
- /// The pin for the digital input port.
- /// The resistor mode for the digital input port.
- /// The created digital input port.
- public IDigitalInputPort CreateDigitalInputPort(
- IPin pin,
- ResistorMode resistorMode
- )
- {
- return DigitalInputPort.From(pin, this.IoController, resistorMode);
- }
+ ///
+ /// Creates a digital input port with the specified pin and resistor mode.
+ ///
+ /// The pin for the digital input port.
+ /// The resistor mode for the digital input port.
+ /// The created digital input port.
+ public IDigitalInputPort CreateDigitalInputPort(
+ IPin pin,
+ ResistorMode resistorMode
+ )
+ {
+ return DigitalInputPort.From(pin, this.IoController, resistorMode);
+ }
+
+ ///
+ /// Creates a digital interrupt port with the specified pin, interrupt mode, and default resistor mode.
+ ///
+ /// The pin for the digital interrupt port.
+ /// The interrupt mode for the digital interrupt port.
+ /// The resistor mode for the digital interrupt port.
+ /// The created digital interrupt port.
+ public IDigitalInterruptPort CreateDigitalInterruptPort(IPin pin, InterruptMode interruptMode, ResistorMode resistorMode = ResistorMode.Disabled)
+ {
+ return CreateDigitalInterruptPort(pin, interruptMode, resistorMode, TimeSpan.Zero, TimeSpan.Zero);
+ }
- ///
- /// Creates a digital interrupt port with the specified pin, interrupt mode, and default resistor mode.
- ///
- /// The pin for the digital interrupt port.
- /// The interrupt mode for the digital interrupt port.
- /// The resistor mode for the digital interrupt port.
- /// The created digital interrupt port.
- public IDigitalInterruptPort CreateDigitalInterruptPort(IPin pin, InterruptMode interruptMode, ResistorMode resistorMode = ResistorMode.Disabled)
+ ///
+ /// Creates a digital interrupt port with the specified parameters.
+ ///
+ /// The pin for the digital interrupt port.
+ /// The interrupt mode for the digital interrupt port.
+ /// The resistor mode for the digital interrupt port.
+ /// The debounce duration for the digital interrupt port.
+ /// The glitch duration for the digital interrupt port.
+ /// The created digital interrupt port.
+ public IDigitalInterruptPort CreateDigitalInterruptPort(IPin pin, InterruptMode interruptMode, ResistorMode resistorMode, TimeSpan debounceDuration, TimeSpan glitchDuration)
+ {
+ return DigitalInterruptPort.From(pin, this.IoController, interruptMode, resistorMode, debounceDuration, glitchDuration);
+ }
+
+ private bool _supportsOsAnalyzer = true;
+
+ ///
+ public virtual IDigitalSignalAnalyzer CreateDigitalSignalAnalyzer(IPin pin, bool captureDutyCycle = true)
+ {
+ if (_supportsOsAnalyzer)
{
- return CreateDigitalInterruptPort(pin, interruptMode, resistorMode, TimeSpan.Zero, TimeSpan.Zero);
- }
+ if (pin is F7Pin f7Pin)
+ {
+ try
+ {
+ var f7Analyzer = new F7DigitalSignalAnalyzer(f7Pin, captureDutyCycle);
+ return f7Analyzer;
+ }
+ catch (Exception ex)
+ {
+ Resolver.Log.Warn($"Failed creating an F7 analyzer. ({ex.GetType().Name}) {ex.Message}");
+ Resolver.Log.Warn($"Falling back to a SoftDigitalSignalAnalyzer.");
- ///
- /// Creates a digital interrupt port with the specified parameters.
- ///
- /// The pin for the digital interrupt port.
- /// The interrupt mode for the digital interrupt port.
- /// The resistor mode for the digital interrupt port.
- /// The debounce duration for the digital interrupt port.
- /// The glitch duration for the digital interrupt port.
- /// The created digital interrupt port.
- public IDigitalInterruptPort CreateDigitalInterruptPort(IPin pin, InterruptMode interruptMode, ResistorMode resistorMode, TimeSpan debounceDuration, TimeSpan glitchDuration)
+ _supportsOsAnalyzer = false;
+ return new SoftDigitalSignalAnalyzer(pin);
+ }
+ }
+ else
+ {
+ return new SoftDigitalSignalAnalyzer(pin);
+ }
+ }
+ else
{
- return DigitalInterruptPort.From(pin, this.IoController, interruptMode, resistorMode, debounceDuration, glitchDuration);
+ return new SoftDigitalSignalAnalyzer(pin);
}
}
}
diff --git a/source/implementations/f7/Meadow.F7/Devices/F7MicroBase.cs b/source/implementations/f7/Meadow.F7/Devices/F7MicroBase.cs
index 38c59840a..83d218235 100644
--- a/source/implementations/f7/Meadow.F7/Devices/F7MicroBase.cs
+++ b/source/implementations/f7/Meadow.F7/Devices/F7MicroBase.cs
@@ -161,7 +161,7 @@ protected bool InitCoprocessor()
}
else
{
- wifiAdapter.ConnectToDefaultAccessPoint(TimeSpan.FromSeconds(60), CancellationToken.None).Wait();
+ _ = wifiAdapter.ConnectToDefaultAccessPoint(TimeSpan.FromSeconds(60), CancellationToken.None);
}
}
}
@@ -178,13 +178,10 @@ protected bool InitCoprocessor()
}
catch (Exception e)
{
- Resolver.Log.Error($"Unable to create ESP32 coprocessor: {e.Message}", MessageGroup.Core);
+ Resolver.Log.Error($"Error initializing ESP32 coprocessor: {e.Message}", MessageGroup.Core);
return false;
}
- finally
- {
- }
return true;
}
else
diff --git a/source/implementations/f7/Meadow.F7/Devices/F7Pin.cs b/source/implementations/f7/Meadow.F7/Devices/F7Pin.cs
new file mode 100644
index 000000000..78264f878
--- /dev/null
+++ b/source/implementations/f7/Meadow.F7/Devices/F7Pin.cs
@@ -0,0 +1,24 @@
+using Meadow.Hardware;
+using System.Collections.Generic;
+using static Meadow.Core.Interop;
+
+namespace Meadow.Devices;
+
+internal class F7Pin : Pin
+{
+ public STM32.GpioPort ProcessorPort { get; }
+ public int ProcessorPin { get; }
+
+ public F7Pin(
+ IPinController? controller,
+ string name,
+ object key,
+ STM32.GpioPort port,
+ int pin,
+ IList? supportedChannels)
+ : base(controller, name, key, supportedChannels)
+ {
+ ProcessorPort = port;
+ ProcessorPin = pin;
+ }
+}
diff --git a/source/implementations/f7/Meadow.F7/Devices/NtpClient.cs b/source/implementations/f7/Meadow.F7/Devices/NtpClient.cs
index 0d105d3a6..30581a266 100644
--- a/source/implementations/f7/Meadow.F7/Devices/NtpClient.cs
+++ b/source/implementations/f7/Meadow.F7/Devices/NtpClient.cs
@@ -1,26 +1,16 @@
using System;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-using System.Threading.Tasks;
-using static Meadow.Logging.Logger;
namespace Meadow;
///
/// Client for Network Time Protocol
///
-public class NtpClient : INtpClient
+public class NtpClient : NtpClientBase
{
- ///
- /// Event raised when the device clock is adjusted by NTP
- ///
- public event TimeChangedEventHandler TimeChanged = default!;
-
///
/// Returns true if the NTP Client is enabled
///
- public bool Enabled => F7PlatformOS.GetBoolean(IPlatformOS.ConfigurationValues.GetTimeAtStartup);
+ public override bool Enabled => F7PlatformOS.GetBoolean(IPlatformOS.ConfigurationValues.GetTimeAtStartup);
internal NtpClient()
{ }
@@ -28,7 +18,7 @@ internal NtpClient()
///
/// Time period that the NTP client attempts to query the NTP time server(s)
///
- public TimeSpan PollPeriod
+ public override TimeSpan PollPeriod
{
get => TimeSpan.Zero; // currently only happens at startup
set => throw new PlatformNotSupportedException("Changing NTP Poll Frequency not currently supported");
@@ -36,78 +26,6 @@ public TimeSpan PollPeriod
internal void RaiseTimeChanged()
{
- TimeChanged?.Invoke(DateTime.UtcNow);
- }
-
- private int _ntpLock = 1;
-
- ///
- public Task Synchronize(string? ntpServer = null)
- {
- if (ntpServer == null)
- {
- if (Resolver.Device.PlatformOS.NtpServers.Length == 0)
- {
- ntpServer = "0.pool.ntp.org";
- Resolver.Log.Info($"No configured NTP servers. Defaulting to {ntpServer}", MessageGroup.Core);
- }
- else
- {
- ntpServer = Resolver.Device.PlatformOS.NtpServers[0];
- }
- }
-
- if (Interlocked.Exchange(ref _ntpLock, 0) == 1)
- {
- try
- {
- var m_ntpPacket = new byte[48];
- //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
- m_ntpPacket[0] = 0x1B;
-
- UdpClient client = new UdpClient();
- client.Connect(ntpServer, 123);
- client.Send(m_ntpPacket, m_ntpPacket.Length);
- IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
- byte[] data = client.Receive(ref ep);
-
- // receive date data is at offset 32
- // Data is 64 bits - first 32 is seconds
- // it is not in an endian order, so we must rearrange
- byte[] endianSeconds = new byte[4];
- endianSeconds[0] = data[32 + 3];
- endianSeconds[1] = data[32 + 2];
- endianSeconds[2] = data[32 + 1];
- endianSeconds[3] = data[32 + 0];
- uint seconds = BitConverter.ToUInt32(endianSeconds, 0);
-
- // second 32 is fraction of a second
- endianSeconds[0] = data[32 + 7];
- endianSeconds[1] = data[32 + 6];
- endianSeconds[2] = data[32 + 5];
- endianSeconds[3] = data[32 + 4];
-
- uint fraction = BitConverter.ToUInt32(endianSeconds, 0);
-
- var s = double.Parse($"{seconds}.{fraction}");
-
- var dt = new DateTime(1900, 1, 1).AddSeconds(s);
- Resolver.Device.PlatformOS.SetClock(dt);
- TimeChanged?.Invoke(dt);
- return Task.FromResult(true);
- }
- catch (Exception ex)
- {
- Resolver.Log.Error($"Failed to query NTP Server: '{ex.Message}'.", MessageGroup.Core);
- return Task.FromResult(false);
- }
- finally
- {
- Interlocked.Exchange(ref _ntpLock, 1);
- }
-
- }
-
- return Task.FromResult(false);
+ RaiseTimeChanged(DateTime.UtcNow);
}
}
diff --git a/source/implementations/f7/Meadow.F7/F7FileSystemInfo.cs b/source/implementations/f7/Meadow.F7/F7FileSystemInfo.cs
index 49ac5e02a..e979465ea 100644
--- a/source/implementations/f7/Meadow.F7/F7FileSystemInfo.cs
+++ b/source/implementations/f7/Meadow.F7/F7FileSystemInfo.cs
@@ -11,12 +11,9 @@ namespace Meadow;
///
public class F7FileSystemInfo : IPlatformOS.FileSystemInfo
{
- ///
- public new event ExternalStorageEventHandler ExternalStorageEvent = default!;
-
private readonly List _drives = new();
private F7ExternalStorage? _sdCard = default;
-
+ private bool _isMounted = false;
private readonly bool _sdSupported;
///
@@ -38,8 +35,12 @@ internal F7FileSystemInfo(StorageCapabilities capabilities, bool sdSupported)
if (F7ExternalStorage.TryMount("/dev/mmcsd0", "/sdcard", out _sdCard))
{
_drives.Add(_sdCard);
+ _isMounted = true;
+ }
+ else
+ {
+ _isMounted = false;
}
-
if (Resolver.Device is F7CoreComputeBase ccm)
{
// thread an not interrupt because we don't want to consume int group 6 for this and speed isn't critical
@@ -58,11 +59,13 @@ internal F7FileSystemInfo(StorageCapabilities capabilities, bool sdSupported)
private void HandleInserted()
{
- if (_drives.Count == 0)
+ if (!_isMounted)
{
if (F7ExternalStorage.TryMount("/dev/mmcsd0", "/sdcard", out _sdCard))
{
- ExternalStorageEvent?.Invoke(_sdCard, ExternalStorageState.Inserted);
+ _drives.Add(_sdCard);
+ RaiseExternalStorageEvent(_sdCard, ExternalStorageState.Inserted);
+ _isMounted = true;
}
}
}
@@ -71,7 +74,9 @@ private void HandleRemoved()
{
if (_sdCard != null)
{
- ExternalStorageEvent?.Invoke(_sdCard, ExternalStorageState.Ejected);
+ RaiseExternalStorageEvent(_sdCard, ExternalStorageState.Ejected);
+ _drives.Remove(_sdCard);
+ _isMounted = false;
_sdCard = null;
}
}
diff --git a/source/implementations/f7/Meadow.F7/F7PlatformOS.PowerController.cs b/source/implementations/f7/Meadow.F7/F7PlatformOS.PowerController.cs
index 9ae7bfa4c..60a9673d1 100644
--- a/source/implementations/f7/Meadow.F7/F7PlatformOS.PowerController.cs
+++ b/source/implementations/f7/Meadow.F7/F7PlatformOS.PowerController.cs
@@ -63,9 +63,6 @@ public void Sleep(TimeSpan duration)
// This should suspend the processor and code should stop executing
UPD.Ioctl(UpdIoctlFn.PowerSleep, cmd);
- // Stop execution while the device actually does it's thing
- Thread.Sleep(100);
-
// sleeping invalidates the UPD driver handle
UPD.ReOpen();
diff --git a/source/implementations/f7/Meadow.F7/Hardware/PwmPort.cs b/source/implementations/f7/Meadow.F7/Hardware/PwmPort.cs
index 3f794e9d5..bea577920 100644
--- a/source/implementations/f7/Meadow.F7/Hardware/PwmPort.cs
+++ b/source/implementations/f7/Meadow.F7/Hardware/PwmPort.cs
@@ -44,7 +44,7 @@ protected PwmPort(
IPwmChannelInfo channel,
bool inverted = false,
bool isOnboard = false)
- : base(pin, channel, new Frequency(100f, Units.Frequency.UnitType.Hertz))
+ : base(pin, channel)
{
this.IsOnboard = isOnboard;
this.IOController = ioController;
diff --git a/source/implementations/f7/Meadow.F7/Interop/Interop.freq.cs b/source/implementations/f7/Meadow.F7/Interop/Interop.freq.cs
new file mode 100644
index 000000000..5bac365cb
--- /dev/null
+++ b/source/implementations/f7/Meadow.F7/Interop/Interop.freq.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Meadow.Core
+{
+ internal static partial class Interop
+ {
+ public static partial class Nuttx
+ {
+ public enum AnalyzerType
+ {
+ NoDutyCycle = 1,
+ WithDutyCycle = 2,
+ Unconfigure = 1
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct AnalyzerConfig
+ {
+ [FieldOffset(0)]
+ public int TimerNumber;
+ [FieldOffset(4)]
+ public int ChannelNumber;
+ [FieldOffset(8)]
+ public int PortAndPin;
+ [FieldOffset(12)]
+ public AnalyzerType Type;
+
+ public int Port
+ {
+ set
+ {
+ PortAndPin &= 0x0f;
+ PortAndPin |= (value << 4);
+ }
+ }
+ public int Pin
+ {
+ set
+ {
+ if (value > 15) throw new ArgumentException();
+ PortAndPin &= 0xf0;
+ PortAndPin |= value;
+ }
+ }
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct AnalyzerData
+ {
+ [FieldOffset(0)]
+ public int TimerNumber;
+ [FieldOffset(4)]
+ public int ChannelNumber;
+ [FieldOffset(8)]
+ public int Frequency1k;
+ [FieldOffset(12)]
+ public int DutyCycle1k;
+ [FieldOffset(16)]
+ public int AvgFrequency1k;
+ [FieldOffset(20)]
+ public int GpioCount;
+ }
+
+ [DllImport(LIBRARY_NAME, SetLastError = true)]
+ public static extern AnalyzerCallStatus meadow_measure_freq_configure(ref AnalyzerConfig config);
+
+ [DllImport(LIBRARY_NAME, SetLastError = true)]
+ public static extern AnalyzerCallStatus meadow_measure_freq_return_freq_info(ref AnalyzerData config);
+
+ public enum AnalyzerCallStatus
+ {
+ // configure results
+ ConfigureSuccess = (10),
+ UndefinedOption = (-1),
+ IllegalTimerNumber = (-2),
+ IllegalChannelNumber = (-3),
+ BadTimerForPortAndPin = (-4),
+ BadChannelForPortAndPin = (-5),
+ TimerNotAvailable = (-6),
+ ChannelInUse = (-7),
+ MemoryAllocationFailed = (-8),
+ GpioConfigError = (-9),
+ InitChannelHardwareFailed = (-10),
+ InitTimerHardwareFailed = (-11),
+ // READ results
+ ReadSuccess = (20),
+ NoChannelActivity = (-21),
+ InvalidTimerNumber = (-22),
+ InvalidChannelNumber = (-23),
+ TimerAccessNull = (-24),
+ ChannelNotConfigured = (-25),
+ ChannelDataNull = (-26),
+ NoActiveChannels = (-27),
+ FrequencyInputNotDetected = (-28),
+ // unconfigure results
+ UnconfigureSuccess = (30),
+ UnconfigureInvalidTimer = (-31),
+ UnconfigureInvalidChannel = (-32),
+ UnconfigureTimerAccessNull = (-33),
+ UnconfigureChannelNotConfigured = (-34),
+ UnconfigureNoChannel = (-35),
+ UnconfigureInterruptDetachError = (-36),
+ }
+
+ /*
+ int meadow_measure_freq_configure(mdwFreqCfgTimer_t* mdwCfgTimerChan);
+ int meadow_measure_freq_return_freq_info(mdwFreqReturnData_t* mdwFreqReturnData);
+
+ #define MEADOW_MEAS_FREQ_CONF_OPTION_NO_DC (1)
+ #define MEADOW_MEAS_FREQ_CONF_OPTION_WITH_DC (2)
+ #define MEADOW_MEAS_FREQ_CONF_OPTION_UNCFG (3)
+
+ struct mdwFreqCfgTimer_s
+ {
+ uint32_t timerNumber; // 1-14 (1 & 8 not supported)
+ uint32_t channelNumber; // 1-4
+ uint32_t portAndPin; // bits 7:4 port 0=A, 1=B & bits 3:0 pin 0-15
+ uint32_t configOption; // See below
+ // 0 = illegal - Must set configOption to 1-3
+ // 1 = Configure without Duty Cycle,
+ // 2 = Configure with Duty Cycle (twice the interrupts),
+ // 3 = Unconfigure
+ };
+ typedef struct mdwFreqCfgTimer_s mdwFreqCfgTimer_t;
+
+ struct mdwFreqReturnData_s
+ {
+ uint32_t timerNumber; // The timer number 1-14
+ uint32_t channelNumber; // The channel number 1-4
+ uint32_t frequencyX1000; // Frequency * 1000
+ uint32_t dutyCycleX1000; // Duty Cycle * 1000
+ uint32_t avgFreqX1000; // Average frequency * 1000
+ uint32_t gpioCountForAvg; // Number of transitions since list read
+ };
+ typedef struct mdwFreqReturnData_s mdwFreqReturnData_t;
+
+ // Define values returned by measurement code
+ #define MEADOW_MEAS_FREQ_CONF_SUCCESSFUL (10)
+ #define MEADOW_MEAS_FREQ_CONF_UNDEFINED_OPTION (-1)
+ #define MEADOW_MEAS_FREQ_CONF_TIM_NUMB_ILLEGAL (-2)
+ #define MEADOW_MEAS_FREQ_CONF_CHAN_NUMB_ILLEGAL (-3)
+ #define MEADOW_MEAS_FREQ_CONF_PORT_PIN_NOT_FOR_TIM (-4)
+ #define MEADOW_MEAS_FREQ_CONF_PORT_PIN_TIM_CHAN_INVALID (-5)
+ #define MEADOW_MEAS_FREQ_CONF_TIM_NOT_USABLE (-6)
+ #define MEADOW_MEAS_FREQ_CONF_TIM_CHAN_IN_USE (-7)
+ #define MEADOW_MEAS_FREQ_CONF_CHAN_MEM_ALLOC_FAILED (-8)
+ #define MEADOW_MEAS_FREQ_CONF_CONFIGGPIO_ERR (-9)
+ #define MEADOW_MEAS_FREQ_CONF_INIT_CHAN_HW_FAIL (-10)
+ #define MEADOW_MEAS_FREQ_CONF_INIT_TIM_HW_FAIL (-11)
+
+ #define MEADOW_MEAS_FREQ_READ_SUCCESSFUL (20)
+ #define MEADOW_MEAS_FREQ_READ_NO_CHANS_ACTIVITY (-21)
+ #define MEADOW_MEAS_FREQ_READ_INVALID_TIMER_NUMB (-22)
+ #define MEADOW_MEAS_FREQ_READ_INVALID_CHANNEL_NUMB (-23)
+ #define MEADOW_MEAS_FREQ_READ_TIMER_ACCESS_NULL (-24)
+ #define MEADOW_MEAS_FREQ_READ_CHAN_NOT_CONFIG (-25)
+ #define MEADOW_MEAS_FREQ_READ_CHANNEL_DATA_NULL (-26)
+ #define MEADOW_MEAS_FREQ_READ_NO_CHANS_ACTIVE (-27)
+ #define MEADOW_MEAS_FREQ_READ_FREQ_INPUT_NOT_DETECTED (-28)
+
+ #define MEADOW_MEAS_FREQ_UNCFG_SUCCESSFUL (30)
+ #define MEADOW_MEAS_FREQ_UNCFG_INVALID_TIMER_NUMB (-31)
+ #define MEADOW_MEAS_FREQ_UNCFG_INVALID_CHANNEL_NUMB (-32)
+ #define MEADOW_MEAS_FREQ_UNCFG_TIMER_ACCESS_NULL (-33)
+ #define MEADOW_MEAS_FREQ_UNCFG_CHAN_NOT_CONFIG (-34)
+ #define MEADOW_MEAS_FREQ_UNCFG_NO_CHANNEL (-35)
+ #define MEADOW_MEAS_FREQ_UNCFG_IRQ_DETACH_ERR (-36)
+
+ */
+ }
+ }
+}
diff --git a/source/implementations/f7/Meadow.F7/Meadow.F7.csproj b/source/implementations/f7/Meadow.F7/Meadow.F7.csproj
index 44fdf2c76..c11b6ae87 100644
--- a/source/implementations/f7/Meadow.F7/Meadow.F7.csproj
+++ b/source/implementations/f7/Meadow.F7/Meadow.F7.csproj
@@ -25,6 +25,11 @@
-
+
+
+
+
+
+
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Communications/SpiBus.cs b/source/implementations/linux/Meadow.Linux/Hardware/Communications/SpiBus.cs
index 312a7f05b..4cf52df9b 100644
--- a/source/implementations/linux/Meadow.Linux/Hardware/Communications/SpiBus.cs
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Communications/SpiBus.cs
@@ -32,6 +32,7 @@ internal enum SpiMode
}
private int DriverHandle { get; set; }
+ private int BusNumber { get; }
///
public Frequency[] SupportedSpeeds
@@ -59,8 +60,7 @@ internal unsafe SpiBus(int chipSelect, SpiClockConfiguration.ClockPhase phase, S
internal unsafe SpiBus(int busNumber, int chipSelect, SpiMode mode, Units.Frequency speed)
{
- Resolver.Log.Info($"SPI{busNumber}");
-
+ BusNumber = busNumber;
var driver = $"/dev/spidev{busNumber}.{chipSelect}";
DriverHandle = Interop.open(driver, Interop.DriverFlags.O_RDWR);
if (DriverHandle < 0)
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBoneAnalogInputPort.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBoneAnalogInputPort.cs
new file mode 100644
index 000000000..f7dd61dfc
--- /dev/null
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBoneAnalogInputPort.cs
@@ -0,0 +1,80 @@
+using Meadow.Hardware;
+using Meadow.Units;
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Meadow.Pinouts;
+
+///
+/// Represents an analog input port for BeagleBone
+///
+public class BeagleBoneAnalogInputPort : AnalogInputPortBase
+{
+ private readonly string _devicePath;
+ private Task? _updateTask;
+ private bool _isUpdating = false;
+ private CancellationTokenSource _cancellationTokenSource = new();
+ private CircularBuffer _buffer;
+
+ internal BeagleBoneAnalogInputPort(IPin pin, IAnalogChannelInfo channelInfo, int sampleCount, TimeSpan sampleInterval)
+ : base(pin, channelInfo, sampleCount, sampleInterval, 1.8.Volts())
+ {
+ // pin name is in the form AINx where x is the device number
+ var deviceNumber = pin.Name.Last();
+ _devicePath = $"/sys/bus/iio/devices/iio:device0/in_voltage{deviceNumber}_raw";
+ _buffer = new CircularBuffer(sampleCount);
+ }
+
+ private Voltage GetChannelVoltage()
+ {
+ // cat /sys/bus/iio/devices/iio\:device0/in_voltage0_raw
+
+ var rawText = File.ReadAllText(_devicePath);
+ if (int.TryParse(rawText, out var raw))
+ {
+ return ((raw / 4096d) * ReferenceVoltage.Volts).Volts();
+ }
+
+ throw new Exception($"Unable to parse {rawText} to a voltage");
+ }
+
+ ///
+ public override Task Read()
+ {
+ if (_isUpdating)
+ {
+ return Task.FromResult(_buffer.Average(e => e.Volts).Volts());
+ }
+
+ return Task.FromResult(GetChannelVoltage());
+ }
+
+ ///
+ public override void StartUpdating(TimeSpan? updateInterval)
+ {
+ if (_isUpdating) return;
+
+ _updateTask = Task.Run(async () =>
+ {
+ _isUpdating = true;
+ _cancellationTokenSource.TryReset();
+
+ while (!_cancellationTokenSource.IsCancellationRequested)
+ {
+ _buffer.Append(GetChannelVoltage());
+ await Task.Delay(UpdateInterval);
+ }
+ }, _cancellationTokenSource.Token);
+ }
+
+ ///
+ public override void StopUpdating()
+ {
+ if (!_isUpdating) return;
+
+ _cancellationTokenSource.Cancel();
+ }
+}
\ No newline at end of file
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBoneBlack.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBoneBlack.cs
new file mode 100644
index 000000000..de3c35ac8
--- /dev/null
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBoneBlack.cs
@@ -0,0 +1,95 @@
+using Meadow.Hardware;
+using Meadow.Pinouts;
+using Meadow.Units;
+using System;
+using System.Linq;
+
+namespace Meadow;
+
+///
+/// Represents a BeagleBoneBlack running the Linux operating system
+///
+public partial class BeagleBoneBlack : Linux
+{
+ private readonly DeviceCapabilities _capabilities;
+
+ ///
+ public BeagleBoneBlackPinout Pins { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BeagleBoneBlack()
+ {
+ Pins = new BeagleBoneBlackPinout();
+ Pins.Controller = this;
+
+ _capabilities = new DeviceCapabilities(
+ new AnalogCapabilities(true, 12), // TODO: check resolution
+ new NetworkCapabilities(true, true),
+ new StorageCapabilities(false)
+ );
+ }
+
+ ///
+ /// Initializes the specified pin to be an AnalogInput and returns the
+ /// port used to sample the port value.
+ ///
+ /// The pin to create the port on.
+ /// The number of samples to use for input averaging
+ /// The interval between readings
+ public IAnalogInputPort CreateAnalogInputPort(IPin pin, int sampleCount, TimeSpan sampleInterval)
+ {
+ return CreateAnalogInputPort(pin, sampleCount, sampleInterval, 1.8.Volts());
+ }
+
+ ///
+ public override IAnalogInputPort CreateAnalogInputPort(IPin pin, int sampleCount, TimeSpan sampleInterval, Voltage voltageReference)
+ {
+ // TODO: verify the vRef (1.8V on the BBB)
+ var channelInfo = pin.SupportedChannels!.OfType().FirstOrDefault();
+ if (channelInfo == null)
+ {
+ throw new NotSupportedException($"Pin {pin.Name} is not Analog Input capable");
+ }
+
+ return new BeagleBoneAnalogInputPort(pin, channelInfo, sampleCount, sampleInterval);
+ }
+
+ ///
+ public override IPwmPort CreatePwmPort(IPin pin, Frequency frequency, float dutyCycle = 0.5F, bool inverted = false)
+ {
+ var channelInfo = pin.SupportedChannels!.OfType().FirstOrDefault();
+ if (channelInfo == null)
+ {
+ throw new NotSupportedException($"Pin {pin.Name} is not PWM capable");
+ }
+
+ return new BeagleBonePwmPort(pin, channelInfo, frequency, dutyCycle, inverted);
+ }
+
+ ///
+ public override II2cBus CreateI2cBus(int busNumber = 2)
+ {
+ return base.CreateI2cBus(busNumber);
+ }
+
+ ///
+ public override ISpiBus CreateSpiBus(int busNumber, Frequency speed)
+ {
+ try
+ {
+ return base.CreateSpiBus(busNumber, speed);
+ }
+ catch (NativeException ne)
+ {
+ switch (ne.ErrorCode)
+ {
+ case 13:
+ throw new NativeException($"No permissions to access the SPI bus. Have you configured the lines with `config-pin`?", ne.ErrorCode.Value);
+ default:
+ throw;
+ }
+ }
+ }
+}
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBoneBlackPinout.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBoneBlackPinout.cs
new file mode 100644
index 000000000..142f7fb6f
--- /dev/null
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBoneBlackPinout.cs
@@ -0,0 +1,470 @@
+using Meadow.Hardware;
+
+namespace Meadow.Pinouts;
+
+///
+/// Defines the pinout configuration for a BeagleBone Black.
+///
+public class BeagleBoneBlackPinout : PinDefinitionBase, IPinDefinitions
+{
+ private const string gpiochip0 = "gpiochip0";
+ private const string gpiochip1 = "gpiochip1";
+ private const string gpiochip2 = "gpiochip2";
+ private const string gpiochip3 = "gpiochip3";
+
+ internal BeagleBoneBlackPinout() { }
+
+ ///
+ /// Represents the GPIO_48 pin.
+ ///
+ public IPin GPIO_48 => new GpiodPin(Controller, "GPIO_48", "P9_15", gpiochip1, 16);
+ ///
+ /// Represents the GPIO_49 pin.
+ ///
+ public IPin GPIO_49 => new GpiodPin(Controller, "GPIO_49", "P9_23", gpiochip1, 17);
+ ///
+ /// Represents the GPIO_117 pin.
+ ///
+ public IPin GPIO_117 => new GpiodPin(Controller, "GPIO_117", "P9_25", gpiochip3, 21);
+ ///
+ /// Represents the GPIO_115 pin.
+ ///
+ public IPin GPIO_115 => new GpiodPin(Controller, "GPIO_115", "P9_27", gpiochip3, 19);
+ ///
+ /// Represents the GPIO_112 pin.
+ ///
+ public IPin GPIO_112 => new GpiodPin(Controller, "GPIO_112", "P9_30", gpiochip3, 16);
+ ///
+ /// Represents the GPIO_20 pin.
+ ///
+ public IPin GPIO_20 => new GpiodPin(Controller, "GPIO_20", "P9_41", gpiochip0, 20);
+ ///
+ /// Represents the GPIO_69 pin.
+ ///
+ public IPin GPIO_69 => new GpiodPin(Controller, "GPIO_69", "P8_9", gpiochip2, 5);
+ ///
+ /// Represents the GPIO_68 pin.
+ ///
+ public IPin GPIO_68 => new GpiodPin(Controller, "GPIO_68", "P8_10", gpiochip2, 4);
+ ///
+ /// Represents the GPIO_45 pin.
+ ///
+ public IPin GPIO_45 => new GpiodPin(Controller, "GPIO_45", "P8_11", gpiochip1, 13);
+ ///
+ /// Represents the GPIO_44 pin.
+ ///
+ public IPin GPIO_44 => new GpiodPin(Controller, "GPIO_44", "P8_12", gpiochip1, 12);
+ ///
+ /// Represents the GPIO_26 pin.
+ ///
+ public IPin GPIO_26 => new GpiodPin(Controller, "GPIO_26", "P8_14", gpiochip1, 18);
+ ///
+ /// Represents the GPIO_47 pin.
+ ///
+ public IPin GPIO_47 => new GpiodPin(Controller, "GPIO_47", "P8_15", gpiochip1, 16);
+ ///
+ /// Represents the GPIO_46 pin.
+ ///
+ public IPin GPIO_46 => new GpiodPin(Controller, "GPIO_46", "P8_16", gpiochip1, 14);
+ ///
+ /// Represents the GPIO_27 pin.
+ ///
+ public IPin GPIO_27 => new GpiodPin(Controller, "GPIO_27", "P8_17", gpiochip0, 27);
+ ///
+ /// Represents the GPIO_65 pin.
+ ///
+ public IPin GPIO_65 => new GpiodPin(Controller, "GPIO_65", "P8_18", gpiochip2, 1);
+ ///
+ /// Represents the GPIO_61 pin.
+ ///
+ public IPin GPIO_61 => new GpiodPin(Controller, "GPIO_61", "P8_26", gpiochip1, 29);
+ ///
+ /// Represents the GPIO_60 pin.
+ ///
+ public IPin GPIO_60 => new GpiodPin(Controller, "GPIO_60", "P9_12", gpiochip1, 28);
+ ///
+ /// Represents the GPIO_66 pin.
+ ///
+ public IPin GPIO_66 => new GpiodPin(Controller, "GPIO_66", "P8_7", gpiochip2, 2);
+ ///
+ /// Represents the GPIO_67 pin.
+ ///
+ public IPin GPIO_67 => new GpiodPin(Controller, "GPIO_67", "P8_8", gpiochip2, 3);
+
+ ///
+ /// Represents the AIN0 pin.
+ ///
+ public IPin AIN0 => new Pin(Controller, "AIN0", "P9_39", new[] { new AnalogChannelInfo("AIN0", 12, true, false) });
+ ///
+ /// Represents the AIN1 pin.
+ ///
+ public IPin AIN1 => new Pin(Controller, "AIN1", "P9_40", new[] { new AnalogChannelInfo("AIN1", 12, true, false) });
+ ///
+ /// Represents the AIN2 pin.
+ ///
+ public IPin AIN2 => new Pin(Controller, "AIN2", "P9_37", new[] { new AnalogChannelInfo("AIN2", 12, true, false) });
+ ///
+ /// Represents the AIN3 pin.
+ ///
+ public IPin AIN3 => new Pin(Controller, "AIN3", "P9_38", new[] { new AnalogChannelInfo("AIN3", 12, true, false) });
+ ///
+ /// Represents the AIN4 pin.
+ ///
+ public IPin AIN4 => new Pin(Controller, "AIN4", "P9_33", new[] { new AnalogChannelInfo("AIN4", 12, true, false) });
+ ///
+ /// Represents the AIN5 pin.
+ ///
+ public IPin AIN5 => new Pin(Controller, "AIN5", "P9_36", new[] { new AnalogChannelInfo("AIN5", 12, true, false) });
+ ///
+ /// Represents the AIN6 pin.
+ ///
+ public IPin AIN6 => new Pin(Controller, "AIN6", "P9_35", new[] { new AnalogChannelInfo("AIN6", 12, true, false) });
+
+ ///
+ /// Represents the ECAPPWM0 pin.
+ ///
+ public IPin ECAPPWM0 => new Pin(Controller, "ECAPPWM0", "P9_42", new[] { new PwmChannelInfo("ECAPPWM0", 0, 0) });
+ ///
+ /// Represents the ECAPPWM2 pin.
+ ///
+ public IPin ECAPPWM2 => new Pin(Controller, "ECAPPWM2", "P9_28", new[] { new PwmChannelInfo("ECAPPWM2", 1, 0) });
+ ///
+ /// Represents the EHRPWM1A pin.
+ ///
+ public IPin EHRPWM1A => new Pin(Controller, "EHRPWM1A", "P9_14", new[] { new PwmChannelInfo("EHRPWM1A", 2, 0) });
+ ///
+ /// Represents the EHRPWM1B pin.
+ ///
+ public IPin EHRPWM1B => new Pin(Controller, "EHRPWM1B", "P9_16", new[] { new PwmChannelInfo("EHRPWM1B", 3, 0) });
+ ///
+ /// Represents the EHRPWM2A pin.
+ ///
+ public IPin EHRPWM2A => new Pin(Controller, "EHRPWM2A", "P8_19", new[] { new PwmChannelInfo("EHRPWM2A", 5, 0) });
+ ///
+ /// Represents the EHRPWM2B pin.
+ ///
+ public IPin EHRPWM2B => new Pin(Controller, "EHRPWM2B", "P8_13", new[] { new PwmChannelInfo("EHRPWM2B", 7, 0) });
+
+ ///
+ /// Represents the I2C2_SCL pin.
+ ///
+ public IPin I2C2_SCL => new Pin(Controller, "I2C2_SCL", "P9_19", new[]
+ {
+ new I2cChannelInfo("I2C2_SCL", I2cChannelFunctionType.Clock, busNumber: 2)
+ });
+ ///
+ /// Represents the I2C2_SDA pin.
+ ///
+ public IPin I2C2_SDA => new Pin(Controller, "I2C2_SDA", "P9_20", new[]
+ {
+ new I2cChannelInfo("I2C2_SDA", I2cChannelFunctionType.Data, busNumber: 2)
+ });
+
+ ///
+ /// Represents the SPI0_D0 (MISO/CIPO) pin.
+ ///
+ public IPin SPI0_D0 => new Pin(Controller, "SPI0_D0", "P9_21", new[]
+ {
+ new SpiChannelInfo("SPI0_D0", SpiLineType.CIPO, busNumber: 0)
+ });
+ ///
+ /// Represents the SPI0_D1 (MOSI/COPI) pin.
+ ///
+ public IPin SPI0_D1 => new Pin(Controller, "SPI0_D1", "P9_18", new[]
+ {
+ new SpiChannelInfo("SPI0_D1", SpiLineType.COPI, busNumber: 0)
+ });
+ ///
+ /// Represents the SPI0_SCLK pin.
+ ///
+ public IPin SPI0_SCLK => new Pin(Controller, "SPI0_SCLK", "P9_22", new[]
+ {
+ new SpiChannelInfo("SPI0_SCLK", SpiLineType.Clock, busNumber: 0)
+ });
+
+
+ ///
+ /// Represents Pin P9_12, which corresponds to GPIO_60.
+ ///
+ public IPin P9_12 => GPIO_60;
+ ///
+ /// Represents Pin P9_15, which corresponds to GPIO_48.
+ ///
+ public IPin P9_15 => GPIO_48;
+ ///
+ /// Represents Pin P8_7, which corresponds to GPIO_66.
+ ///
+ public IPin P8_7 => GPIO_66;
+ ///
+ /// Represents Pin P8_8, which corresponds to GPIO_67
+ ///
+ public IPin P8_8 => GPIO_67;
+ ///
+ /// Represents Pin P9_23, which corresponds to GPIO_49
+ ///
+ public IPin P9_23 => GPIO_49;
+ ///
+ /// Represents Pin P9_25, which corresponds to GPIO_117
+ ///
+ public IPin P9_25 => GPIO_117;
+ ///
+ /// Represents Pin P9_27, which corresponds to GPIO_115
+ ///
+ public IPin P9_27 => GPIO_115;
+ ///
+ /// Represents Pin P9_30, which corresponds to GPIO_112
+ ///
+ public IPin P9_30 => GPIO_112;
+ ///
+ /// Represents Pin P9_41, which corresponds to GPIO_20
+ ///
+ public IPin P9_41 => GPIO_20;
+ ///
+ /// Represents Pin P8_9, which corresponds to GPIO_69
+ ///
+ public IPin P8_9 => GPIO_69;
+ ///
+ /// Represents Pin P8_10, which corresponds to GPIO_68
+ ///
+ public IPin P8_10 => GPIO_68;
+ ///
+ /// Represents Pin P8_11, which corresponds to GPIO_45
+ ///
+ public IPin P8_11 => GPIO_45;
+ ///
+ /// Represents Pin P8_12, which corresponds to GPIO_44
+ ///
+ public IPin P8_12 => GPIO_44;
+ ///
+ /// Represents Pin P8_14, which corresponds to GPIO_26
+ ///
+ public IPin P8_14 => GPIO_26;
+ ///
+ /// Represents Pin P8_15, which corresponds to GPIO_47
+ ///
+ public IPin P8_15 => GPIO_47;
+ ///
+ /// Represents Pin P8_16, which corresponds to GPIO_46
+ ///
+ public IPin P8_16 => GPIO_46;
+ ///
+ /// Represents Pin P8_17, which corresponds to GPIO_27
+ ///
+ public IPin P8_17 => GPIO_27;
+ ///
+ /// Represents Pin P8_18, which corresponds to GPIO_65
+ ///
+ public IPin P8_18 => GPIO_65;
+ ///
+ /// Represents Pin P8_26, which corresponds to GPIO_61
+ ///
+ public IPin P8_26 => GPIO_61;
+
+ ///
+ /// Represents Pin P9_33, which corresponds to AIN4
+ ///
+ public IPin P9_33 => AIN4;
+ ///
+ /// Represents Pin P9_35, which corresponds to AIN6
+ ///
+ public IPin P9_35 => AIN6;
+ ///
+ /// Represents Pin P9_36, which corresponds to AIN5
+ ///
+ public IPin P9_36 => AIN5;
+ ///
+ /// Represents Pin P9_37, which corresponds to AIN2
+ ///
+ public IPin P9_37 => AIN2;
+ ///
+ /// Represents Pin P9_38, which corresponds to AIN3
+ ///
+ public IPin P9_38 => AIN3;
+ ///
+ /// Represents Pin P9_39, which corresponds to AIN0
+ ///
+ public IPin P9_39 => AIN0;
+ ///
+ /// Represents Pin P9_40, which corresponds to AIN1
+ ///
+ public IPin P9_40 => AIN1;
+
+ ///
+ /// Represents Pin P9_42, which corresponds to ECAPPWM0
+ ///
+ public IPin P9_42 => ECAPPWM0;
+ ///
+ /// Represents Pin P9_28, which corresponds to ECAPPWM2
+ ///
+ public IPin P9_28 => ECAPPWM2;
+ ///
+ /// Represents Pin P9_14, which corresponds to EHRPWM1A
+ ///
+ public IPin P9_14 => EHRPWM1A;
+ ///
+ /// Represents Pin P9_16, which corresponds to EHRPWM1B
+ ///
+ public IPin P9_16 => EHRPWM1B;
+ ///
+ /// Represents Pin P8_19, which corresponds to EHRPWM2A
+ ///
+ public IPin P8_19 => EHRPWM2A;
+ ///
+ /// Represents Pin P8_13, which corresponds to EHRPWM2B
+ ///
+ public IPin P8_13 => EHRPWM2B;
+
+ ///
+ /// Represents Pin P9_19, which corresponds to I2C2_SCL
+ ///
+ public IPin P9_19 => I2C2_SCL;
+ ///
+ /// Represents Pin P9_20, which corresponds to I2C2_SDA
+ ///
+ public IPin P9_20 => I2C2_SDA;
+
+
+ ///
+ /// Represents Pin P9_21, which corresponds to SPI0_D0
+ ///
+ public IPin P9_21 => SPI0_D0;
+ ///
+ /// Represents Pin P9_18, which corresponds to SPI0_D1
+ ///
+ public IPin P9_18 => SPI0_D1;
+ ///
+ /// Represents Pin P9_22, which corresponds to SPI0_SCLK
+ ///
+ public IPin P9_22 => SPI0_SCLK;
+}
+
+/*
+[application] gpiochip0 line 00: "[mdio_data]" unused input active-high
+[application] gpiochip0 line 01: "[mdio_clk]" unused input active-high
+[application] gpiochip0 line 02: "P9_22 [spi0_sclk]" "P9_22" input active-high
+[application] gpiochip0 line 03: "P9_21 [spi0_d0]" "P9_21" input active-high
+[application] gpiochip0 line 04: "P9_18 [spi0_d1]" "P9_18" input active-high
+[application] gpiochip0 line 05: "P9_17 [spi0_cs0]" "P9_17" input active-high
+[application] gpiochip0 line 06: "[mmc0_cd]" "cd" input active-low
+[application] gpiochip0 line 07: "P8_42A [ecappwm0]" "P9_42" input active-high
+[application] gpiochip0 line 08: "P8_35 [lcd d12]" "P8_35" input active-high
+[application] gpiochip0 line 09: "P8_33 [lcd d13]" "P8_33" input active-high
+[application] gpiochip0 line 10: "P8_31 [lcd d14]" "P8_31" input active-high
+[application] gpiochip0 line 11: "P8_32 [lcd d15]" "P8_32" input active-high
+[application] gpiochip0 line 12: "P9_20 [i2c2_sda]" "P9_20" input active-high
+[application] gpiochip0 line 13: "P9_19 [i2c2_scl]" "P9_19" input active-high
+[application] gpiochip0 line 14: "P9_26 [uart1_rxd]" "P9_26" input active-high
+[application] gpiochip0 line 15: "P9_24 [uart1_txd]" "P9_24" input active-high
+[application] gpiochip0 line 16: "[rmii1_txd3]" unused input active-high
+[application] gpiochip0 line 17: "[rmii1_txd2]" unused input active-high
+[application] gpiochip0 line 18: "[usb0_drvvbus]" unused input active-high
+[application] gpiochip0 line 19: "[hdmi cec]" unused input active-high
+[application] gpiochip0 line 20: "P9_41B" "P9_41" input active-high
+[application] gpiochip0 line 21: "[rmii1_txd1]" unused input active-high
+[application] gpiochip0 line 22: "P8_19 [ehrpwm2a]" "P8_19" input active-high
+[application] gpiochip0 line 23: "P8_13 [ehrpwm2b]" "P8_13" input active-high
+[application] gpiochip0 line 24: "NC" unused input active-high
+[application] gpiochip0 line 25: "NC" unused input active-high
+[application] gpiochip0 line 26: "P8_14" "P8_14" input active-high
+[application] gpiochip0 line 27: "P8_17" "P8_17" input active-high
+[application] gpiochip0 line 28: "[rmii1_txd0]" unused input active-high
+[application] gpiochip0 line 29: "[rmii1_refclk]" unused input active-high
+[application] gpiochip0 line 30: "P9_11 [uart4_rxd]" "P9_11" input active-high
+[application] gpiochip0 line 31: "P9_13 [uart4_txd]" "P9_13" input active-high
+[application] gpiochip1 line 00: "P8_25 [mmc1_dat0]" "P8_25" input active-high
+[application] gpiochip1 line 01: "[mmc1_dat1]" "P8_24" input active-high
+[application] gpiochip1 line 02: "P8_5 [mmc1_dat2]" "P8_05" input active-high
+[application] gpiochip1 line 03: "P8_6 [mmc1_dat3]" "P8_06" input active-high
+[application] gpiochip1 line 04: "P8_23 [mmc1_dat4]" "P8_23" input active-high
+[application] gpiochip1 line 05: "P8_22 [mmc1_dat5]" "P8_22" input active-high
+[application] gpiochip1 line 06: "P8_3 [mmc1_dat6]" "P8_03" input active-high
+[application] gpiochip1 line 07: "P8_4 [mmc1_dat7]" "P8_04" input active-high
+[application] gpiochip1 line 08: "NC" "reset" output active-low
+[application] gpiochip1 line 09: "NC" unused input active-high
+[application] gpiochip1 line 10: "NC" unused input active-high
+[application] gpiochip1 line 11: "NC" unused input active-high
+[application] gpiochip1 line 12: "P8_12" "P8_12" input active-high
+[application] gpiochip1 line 13: "P8_11" "P8_11" input active-high
+[application] gpiochip1 line 14: "P8_16" "P8_16" input active-high
+[application] gpiochip1 line 15: "P8_15" "P8_15" input active-high
+[application] gpiochip1 line 16: "P9_15A" "P9_15" input active-high
+[application] gpiochip1 line 17: "P9_23" "P9_23" input active-high
+[application] gpiochip1 line 18: "P9_14 [ehrpwm1a]" "P9_14" input active-high
+[application] gpiochip1 line 19: "P9_16 [ehrpwm1b]" "P9_16" input active-high
+[application] gpiochip1 line 20: "[emmc rst]" unused input active-high
+[application] gpiochip1 line 21: "[usr0 led]" "beaglebone:green:usr0" output active-high
+[application] gpiochip1 line 22: "[usr1 led]" "beaglebone:green:usr1" output active-high
+[application] gpiochip1 line 23: "[usr2 led]" "beaglebone:green:usr2" output active-high
+[application] gpiochip1 line 24: "[usr3 led]" "beaglebone:green:usr3" output active-high
+[application] gpiochip1 line 25: "[hdmi irq]" "interrupt" input active-high
+[application] gpiochip1 line 26: "[usb vbus oc]" unused input active-high
+[application] gpiochip1 line 27: "[hdmi audio]" "enable" output active-high
+[application] gpiochip1 line 28: "P9_12" "P9_12" input active-high
+[application] gpiochip1 line 29: "P8_26" "P8_26" input active-high
+[application] gpiochip1 line 30: "P8_21 [emmc]" "P8_21" input active-high
+[application] gpiochip1 line 31: "P8_20 [emmc]" "P8_20" input active-high
+[application] gpiochip2 line 00: "P9_15B" unused input active-high
+[application] gpiochip2 line 01: "P8_18" "P8_18" input active-high
+[application] gpiochip2 line 02: "P8_7" "P8_07" input active-high
+[application] gpiochip2 line 03: "P8_8" "P8_08" input active-high
+[application] gpiochip2 line 04: "P8_10" "P8_10" input active-high
+[application] gpiochip2 line 05: "P8_9" "P8_09" input active-high
+[application] gpiochip2 line 06: "P8_45 [hdmi]" "P8_45" input active-high
+[application] gpiochip2 line 07: "P8_46 [hdmi]" "P8_46" input active-high
+[application] gpiochip2 line 08: "P8_43 [hdmi]" "P8_43" input active-high
+[application] gpiochip2 line 09: "P8_44 [hdmi]" "P8_44" input active-high
+[application] gpiochip2 line 10: "P8_41 [hdmi]" "P8_41" input active-high
+[application] gpiochip2 line 11: "P8_42 [hdmi]" "P8_42" input active-high
+[application] gpiochip2 line 12: "P8_39 [hdmi]" "P8_39" input active-high
+[application] gpiochip2 line 13: "P8_40 [hdmi]" "P8_40" input active-high
+[application] gpiochip2 line 14: "P8_37 [hdmi]" "P8_37" input active-high
+[application] gpiochip2 line 15: "P8_38 [hdmi]" "P8_38" input active-high
+[application] gpiochip2 line 16: "P8_36 [hdmi]" "P8_36" input active-high
+[application] gpiochip2 line 17: "P8_34 [hdmi]" "P8_34" input active-high
+[application] gpiochip2 line 18: "[rmii1_rxd3]" unused input active-high
+[application] gpiochip2 line 19: "[rmii1_rxd2]" unused input active-high
+[application] gpiochip2 line 20: "[rmii1_rxd1]" unused input active-high
+[application] gpiochip2 line 21: "[rmii1_rxd0]" unused input active-high
+[application] gpiochip2 line 22: "P8_27 [hdmi]" "P8_27" input active-high
+[application] gpiochip2 line 23: "P8_29 [hdmi]" "P8_29" input active-high
+[application] gpiochip2 line 24: "P8_28 [hdmi]" "P8_28" input active-high
+[application] gpiochip2 line 25: "P8_30 [hdmi]" "P8_30" input active-high
+[application] gpiochip2 line 26: "[mmc0_dat3]" unused input active-high
+[application] gpiochip2 line 27: "[mmc0_dat2]" unused input active-high
+[application] gpiochip2 line 28: "[mmc0_dat1]" unused input active-high
+[application] gpiochip2 line 29: "[mmc0_dat0]" unused input active-high
+[application] gpiochip2 line 30: "[mmc0_clk]" unused input active-high
+[application] gpiochip2 line 31: "[mmc0_cmd]" unused input active-high
+[application] gpiochip3 line 00: "[mii col]" unused input active-high
+[application] gpiochip3 line 01: "[mii crs]" unused input active-high
+[application] gpiochip3 line 02: "[mii rx err]" unused input active-high
+[application] gpiochip3 line 03: "[mii tx en]" unused input active-high
+[application] gpiochip3 line 04: "[mii rx dv]" unused input active-high
+[application] gpiochip3 line 05: "[i2c0 sda]" unused input active-high
+[application] gpiochip3 line 06: "[i2c0 scl]" unused input active-high
+[application] gpiochip3 line 07: "[jtag emu0]" unused input active-high
+[application] gpiochip3 line 08: "[jtag emu1]" unused input active-high
+[application] gpiochip3 line 09: "[mii tx clk]" unused input active-high
+[application] gpiochip3 line 10: "[mii rx clk]" unused input active-high
+[application] gpiochip3 line 11: "NC" unused input active-high
+[application] gpiochip3 line 12: "NC" unused input active-high
+[application] gpiochip3 line 13: "[usb vbus en]" unused input active-high
+[application] gpiochip3 line 14: "P9_31 [spi1_sclk]" "P9_31" input active-high
+[application] gpiochip3 line 15: "P9_29 [spi1_d0]" "P9_29" input active-high
+[application] gpiochip3 line 16: "P9_30 [spi1_d1]" "P9_30" input active-high
+[application] gpiochip3 line 17: "P9_28 [spi1_cs0]" "P9_28" input active-high
+[application] gpiochip3 line 18: "P9_42B [ecappwm0]" "P9_92" input active-high
+[application] gpiochip3 line 19: "P9_27" "P9_27" input active-high
+[application] gpiochip3 line 20: "P9_41A" "P9_91" input active-high
+[application] gpiochip3 line 21: "P9_25" "P9_25" input active-high
+[application] gpiochip3 line 22: "NC" unused input active-high
+[application] gpiochip3 line 23: "NC" unused input active-high
+[application] gpiochip3 line 24: "NC" unused input active-high
+[application] gpiochip3 line 25: "NC" unused input active-high
+[application] gpiochip3 line 26: "NC" unused input active-high
+[application] gpiochip3 line 27: "NC" unused input active-high
+[application] gpiochip3 line 28: "NC" unused input active-high
+[application] gpiochip3 line 29: "NC" unused input active-high
+[application] gpiochip3 line 30: "NC" unused input active-high
+[application] gpiochip3 line 31: "NC" unused input active-high
+*/
\ No newline at end of file
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBonePwmPort.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBonePwmPort.cs
new file mode 100644
index 000000000..0a4f5e286
--- /dev/null
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/BeagleBone/BeagleBonePwmPort.cs
@@ -0,0 +1,130 @@
+using Meadow.Hardware;
+using Meadow.Units;
+using System;
+using System.IO;
+
+namespace Meadow.Pinouts;
+
+///
+/// Represents a PWM output port for BeagleBone
+///
+public class BeagleBonePwmPort : PwmPortBase
+{
+ // https://www.kernel.org/doc/html/v5.10/driver-api/pwm.html
+
+ private readonly string _devicePath;
+ private readonly string _enablePath;
+ private readonly string _periodPath;
+ private readonly string _polarityPath;
+ private readonly string _dutyCyclePath;
+
+ private const string PolarityNormal = "normal";
+ private const string PolarityInverted = "inversed";
+
+ ///
+ public override TimePeriod Duration { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+
+ ///
+ public override bool State => ReadFileValue(_enablePath) != 0;
+
+ internal BeagleBonePwmPort(IPin pin, IPwmChannelInfo channel, Frequency frequency, float dutyCycle, bool inverted)
+ : base(pin, channel)
+ {
+ _devicePath = $"/sys/class/pwm/pwmchip{channel.Timer}/pwm{channel.TimerChannel}";
+ _enablePath = Path.Combine(_devicePath, "enable");
+ _periodPath = Path.Combine(_devicePath, "period");
+ _polarityPath = Path.Combine(_devicePath, "polarity");
+ _dutyCyclePath = Path.Combine(_devicePath, "duty_cycle");
+
+ Frequency = frequency;
+ DutyCycle = dutyCycle;
+ Inverted = inverted;
+ }
+
+ ///
+ public override bool Inverted
+ {
+ get => File.ReadAllText(_polarityPath) == PolarityInverted;
+ set
+ {
+ if (_polarityPath == null) return;
+ File.WriteAllText(_polarityPath, value ? PolarityInverted : PolarityNormal);
+ }
+ }
+
+ ///
+ public override Frequency Frequency
+ {
+ get => (1.0 / Period.Seconds).Hertz();
+ set => Period = TimePeriod.FromSeconds(1 / value.Hertz);
+ }
+
+ ///
+ public override double DutyCycle
+ {
+ get => ReadFileValue(_dutyCyclePath) / (Period.Nanoseconds);
+ set
+ {
+ SetPeriodAndDuty(Period, value);
+ }
+ }
+
+ ///
+ public override TimePeriod Period
+ {
+ get => TimePeriod.FromMicroseconds(ReadFileValue(_periodPath) / 1000d);
+ set
+ {
+ SetPeriodAndDuty(value, DutyCycle);
+ }
+ }
+
+ private void SetPeriodAndDuty(TimePeriod period, double duty)
+ {
+ if (_periodPath == null || _dutyCyclePath == null) return;
+
+ if (duty < 0) duty = 0;
+ if (duty > 1.0) duty = 1.0;
+
+ var dutyNs = (long)(period.Nanoseconds * duty);
+
+ File.WriteAllText(_periodPath, ((long)period.Nanoseconds).ToString());
+ File.WriteAllText(_dutyCyclePath, dutyNs.ToString());
+ }
+
+ private int ReadFileValue(string path)
+ {
+ return int.Parse(File.ReadAllText(path));
+ }
+
+ ///
+ public override void Start()
+ {
+ File.WriteAllText(_enablePath, "1");
+ }
+
+ ///
+ public override void Stop()
+ {
+ File.WriteAllText(_enablePath, "0");
+ }
+}
+
+// 0 => 1
+// 1 => 1
+// 2 => 1
+// 3 => 2
+// 5 => 2
+// 7 => 2
+/*
+ADDRESS CONTROLLER SYSFS POSSIBLE PINS
+------- ---------- ----- -------------
+0x48302200 EHRPWM1A pwmchip0-0 P9_14, P8_36
+0x48302200 EHRPWM1B pwmchip0-1 P9_16, P8_34
+0x48300200 EHRPWM0A pwmchip1-0 P9_22, P9_31
+0x48300200 EHRPWM0B pwmchip1-1 P9_21, P9_29
+0x48300100 ECAPPWM0 pwmchip3-0 P9_42
+0x48304180 ECAPPWM2 pwmchip3-1 P9_28
+0x48304180 EHRPWM2A pwmchip6-0 P8_19, P8_45
+0x48304180 EHRPWM2B pwmchip6-1 P8_13, P8_46
+*/
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Desktop/DesktopLinux.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Desktop/DesktopLinux.cs
new file mode 100644
index 000000000..f137b9c68
--- /dev/null
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Desktop/DesktopLinux.cs
@@ -0,0 +1,19 @@
+namespace Meadow.Pinouts;
+
+///
+/// Represents a generic desktop Linux operating system.
+///
+public class DesktopLinux : Linux
+{
+ ///
+ public EmptyPinout Pins { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DesktopLinux()
+ {
+ Pins = new EmptyPinout();
+ Pins.Controller = this;
+ }
+}
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Pinouts/WSL2.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Desktop/EmptyPinout.cs
similarity index 78%
rename from source/implementations/linux/Meadow.Linux/Hardware/Pinouts/WSL2.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/Desktop/EmptyPinout.cs
index 737f7b64d..87c337b89 100644
--- a/source/implementations/linux/Meadow.Linux/Hardware/Pinouts/WSL2.cs
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Desktop/EmptyPinout.cs
@@ -5,9 +5,9 @@
namespace Meadow.Pinouts;
///
-/// Defines the pinout configuration for Windows Subsystem for Linux (WSL)
+/// Defines the an empty pinout configuration
///
-public class WSL2 : IPinDefinitions
+public class EmptyPinout : IPinDefinitions
{
///
public IEnumerator GetEnumerator() => AllPins.GetEnumerator();
@@ -19,7 +19,7 @@ public class WSL2 : IPinDefinitions
///
public IPinController? Controller { get; set; }
- internal WSL2()
+ internal EmptyPinout()
{
}
}
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/JetsonNano.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonNano.cs
similarity index 100%
rename from source/implementations/linux/Meadow.Linux/Hardware/JetsonNano.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonNano.cs
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Pinouts/JetsonNanoPinout.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonNanoPinout.cs
similarity index 87%
rename from source/implementations/linux/Meadow.Linux/Hardware/Pinouts/JetsonNanoPinout.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonNanoPinout.cs
index 0f61fa24a..2ef7285ce 100644
--- a/source/implementations/linux/Meadow.Linux/Hardware/Pinouts/JetsonNanoPinout.cs
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonNanoPinout.cs
@@ -12,11 +12,20 @@ internal JetsonNanoPinout() { }
///
/// Represents the I2C_2_SDA pin.
///
- public IPin I2C_2_SDA => new Pin(Controller, "I2C_2_SDA", "PIN03", null);
+ public IPin I2C_2_SDA => new Pin(Controller, "I2C_2_SDA", "PIN03",
+ new IChannelInfo[]
+ {
+ new I2cChannelInfo("I2C_2_SDA", I2cChannelFunctionType.Data, busNumber: 2)
+ });
+
///
/// Represents the I2C_2_SCL pin.
///
- public IPin I2C_2_SCL => new Pin(Controller, "I2C_2_SCL", "PIN05", null);
+ public IPin I2C_2_SCL => new Pin(Controller, "I2C_2_SCL", "PIN05",
+ new IChannelInfo[]
+ {
+ new I2cChannelInfo("I2C_2_SCL", I2cChannelFunctionType.Clock, busNumber: 2)
+ });
///
/// Represents the D04 pin.
@@ -85,11 +94,20 @@ internal JetsonNanoPinout() { }
///
/// Represents the I2C_1_SDA pin.
///
- public IPin I2C_1_SDA => new Pin(Controller, "I2C_1_SDA", "PIN27", null);
+ public IPin I2C_1_SDA => new Pin(Controller, "I2C_1_SDA", "PIN27",
+ new IChannelInfo[]
+ {
+ new I2cChannelInfo("I2C_1_SDA", I2cChannelFunctionType.Data, busNumber: 1)
+ });
+
///
/// Represents the I2C_1_SCL pin.
///
- public IPin I2C_1_SCL => new Pin(Controller, "I2C_1_SCL", "PIN28", null);
+ public IPin I2C_1_SCL => new Pin(Controller, "I2C_1_SCL", "PIN28",
+ new IChannelInfo[]
+ {
+ new I2cChannelInfo("I2C_1_SCL", I2cChannelFunctionType.Clock, busNumber: 1)
+ });
///
/// Represents the D05 pin.
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Pinouts/JetsonXavierAGXPinout.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonXavierAGXPinout.cs
similarity index 84%
rename from source/implementations/linux/Meadow.Linux/Hardware/Pinouts/JetsonXavierAGXPinout.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonXavierAGXPinout.cs
index 4300980fe..3f8e1de72 100644
--- a/source/implementations/linux/Meadow.Linux/Hardware/Pinouts/JetsonXavierAGXPinout.cs
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonXavierAGXPinout.cs
@@ -12,12 +12,20 @@ internal JetsonXavierAGXPinout() { }
///
/// Gets the I2C_GP5_DAT pin.
///
- public IPin I2C_GP5_DAT => new Pin(Controller, "I2C_GP5_DAT", "PIN03", null);
+ public IPin I2C_GP5_DAT => new Pin(Controller, "I2C_GP5_DAT", "PIN03",
+ new IChannelInfo[]
+ {
+ new I2cChannelInfo("I2C_GP5_DAT", I2cChannelFunctionType.Data, busNumber: 8)
+ });
///
/// Gets the I2C_GP5_CLK pin.
///
- public IPin I2C_GP5_CLK => new Pin(Controller, "I2C_GP5_CLK", "PIN05", null);
+ public IPin I2C_GP5_CLK => new Pin(Controller, "I2C_GP5_CLK", "PIN05",
+ new IChannelInfo[]
+ {
+ new I2cChannelInfo("I2C_GP5_CLK", I2cChannelFunctionType.Clock, busNumber: 8)
+ });
///
/// Gets the MCLK05 pin.
@@ -57,12 +65,22 @@ internal JetsonXavierAGXPinout() { }
///
/// Gets the SPI1_MOSI pin.
///
- public IPin SPI1_MOSI => new SysFsPin(Controller, "SPI1_MOSI", "PIN19", 493);
+ public IPin SPI1_MOSI => new SysFsPin(Controller, "SPI1_MOSI", "PIN19", 493,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("SPI1_MOSI"),
+ new SpiChannelInfo("SPI1_MOSI", SpiLineType.MOSI, busNumber: 1)
+ });
///
/// Gets the SPI1_MISO pin.
///
- public IPin SPI1_MISO => new SysFsPin(Controller, "SPI1_MISO", "PIN21", 492);
+ public IPin SPI1_MISO => new SysFsPin(Controller, "SPI1_MISO", "PIN21", 492,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("SPI1_MISO"),
+ new SpiChannelInfo("SPI1_MISO", SpiLineType.MISO, busNumber: 1)
+ });
///
/// Gets the GPIO17_40HEADER pin.
@@ -72,7 +90,12 @@ internal JetsonXavierAGXPinout() { }
///
/// Gets the SPI1_SCLK pin.
///
- public IPin SPI1_SCLK => new SysFsPin(Controller, "SPI1_SCLK", "PIN23", 491);
+ public IPin SPI1_SCLK => new SysFsPin(Controller, "SPI1_SCLK", "PIN23", 491,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("SPI1_SCLK"),
+ new SpiChannelInfo("SPI1_SCLK", SpiLineType.Clock, busNumber: 1)
+ });
///
/// Gets the SPI1_CS0 pin.
@@ -87,12 +110,20 @@ internal JetsonXavierAGXPinout() { }
///
/// Gets the I2C_GP2_DAT pin.
///
- public IPin I2C_GP2_DAT => new Pin(Controller, "I2C_GP2_DAT", "PIN27", null);
+ public IPin I2C_GP2_DAT => new Pin(Controller, "I2C_GP2_DAT", "PIN27",
+ new IChannelInfo[]
+ {
+ new I2cChannelInfo("I2C_GP2_DAT", I2cChannelFunctionType.Data, busNumber: 1)
+ });
///
/// Gets the I2C_GP2_CLK pin.
///
- public IPin I2C_GP2_CLK => new Pin(Controller, "I2C_GP2_CLK", "PIN28", null);
+ public IPin I2C_GP2_CLK => new Pin(Controller, "I2C_GP2_CLK", "PIN28",
+ new IChannelInfo[]
+ {
+ new I2cChannelInfo("I2C_GP2_CLK", I2cChannelFunctionType.Clock, busNumber: 1)
+ });
///
/// Gets the CAN0_DIN pin.
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/JetsonXavierAgx.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonXavierAgx.cs
similarity index 70%
rename from source/implementations/linux/Meadow.Linux/Hardware/JetsonXavierAgx.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonXavierAgx.cs
index 548408f57..63ccdb6e4 100644
--- a/source/implementations/linux/Meadow.Linux/Hardware/JetsonXavierAgx.cs
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/NVIDIA/JetsonXavierAgx.cs
@@ -1,6 +1,5 @@
using Meadow.Hardware;
using Meadow.Pinouts;
-using System;
using System.Linq;
namespace Meadow;
@@ -44,19 +43,4 @@ public override IPin GetPin(string pinName)
{
return Pins.AllPins.First(p => string.Compare(p.Name, pinName) == 0);
}
-
- ///
- public override II2cBus CreateI2cBus(IPin clock, IPin data, I2cBusSpeed busSpeed)
- {
- if (clock == Pins["I2C_GP2_CLK"] && data == Pins["I2C_GP2_DAT"])
- {
- return new I2CBus(1, busSpeed);
- }
- else if (clock == Pins["I2C_GP5_CLK"] && data == Pins["I2C_GP5_DAT"])
- {
- return new I2CBus(8, busSpeed);
- }
-
- throw new ArgumentOutOfRangeException("Requested pins are not I2C bus pins");
- }
}
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/RaspberryPi.LedOutputPort.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPi.LedOutputPort.cs
similarity index 100%
rename from source/implementations/linux/Meadow.Linux/Hardware/RaspberryPi.LedOutputPort.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPi.LedOutputPort.cs
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/RaspberryPi.LedPin.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPi.LedPin.cs
similarity index 100%
rename from source/implementations/linux/Meadow.Linux/Hardware/RaspberryPi.LedPin.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPi.LedPin.cs
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/RaspberryPi.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPi.cs
similarity index 86%
rename from source/implementations/linux/Meadow.Linux/Hardware/RaspberryPi.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPi.cs
index e63ec009b..deafcdc97 100644
--- a/source/implementations/linux/Meadow.Linux/Hardware/RaspberryPi.cs
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPi.cs
@@ -73,17 +73,6 @@ public override IPin GetPin(string pinName)
return Pins.AllPins.First(p => string.Compare(p.Name, pinName) == 0);
}
- ///
- public override II2cBus CreateI2cBus(IPin clock, IPin data, I2cBusSpeed busSpeed)
- {
- if (clock == Pins["PIN05"] && data == Pins["PIN03"])
- {
- return new I2CBus(1, busSpeed);
- }
-
- throw new ArgumentOutOfRangeException("Requested pins are not I2C bus pins");
- }
-
///
public override ISpiBus CreateSpiBus(IPin clock, IPin mosi, IPin miso, SpiClockConfiguration.Mode mode, Frequency speed)
{
@@ -92,11 +81,9 @@ public override ISpiBus CreateSpiBus(IPin clock, IPin mosi, IPin miso, SpiClockC
// just switch on the clock, assume they did the rest right
if (clock.Key.ToString() == "PIN40")
{
- Resolver.Log.Info($"EQUAL");
return new SpiBus(1, 0, (SpiBus.SpiMode)mode, speed);
}
- Resolver.Log.Info($"NOT {clock.Key.ToString()}");
return new SpiBus(0, 0, (SpiBus.SpiMode)mode, speed);
}
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Pinouts/RaspberryPiPinout.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPiPinout.cs
similarity index 84%
rename from source/implementations/linux/Meadow.Linux/Hardware/Pinouts/RaspberryPiPinout.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPiPinout.cs
index b81ca6534..01cb68bb3 100644
--- a/source/implementations/linux/Meadow.Linux/Hardware/Pinouts/RaspberryPiPinout.cs
+++ b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/Raspberry Pi/RaspberryPiPinout.cs
@@ -30,11 +30,23 @@ internal RaspberryPiPinout(
///
/// Represents the GPIO2 pin.
///
- public IPin GPIO2 => new LinuxFlexiPin(Controller, "GPIO2", "PIN03", 2 + SysFsOffset, GpiodChipName, 2);
+ public IPin GPIO2 => new LinuxFlexiPin(Controller, "GPIO2", "PIN03", 2 + SysFsOffset, GpiodChipName, 2,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("GPIO2"),
+ new I2cChannelInfo("SDA1", I2cChannelFunctionType.Data, busNumber: 1)
+ });
+
///
/// Represents the GPIO3 pin.
///
- public IPin GPIO3 => new LinuxFlexiPin(Controller, "GPIO3", "PIN05", 3 + SysFsOffset, GpiodChipName, 3);
+ public IPin GPIO3 => new LinuxFlexiPin(Controller, "GPIO3", "PIN05", 3 + SysFsOffset, GpiodChipName, 3,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("GPIO3"),
+ new I2cChannelInfo("SCL1", I2cChannelFunctionType.Clock, busNumber: 1)
+ });
+
///
/// Represents the GPIO4 pin.
///
@@ -66,11 +78,23 @@ internal RaspberryPiPinout(
///
/// Represents the GPIO10 pin.
///
- public IPin GPIO10 => new LinuxFlexiPin(Controller, "GPIO10", "PIN19", 10 + SysFsOffset, GpiodChipName, 10);
+ public IPin GPIO10 => new LinuxFlexiPin(Controller, "GPIO10", "PIN19", 10 + SysFsOffset, GpiodChipName, 10,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("GPIO10"),
+ new SpiChannelInfo("SPI0_MOSI", SpiLineType.MOSI, busNumber: 0)
+ });
+
///
/// Represents the GPIO9 pin.
///
- public IPin GPIO9 => new LinuxFlexiPin(Controller, "GPIO9", "PIN21", 9 + SysFsOffset, GpiodChipName, 9);
+ public IPin GPIO9 => new LinuxFlexiPin(Controller, "GPIO9", "PIN21", 9 + SysFsOffset, GpiodChipName, 9,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("GPIO9"),
+ new SpiChannelInfo("SPI0_MISO", SpiLineType.MISO, busNumber: 0)
+ });
+
///
/// Represents the GPIO25 pin.
///
@@ -78,7 +102,13 @@ internal RaspberryPiPinout(
///
/// Represents the GPIO11 pin.
///
- public IPin GPIO11 => new LinuxFlexiPin(Controller, "GPIO11", "PIN23", 11 + SysFsOffset, GpiodChipName, 11);
+ public IPin GPIO11 => new LinuxFlexiPin(Controller, "GPIO11", "PIN23", 11 + SysFsOffset, GpiodChipName, 11,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("GPIO11"),
+ new SpiChannelInfo("SPI0_SCLK", SpiLineType.Clock, busNumber: 0)
+ });
+
///
/// Represents the GPIO8 pin.
///
@@ -114,7 +144,13 @@ internal RaspberryPiPinout(
///
/// Represents the GPIO19 pin.
///
- public IPin GPIO19 => new LinuxFlexiPin(Controller, "GPIO19", "PIN35", 19 + SysFsOffset, GpiodChipName, 19);
+ public IPin GPIO19 => new LinuxFlexiPin(Controller, "GPIO19", "PIN35", 19 + SysFsOffset, GpiodChipName, 19,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("GPIO19"),
+ new SpiChannelInfo("SPI1_MISO", SpiLineType.Clock, busNumber: 1)
+ });
+
///
/// Represents the GPIO16 pin.
///
@@ -126,11 +162,22 @@ internal RaspberryPiPinout(
///
/// Represents the GPIO20 pin.
///
- public IPin GPIO20 => new LinuxFlexiPin(Controller, "GPIO20", "PIN38", 20 + SysFsOffset, GpiodChipName, 20);
+ public IPin GPIO20 => new LinuxFlexiPin(Controller, "GPIO20", "PIN38", 20 + SysFsOffset, GpiodChipName, 20,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("GPIO20"),
+ new SpiChannelInfo("SPI1_MOSI", SpiLineType.Clock, busNumber: 1)
+ });
+
///
/// Represents the GPIO21 pin.
///
- public IPin GPIO21 => new LinuxFlexiPin(Controller, "GPIO21", "PIN40", 21 + SysFsOffset, GpiodChipName, 21);
+ public IPin GPIO21 => new LinuxFlexiPin(Controller, "GPIO21", "PIN40", 21 + SysFsOffset, GpiodChipName, 21,
+ new IChannelInfo[]
+ {
+ new GpiodDigitalChannelInfo("GPIO21"),
+ new SpiChannelInfo("SPI1_SCLK", SpiLineType.Clock, busNumber: 1)
+ });
///
/// Represents Pin 3, which corresponds to GPIO2.
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Communications/KrtklI2CBus.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/krtkl/KrtklI2CBus.cs
similarity index 100%
rename from source/implementations/linux/Meadow.Linux/Hardware/Communications/KrtklI2CBus.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/krtkl/KrtklI2CBus.cs
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/SnickerdoodleBlack.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/krtkl/SnickerdoodleBlack.cs
similarity index 100%
rename from source/implementations/linux/Meadow.Linux/Hardware/SnickerdoodleBlack.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/krtkl/SnickerdoodleBlack.cs
diff --git a/source/implementations/linux/Meadow.Linux/Hardware/Pinouts/SnickerdoodleBlackPinout.cs b/source/implementations/linux/Meadow.Linux/Hardware/Platforms/krtkl/SnickerdoodleBlackPinout.cs
similarity index 100%
rename from source/implementations/linux/Meadow.Linux/Hardware/Pinouts/SnickerdoodleBlackPinout.cs
rename to source/implementations/linux/Meadow.Linux/Hardware/Platforms/krtkl/SnickerdoodleBlackPinout.cs
diff --git a/source/implementations/linux/Meadow.Linux/Interop/Interop.Time.cs b/source/implementations/linux/Meadow.Linux/Interop/Interop.Time.cs
new file mode 100644
index 000000000..4131f6a43
--- /dev/null
+++ b/source/implementations/linux/Meadow.Linux/Interop/Interop.Time.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+
+namespace Meadow
+{
+ [SuppressUnmanagedCodeSecurity]
+ internal static partial class Interop
+ {
+
+ [DllImport(LIBC, SetLastError = true)]
+ public static extern void tzset();
+
+ [DllImport(LIBC, SetLastError = true)]
+ public static extern ref Rtc_time localtime(ref long time_t);
+
+ [DllImport(LIBC, SetLastError = true)]
+ public static extern IntPtr localtime_r(ref long time_t, ref Tm tm);
+
+ [DllImport(LIBC, SetLastError = true)]
+ public static extern IntPtr gmtime_r(ref long time_t, ref Tm tm);
+
+ [DllImport(LIBC, SetLastError = true)]
+ public static extern int clock_settime(Clock clock, ref Timespec timespec);
+
+ public enum Clock
+ {
+ REALTIME = 0,
+ PROCESS_CPUTIME_ID = 2,
+ MONOTONIC = 3,
+ THREAD_CPUTIME_ID = 4,
+ UPTIME = 5,
+ BOOTTIME = 6
+ //#define CLOCK_REALTIME 0
+ //#define CLOCK_PROCESS_CPUTIME_ID 2
+ //#define CLOCK_MONOTONIC 3
+ //#define CLOCK_THREAD_CPUTIME_ID 4
+ //#define CLOCK_UPTIME 5
+ //#define CLOCK_BOOTTIME 6
+ }
+
+ public struct Tm
+ {
+ public int tm_sec;
+ public int tm_min;
+ public int tm_hour;
+ public int tm_mday;
+ public int tm_mon;
+ public int tm_year;
+ public int tm_wday;
+ public int tm_yday;
+ public int tm_isdst;
+ // public long tm_gmtoff; /* Seconds east of UTC */
+ // public string tm_zone; /* Timezone abbreviation */
+ }
+
+ public struct Rtc_time
+ {
+ /*
+ private struct rtc_time
+ {
+ private int tm_sec;
+ private int tm_min;
+ private int tm_hour;
+ private int tm_mday;
+ private int tm_mon;
+ private int tm_year;
+ private int tm_wday; // unused
+ private int tm_yday; // unused
+ private int tm_isdst;// unused
+ };
+ */
+ public int tm_sec;
+ public int tm_min;
+ public int tm_hour;
+ public int tm_mday;
+ public int tm_mon;
+ public int tm_year;
+ public int tm_wday;
+ public int tm_yday;
+ public int tm_isdst;
+
+ public static Rtc_time From(DateTime dt)
+ {
+ return From((DateTimeOffset)dt);
+ }
+
+ public static Rtc_time From(DateTimeOffset dto)
+ {
+ return new Rtc_time
+ {
+ tm_sec = dto.Second,
+ tm_min = dto.Minute,
+ tm_hour = dto.Hour,
+ tm_mday = dto.Day,
+ tm_mon = dto.Month,
+ tm_year = dto.Year
+ };
+ }
+ }
+
+ public struct Timespec
+ {
+ /*
+ typedef __int64_t __time_t;
+ struct timespec {
+ time_t tv_sec;
+ long tv_nsec;
+ };
+ */
+ public long tv_sec;
+ public long tv_nsec;
+
+ public static Timespec From(DateTime dt)
+ {
+ return From((DateTimeOffset)dt);
+ }
+
+ public static Timespec From(DateTimeOffset dto)
+ {
+ return new Timespec
+ {
+ tv_sec = dto.ToUnixTimeSeconds(),
+ tv_nsec = dto.Millisecond * 1000000
+ };
+ }
+ }
+ }
+}
diff --git a/source/implementations/linux/Meadow.Linux/Interop/Interop.cs b/source/implementations/linux/Meadow.Linux/Interop/Interop.cs
index 87b9f85da..c353690bc 100644
--- a/source/implementations/linux/Meadow.Linux/Interop/Interop.cs
+++ b/source/implementations/linux/Meadow.Linux/Interop/Interop.cs
@@ -1,5 +1,4 @@
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
+using System.Runtime.InteropServices;
using System.Security;
namespace Meadow
@@ -18,18 +17,18 @@ internal static partial class Interop
[DllImport(LIBC, SetLastError = true)]
public static extern int close(int handle);
-
+
[DllImport(LIBC, SetLastError = true)]
public static extern int write(int handle, byte[] buf, int count);
[DllImport(LIBC, SetLastError = true)]
- public unsafe static extern int write(int handle, byte* buf, int count);
+ public static extern unsafe int write(int handle, byte* buf, int count);
[DllImport(LIBC, SetLastError = true)]
public static extern int read(int handle, byte[] buf, int count);
[DllImport(LIBC, SetLastError = true)]
- public unsafe static extern int read(int handle, byte* buf, int count);
+ public static extern unsafe int read(int handle, byte* buf, int count);
//int tcgetattr(int fildes, struct termios *termios_p);
[DllImport(LIBC, SetLastError = true)]
@@ -44,7 +43,7 @@ internal static partial class Interop
[DllImport(LIBC, SetLastError = true)]
public static extern int lseek(int fd, int offset, SeekWhence whence);
-
+
[DllImport(LIBC, SetLastError = true)]
public static extern int poll(pollfd[] fds, int nfds, int timeout);
@@ -57,5 +56,14 @@ public enum SeekWhence
SEEK_CUR = 1, /* set file offset to current plus offset */
SEEK_END = 2 /* set file offset to EOF plus offset */
}
+
+ public enum Errors
+ {
+ None = 0,
+ NoPermissions = 1,
+ NoSuchFile = 2,
+ BadFileNumber = 9,
+ DeviceBusy = 16
+ }
}
}
diff --git a/source/implementations/linux/Meadow.Linux/Interop/Interop.ioctl.cs b/source/implementations/linux/Meadow.Linux/Interop/Interop.ioctl.cs
index be5e76208..5e33c0ebd 100644
--- a/source/implementations/linux/Meadow.Linux/Interop/Interop.ioctl.cs
+++ b/source/implementations/linux/Meadow.Linux/Interop/Interop.ioctl.cs
@@ -63,5 +63,11 @@ public static int _IOWR(int type, int nr, int size)
[DllImport(LIBC, SetLastError = true)]
public static extern int ioctl(int fd, int request, ref uint data);
+
+ public static class Ioctl
+ {
+ // #define RTC_SET_TIME _IOW('p', 0x0a, struct rtc_time) /* Set RTC time */
+ public static int RTC_SET_TIME => _IOW('p', 0x0a, 9 * 4);
+ }
}
}
diff --git a/source/implementations/linux/Meadow.Linux/Linux.cs b/source/implementations/linux/Meadow.Linux/Linux.cs
index a1a4b1ad3..110cbbf6f 100644
--- a/source/implementations/linux/Meadow.Linux/Linux.cs
+++ b/source/implementations/linux/Meadow.Linux/Linux.cs
@@ -5,13 +5,14 @@
using Meadow.Units;
using System;
using System.Diagnostics;
+using System.Linq;
namespace Meadow;
///
/// Represents a Linux-based Meadow device.
///
-public class Linux : IMeadowDevice
+public abstract class Linux : IMeadowDevice
#if NET7_0
, IPixelDisplayProvider
#endif
@@ -87,7 +88,7 @@ public virtual IPin GetPin(string pinName)
}
///
- public II2cBus CreateI2cBus(int busNumber = 1)
+ public virtual II2cBus CreateI2cBus(int busNumber = 1)
{
return CreateI2cBus(busNumber, II2cController.DefaultI2cBusSpeed);
}
@@ -174,7 +175,7 @@ public IDigitalInterruptPort CreateDigitalInterruptPort(IPin pin, InterruptMode
}
///
- public ISpiBus CreateSpiBus(int busNumber, Units.Frequency speed)
+ public virtual ISpiBus CreateSpiBus(int busNumber, Units.Frequency speed)
{
return new SpiBus(busNumber, 0, SpiBus.SpiMode.Mode0, speed);
}
@@ -194,17 +195,52 @@ public ISpiBus CreateSpiBus(IPin clock, IPin mosi, IPin miso, Units.Frequency sp
///
public virtual ISpiBus CreateSpiBus(IPin clock, IPin mosi, IPin miso, SpiClockConfiguration.Mode mode, Units.Frequency speed)
{
- return new SpiBus(0, 0, (SpiBus.SpiMode)mode, speed);
+ // verify pins are SPI capable
+ var clockChannel = clock.SupportedChannels
+ ?.OfType()
+ .Where(c => c.LineTypes == SpiLineType.Clock)
+ .FirstOrDefault();
+ if (clockChannel == null)
+ {
+ throw new ArgumentException($"Pin {clock.Name} does not support SPI Clock");
+ }
+
+ var mosiChannel = mosi.SupportedChannels
+ ?.OfType()
+ .Where(c => c.LineTypes == SpiLineType.MOSI)
+ .FirstOrDefault();
+ if (mosiChannel == null)
+ {
+ throw new ArgumentException($"Pin {mosi.Name} does not support SPI MOSI");
+ }
+
+ var misoChannel = miso.SupportedChannels
+ ?.OfType()
+ .Where(c => c.LineTypes == SpiLineType.MISO)
+ .FirstOrDefault();
+ if (misoChannel == null)
+ {
+ throw new ArgumentException($"Pin {miso.Name} does not support SPI MISO");
+ }
+
+ if ((clockChannel.BusNumber != mosiChannel.BusNumber)
+ || (clockChannel.BusNumber != misoChannel.BusNumber)
+ || (mosiChannel.BusNumber != misoChannel.BusNumber))
+ {
+ throw new ArgumentException($"Pins {clock.Name}, {mosi.Name} and {miso.Name} are on different SPI buses");
+ }
+
+ return CreateSpiBus(clockChannel.BusNumber, speed);
}
///
public IAnalogInputPort CreateAnalogInputPort(IPin pin, int sampleCount, TimeSpan sampleInterval, float voltageReference = 3.3F)
{
- throw new PlatformNotSupportedException("This platform does not support analog inputs. Use an IO Extender.");
+ return CreateAnalogInputPort(pin, sampleCount, sampleInterval, voltageReference.Volts());
}
///
- public IAnalogInputPort CreateAnalogInputPort(IPin pin, int sampleCount, TimeSpan sampleInterval, Voltage voltageReference)
+ public virtual IAnalogInputPort CreateAnalogInputPort(IPin pin, int sampleCount, TimeSpan sampleInterval, Voltage voltageReference)
{
throw new PlatformNotSupportedException("This platform does not support analog inputs. Use an IO Extender.");
}
@@ -218,7 +254,33 @@ public virtual IPwmPort CreatePwmPort(IPin pin, Frequency frequency, float dutyC
///
public virtual II2cBus CreateI2cBus(IPin clock, IPin data, I2cBusSpeed busSpeed)
{
- throw new PlatformNotSupportedException("This platform has no II2CBusses. Use an IO Extender.");
+ // verify pins are I2C capable
+ var clockChannel = clock.SupportedChannels
+ ?.OfType()
+ .Where(c => c.ChannelFunction == I2cChannelFunctionType.Clock)
+ .FirstOrDefault();
+
+ if (clockChannel == null)
+ {
+ throw new ArgumentException($"Pin {clock.Name} does not support I2C Clock");
+ }
+
+ var dataChannel = data.SupportedChannels
+ ?.OfType()
+ .Where(c => c.ChannelFunction == I2cChannelFunctionType.Data)
+ .FirstOrDefault();
+
+ if (dataChannel == null)
+ {
+ throw new ArgumentException($"Pin {data.Name} does not support I2C Data");
+ }
+
+ if (clockChannel.BusNumber != dataChannel.BusNumber)
+ {
+ throw new ArgumentException($"Pins {data.Name} and {clock.Name} are on different I2C buses");
+ }
+
+ return CreateI2cBus(clockChannel.BusNumber, busSpeed);
}
///
@@ -248,6 +310,12 @@ internal static string ExecuteCommandLine(string command, string args)
return process?.StandardOutput.ReadToEnd() ?? string.Empty;
}
+ ///
+ public IDigitalSignalAnalyzer CreateDigitalSignalAnalyzer(IPin pin, bool captureDutyCycle)
+ {
+ return new SoftDigitalSignalAnalyzer(pin, captureDutyCycle: captureDutyCycle);
+ }
+
// ----------------------------------------------
// ----------------------------------------------
// ----- BELOW HERE ARE NOT YET IMPLEMENTED -----
diff --git a/source/implementations/linux/Meadow.Linux/Meadow.Linux.csproj b/source/implementations/linux/Meadow.Linux/Meadow.Linux.csproj
index 345ea1a5b..4f2eec692 100644
--- a/source/implementations/linux/Meadow.Linux/Meadow.Linux.csproj
+++ b/source/implementations/linux/Meadow.Linux/Meadow.Linux.csproj
@@ -22,9 +22,12 @@
True
+
+
-
-
+
+
+
diff --git a/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxNtpClient.cs b/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxNtpClient.cs
index 9649987bc..2000dae71 100644
--- a/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxNtpClient.cs
+++ b/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxNtpClient.cs
@@ -1,27 +1,19 @@
using System;
-using System.Threading.Tasks;
namespace Meadow;
///
/// Represents an NTP (Network Time Protocol) client for Linux.
///
-public class LinuxNtpClient : INtpClient
+public class LinuxNtpClient : NtpClientBase
{
- ///
- public bool Enabled => false;
-
- ///
- public TimeSpan PollPeriod { get; set; }
+ internal LinuxNtpClient()
+ {
+ }
///
- public event TimeChangedEventHandler? TimeChanged;
+ public override bool Enabled => false;
///
- public Task Synchronize(string? ntpServer = null)
- {
- TimeChanged?.Invoke(DateTime.UtcNow);
-
- throw new NotImplementedException();
- }
+ public override TimeSpan PollPeriod { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
}
diff --git a/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxPlatformOS.cs b/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxPlatformOS.cs
index cd63f324c..cbc25985f 100644
--- a/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxPlatformOS.cs
+++ b/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxPlatformOS.cs
@@ -6,9 +6,11 @@
using System.IO;
using System.IO.Ports;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
+using static Meadow.Resolver;
namespace Meadow;
@@ -95,7 +97,7 @@ public void Initialize(DeviceCapabilities capabilities, string[]? args)
}
catch (Exception ex)
{
- Resolver.Log.Debug($"Unable to parse lsb_release: {ex.Message}");
+ Log.Debug($"Unable to parse lsb_release: {ex.Message}");
}
try
@@ -111,10 +113,13 @@ public void Initialize(DeviceCapabilities capabilities, string[]? args)
}
catch (Exception ex)
{
- Resolver.Log.Debug($"Unable to parse uname: {ex.Message}");
+ Log.Debug($"Unable to parse uname: {ex.Message}");
}
}
+ ///
+ public string[] NtpServers => new string[] { "pool.ntp.org" };
+
///
/// Gets the name of all available serial ports on the platform
///
@@ -149,7 +154,83 @@ public virtual Temperature GetCpuTemperature()
///
public void SetClock(DateTime dateTime)
{
- throw new PlatformNotSupportedException();
+ if (dateTime.Kind != DateTimeKind.Utc)
+ {
+ TimeZoneInfo localZone = TimeZoneInfo.Local;
+ dateTime = dateTime.AddMinutes(localZone.BaseUtcOffset.TotalMinutes);
+
+ if (localZone.IsDaylightSavingTime(dateTime))
+ {
+ dateTime = dateTime.AddHours(1);
+ }
+ }
+
+ var ts = Interop.Timespec.From(dateTime);
+ var result = Interop.clock_settime(Interop.Clock.REALTIME, ref ts);
+
+ if (result != 0)
+ {
+ var err = Marshal.GetLastWin32Error();
+
+ string errorMessage;
+
+ if (err == 1)
+ {
+ // this is a permissions issue
+ errorMessage = "Application does not have permissions to set the clock. Try running \"sudo setcap 'cap_sys_time=ep' $DOTNET_ROOT/dotnet\"";
+ }
+ else
+ {
+ errorMessage = $"Failed to set clock. Error code {err}";
+ }
+
+ throw new NativeException(errorMessage, err);
+ }
+
+ // synchronize the system time to the hardware clock (not necessarily an RTC)
+ SetHwClock(dateTime);
+ }
+
+ private unsafe void SetHwClock(DateTime dateTime)
+ {
+ var rtcTime = Interop.Rtc_time.From(dateTime);
+ var fd = Interop.open("/dev/rtc", Interop.DriverFlags.O_RDONLY);
+ if (fd != 0)
+ {
+ // possible the platform doesn't have an hwclock, if so we just abandon the call
+ Log.Debug($"Call to open /dev/rtc yielded {Marshal.GetLastWin32Error()}");
+ return;
+ }
+
+ var gch = GCHandle.Alloc(rtcTime, GCHandleType.Pinned);
+ try
+ {
+ var result = Interop.ioctl(fd, Interop.Ioctl.RTC_SET_TIME, gch.AddrOfPinnedObject());
+
+ if (result != 0)
+ {
+ var err = Marshal.GetLastWin32Error();
+
+ string errorMessage;
+
+ if (err == 1)
+ {
+ // this is a permissions issue
+ errorMessage = "Application does not have permissions to set the hwclock. Try running \"sudo setcap 'cap_sys_time=ep' $DOTNET_ROOT/dotnet\"";
+ }
+ else
+ {
+ errorMessage = $"Failed to set hwclock. Error code {err}";
+ }
+
+ throw new NativeException(errorMessage, err);
+ }
+ }
+ finally
+ {
+ gch.Free();
+ Interop.close(fd);
+ }
}
///
@@ -233,9 +314,6 @@ public DigitalStorage GetPrimaryDiskSpaceInUse()
///
public AllocationInfo GetMemoryAllocationInfo() => throw new NotImplementedException();
- ///
- public string[] NtpServers => throw new NotImplementedException();
-
///
public bool RebootOnUnhandledException => false;
diff --git a/source/implementations/linux/Meadow.Linux/SysFs/SysFsDigitalInterruptPort.cs b/source/implementations/linux/Meadow.Linux/SysFs/SysFsDigitalInterruptPort.cs
index ff5a57ae9..7acae2dee 100644
--- a/source/implementations/linux/Meadow.Linux/SysFs/SysFsDigitalInterruptPort.cs
+++ b/source/implementations/linux/Meadow.Linux/SysFs/SysFsDigitalInterruptPort.cs
@@ -13,6 +13,7 @@ public class SysFsDigitalInterruptPort : DigitalInterruptPortBase, IDigitalInput
private SysFsGpioDriver Driver { get; }
private ResistorMode _resistorMode = ResistorMode.Disabled;
private int? _lastInterrupt = null;
+ private InterruptMode _interruptMode;
///
public override bool State => Driver.GetValue(Gpio);
@@ -27,15 +28,9 @@ internal SysFsDigitalInterruptPort(
ResistorMode resistorMode,
TimeSpan debounceDuration,
TimeSpan glitchDuration)
- : base(pin, channel, interruptMode)
+ : base(pin, channel)
{
- switch (resistorMode)
- {
- case ResistorMode.InternalPullUp:
- case ResistorMode.InternalPullDown:
- throw new NotSupportedException("Internal Resistor Modes are not supported on the current OS");
- }
-
+ Resistor = resistorMode;
DebounceDuration = debounceDuration;
GlitchDuration = glitchDuration;
Driver = driver;
@@ -56,17 +51,26 @@ internal SysFsDigitalInterruptPort(
Driver.Export(Gpio);
Thread.Sleep(100); // this seems to be required to prevent an error 13
Driver.SetDirection(Gpio, SysFsGpioDriver.GpioDirection.Input);
- switch (interruptMode)
+ InterruptMode = interruptMode;
+ }
+
+ ///
+ public override InterruptMode InterruptMode
+ {
+ get => _interruptMode;
+ set
{
- case InterruptMode.None:
- // nothing to do
- break;
- default:
- Driver.HookInterrupt(Gpio, interruptMode, InterruptCallback);
- break;
+ _interruptMode = value;
+ switch (_interruptMode)
+ {
+ case InterruptMode.None:
+ // nothing to do
+ break;
+ default:
+ Driver.HookInterrupt(Gpio, _interruptMode, InterruptCallback);
+ break;
+ }
}
-
- InterruptMode = interruptMode;
}
private void InterruptCallback()
diff --git a/source/implementations/linux/Meadow.Linux/gpiod/ChipInfo.cs b/source/implementations/linux/Meadow.Linux/gpiod/ChipInfo.cs
index ba1c97762..7fef4fbed 100644
--- a/source/implementations/linux/Meadow.Linux/gpiod/ChipInfo.cs
+++ b/source/implementations/linux/Meadow.Linux/gpiod/ChipInfo.cs
@@ -2,60 +2,59 @@
using System;
using System.Runtime.InteropServices;
-namespace Meadow
+namespace Meadow;
+
+internal class ChipInfo : IDisposable
{
- internal class ChipInfo : IDisposable
+ private Logger Logger { get; }
+ public IntPtr Handle { get; private set; }
+ private Gpiod.Interop.gpiod_chip? Chip { get; }
+ public string Name { get; } = string.Empty;
+ public string Label { get; } = string.Empty;
+
+ public LineCollection Lines { get; }
+
+ public bool IsInvalid => Handle.ToInt64() <= 0;
+
+ public static ChipInfo FromIntPtr(Logger logger, IntPtr p)
{
- private Logger Logger { get; }
- public IntPtr Handle { get; private set; }
- private Gpiod.Interop.gpiod_chip? Chip { get; }
- public string Name { get; } = string.Empty;
- public string Label { get; } = string.Empty;
+ return new ChipInfo(logger, p);
+ }
- public LineCollection Lines { get; }
+ private ChipInfo(Logger logger, IntPtr p)
+ {
+ Logger = logger;
- public bool IsInvalid => Handle.ToInt64() <= 0;
+ Handle = p;
- public static ChipInfo FromIntPtr(Logger logger, IntPtr p)
+ if (IsInvalid)
{
- return new ChipInfo(logger, p);
+ Logger.Debug($"Chip ptr is invalid - cannot get GPIOD chip details");
+ Lines = new LineCollection(this, 0);
}
-
- private ChipInfo(Logger logger, IntPtr p)
+ else
{
- Logger = logger;
-
- Handle = p;
-
- if (IsInvalid)
- {
- Logger.Debug($"Chip ptr is invalid - cannot get GPIOD chip details");
- Lines = new LineCollection(this, 0);
- }
- else
- {
- Chip = Marshal.PtrToStructure(Handle);
- Name = Chip.Value.name;
- Label = Chip.Value.label;
-
- // Init as an array of nulls. We'll populate as they are accessed
- Lines = new LineCollection(this, (int)Chip.Value.num_lines);
- }
+ Chip = Marshal.PtrToStructure(Handle);
+ Name = Chip.Value.name;
+ Label = Chip.Value.label;
+
+ // Init as an array of nulls. We'll populate as they are accessed
+ Lines = new LineCollection(this, (int)Chip.Value.num_lines);
}
+ }
- public void Dispose()
- {
- if (IsInvalid) return;
+ public void Dispose()
+ {
+ if (IsInvalid) return;
- Gpiod.Interop.gpiod_chip_close(Handle);
- Handle = IntPtr.Zero;
- }
+ Gpiod.Interop.gpiod_chip_close(Handle);
+ Handle = IntPtr.Zero;
+ }
- public override string ToString()
- {
- // same format as gpiodetect
- // gpiochip0 [pinctrl-bcm2711] (58 lines)
- return $"{Name} [{Label}] ({Lines.Count} lines)";
- }
+ public override string ToString()
+ {
+ // same format as gpiodetect
+ // gpiochip0 [pinctrl-bcm2711] (58 lines)
+ return $"{Name} [{Label}] ({Lines.Count} lines)";
}
}
diff --git a/source/implementations/linux/Meadow.Linux/gpiod/Gpiod.Interop.cs b/source/implementations/linux/Meadow.Linux/gpiod/Gpiod.Interop.cs
index 4ac7118f9..480e396d3 100644
--- a/source/implementations/linux/Meadow.Linux/gpiod/Gpiod.Interop.cs
+++ b/source/implementations/linux/Meadow.Linux/gpiod/Gpiod.Interop.cs
@@ -253,8 +253,11 @@ internal struct gpiod_line_event
*/
//int gpiod_line_request_output(struct gpiod_line *line, const char *consumer, int default_val) GPIOD_API;
[DllImport(LIB_GPIOD, SetLastError = true)]
- public static extern int gpiod_line_request_output(IntPtr line, [MarshalAs(UnmanagedType.LPStr)] string consumer);
+ public static extern int gpiod_line_request_output(IntPtr line, [MarshalAs(UnmanagedType.LPStr)] string consumer, int default_val);
+ //int gpiod_line_request_output(struct gpiod_line *line, const char *consumer, int default_val) GPIOD_API;
+ [DllImport(LIB_GPIOD, SetLastError = true)]
+ public static extern int gpiod_line_request_output_flags(IntPtr line, [MarshalAs(UnmanagedType.LPStr)] string consumer, line_request_flags flags, int default_val);
/**
* @brief Read current value of a single GPIO line.
@@ -380,6 +383,9 @@ internal struct timespec
[DllImport(LIB_GPIOD, SetLastError = true)]
public static extern int gpiod_line_request_falling_edge_events(IntPtr line, [MarshalAs(UnmanagedType.LPStr)] string consumer);
+ [DllImport(LIB_GPIOD, SetLastError = true)]
+ public static extern int gpiod_line_request_falling_edge_events_flags(IntPtr line, [MarshalAs(UnmanagedType.LPStr)] string consumer, line_request_flags flags);
+
/*
* @brief Request all event type notifications on a single line.
* @param line GPIO line object.
@@ -390,6 +396,9 @@ internal struct timespec
[DllImport(LIB_GPIOD, SetLastError = true)]
public static extern int gpiod_line_request_both_edges_events(IntPtr line, [MarshalAs(UnmanagedType.LPStr)] string consumer);
+ [DllImport(LIB_GPIOD, SetLastError = true)]
+ public static extern int gpiod_line_request_both_edges_events_flags(IntPtr line, [MarshalAs(UnmanagedType.LPStr)] string consumer, line_request_flags flags);
+
/*
* @brief Request falling edge event notifications on a single line.
* @param line GPIO line object.
diff --git a/source/implementations/linux/Meadow.Linux/gpiod/Gpiod.cs b/source/implementations/linux/Meadow.Linux/gpiod/Gpiod.cs
index b8c1bc72c..15def4d00 100644
--- a/source/implementations/linux/Meadow.Linux/gpiod/Gpiod.cs
+++ b/source/implementations/linux/Meadow.Linux/gpiod/Gpiod.cs
@@ -1,135 +1,134 @@
using Meadow.Logging;
using System;
+using System.IO;
+using System.Runtime.InteropServices;
-namespace Meadow
+namespace Meadow;
+
+internal partial class Gpiod : IDisposable
{
- internal partial class Gpiod : IDisposable
+ private class PinInfo
{
+ public int FileDescriptor { get; set; }
+ public int ReferenceCount { get; set; }
+ }
- private class PinInfo
- {
- public int FileDescriptor { get; set; }
- public int ReferenceCount { get; set; }
- }
-
- public bool IsDisposed { get; private set; }
-
- private ChipCollection Chips { get; set; }
- private Logger Logger { get; }
-
- public unsafe Gpiod(Logger logger)
- {
- Chips = new ChipCollection();
- Logger = logger;
-
- var iter = Interop.gpiod_chip_iter_new();
-
- try
- {
- IntPtr p;
+ public bool IsDisposed { get; private set; }
- do
- {
- p = Interop.gpiod_chip_iter_next_noclose(iter);
+ private ChipCollection Chips { get; set; }
+ private Logger Logger { get; }
- var info = ChipInfo.FromIntPtr(logger, p);
- if (!info.IsInvalid)
- {
- Chips.Add(info);
-
- Logger.Debug(info.ToString());
+ public unsafe Gpiod(Logger logger)
+ {
+ Chips = new ChipCollection();
+ Logger = logger;
- foreach (var line in info.Lines)
- {
- Logger.Debug(line.ToString());
- }
- }
- } while (p != IntPtr.Zero);
- }
- finally
- {
- Interop.gpiod_chip_iter_free(iter);
- }
+ var iter = Interop.gpiod_chip_iter_new();
- /*
- var names = Directory.GetFiles("/dev", "gpiochip*");
+ try
+ {
+ IntPtr p;
- foreach (var n in names)
+ do
{
- Logger.Debug($"opening {n}");
+ p = Interop.gpiod_chip_iter_next_noclose(iter);
- var info = ChipInfo.FromIntPtr(logger, Interop.gpiod_chip_open_by_name(n));
+ var info = ChipInfo.FromIntPtr(logger, p);
if (!info.IsInvalid)
{
Chips.Add(info);
- Logger.Debug(info.ToString());
-
foreach (var line in info.Lines)
{
- Logger.Debug(line.ToString());
+ Logger.Debug($"{info.Name} {line}");
}
}
- else
- {
- Console.WriteLine($"ERR: {Marshal.GetLastWin32Error()}");
+ } while (p != IntPtr.Zero);
+ }
+ finally
+ {
+ Interop.gpiod_chip_iter_free(iter);
+ }
+ }
- Logger.Error($"Unable to get info for chip {n}");
- }
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!IsDisposed)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
}
- */
+
+ foreach (var chip in Chips)
+ {
+ chip.Dispose();
+ }
+
+ IsDisposed = true;
}
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
- protected virtual void Dispose(bool disposing)
+ internal void LogAllAvailablePins()
+ {
+ var names = Directory.GetFiles("/dev", "gpiochip*");
+
+ foreach (var n in names)
{
- if (!IsDisposed)
+ Logger.Debug($"opening {n}");
+
+ var info = ChipInfo.FromIntPtr(Logger, Interop.gpiod_chip_open_by_name(n));
+ if (!info.IsInvalid)
{
- if (disposing)
- {
- // TODO: dispose managed state (managed objects)
- }
+ Chips.Add(info);
+
+ Logger.Debug(info.ToString());
- foreach (var chip in Chips)
+ foreach (var line in info.Lines)
{
- chip.Dispose();
+ Logger.Debug(line.ToString());
}
+ }
+ else
+ {
+ Console.WriteLine($"ERR: {Marshal.GetLastWin32Error()}");
- IsDisposed = true;
+ Logger.Error($"Unable to get info for chip {n}");
}
}
+ }
- public void Dispose()
+ public LineInfo GetLine(GpiodPin pin)
+ {
+ if (!Chips.Contains(pin.Chip))
{
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
+ throw new NativeException($"Unknown GPIO chip {pin.Chip}");
}
- public LineInfo GetLine(GpiodPin pin)
- {
- if (!Chips.Contains(pin.Chip))
- {
- throw new NativeException($"Unknown GPIO chip {pin.Chip}");
- }
-
- var line = Chips[pin.Chip]!.Lines[pin.Offset];
+ var line = Chips[pin.Chip]!.Lines[pin.Offset];
- // TODO: check availability, check for other reservations
+ // TODO: check availability, check for other reservations
- return line;
- }
+ return line;
+ }
- public LineInfo GetLine(LinuxFlexiPin pin)
+ public LineInfo GetLine(LinuxFlexiPin pin)
+ {
+ if (!Chips.Contains(pin.GpiodChip))
{
- if (!Chips.Contains(pin.GpiodChip))
- {
- throw new NativeException($"Unknown GPIO chip {pin.GpiodChip}");
- }
+ throw new NativeException($"Unknown GPIO chip {pin.GpiodChip}");
+ }
- var line = Chips[pin.GpiodChip]!.Lines[pin.GpiodOffset];
+ var line = Chips[pin.GpiodChip]!.Lines[pin.GpiodOffset];
- // TODO: check availability, check for other reservations
+ // TODO: check availability, check for other reservations
- return line;
- }
+ return line;
}
}
diff --git a/source/implementations/linux/Meadow.Linux/gpiod/GpiodDigitalInterruptPort.cs b/source/implementations/linux/Meadow.Linux/gpiod/GpiodDigitalInterruptPort.cs
index 96738f786..7436bf383 100644
--- a/source/implementations/linux/Meadow.Linux/gpiod/GpiodDigitalInterruptPort.cs
+++ b/source/implementations/linux/Meadow.Linux/gpiod/GpiodDigitalInterruptPort.cs
@@ -12,6 +12,8 @@ public class GpiodDigitalInterruptPort : DigitalInterruptPortBase
private Gpiod Driver { get; }
private LineInfo Line { get; }
private int? _lastInterrupt = null;
+ private InterruptMode _interruptMode;
+ private ResistorMode _resistorMode;
///
public override bool State => Line.GetValue();
@@ -26,15 +28,13 @@ internal GpiodDigitalInterruptPort(
ResistorMode resistorMode,
TimeSpan debounceDuration,
TimeSpan glitchDuration)
- : base(pin, channel, interruptMode)
+ : base(pin, channel)
{
DebounceDuration = debounceDuration;
GlitchDuration = glitchDuration;
Driver = driver;
Pin = pin;
- line_request_flags flags = line_request_flags.None;
-
LineInfo? li = null;
if (pin is GpiodPin { } gp)
@@ -49,33 +49,8 @@ internal GpiodDigitalInterruptPort(
if (li != null)
{
Line = li;
- switch (resistorMode)
- {
- case ResistorMode.InternalPullUp:
- flags = line_request_flags.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
- break;
- case ResistorMode.InternalPullDown:
- flags = line_request_flags.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
- break;
- default:
- flags = line_request_flags.GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE;
- break;
- }
-
- InterruptMode = interruptMode;
-
- switch (InterruptMode)
- {
- case InterruptMode.EdgeRising:
- case InterruptMode.EdgeFalling:
- case InterruptMode.EdgeBoth:
- Line.InterruptOccurred += OnInterruptOccurred;
- Line.RequestInterrupts(InterruptMode, flags);
- break;
- default:
- Line.RequestInput(flags);
- break;
- }
+
+ SetInterruptOrInputMode(resistorMode, interruptMode);
}
else
{
@@ -83,6 +58,40 @@ internal GpiodDigitalInterruptPort(
}
}
+ private void SetInterruptOrInputMode(ResistorMode resistorMode, InterruptMode interruptMode)
+ {
+ line_request_flags flags = line_request_flags.None;
+
+ switch (resistorMode)
+ {
+ case ResistorMode.InternalPullUp:
+ flags = line_request_flags.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
+ break;
+ case ResistorMode.InternalPullDown:
+ flags = line_request_flags.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
+ break;
+ default:
+ flags = line_request_flags.GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE;
+ break;
+ }
+
+ switch (interruptMode)
+ {
+ case InterruptMode.EdgeRising:
+ case InterruptMode.EdgeFalling:
+ case InterruptMode.EdgeBoth:
+ Line.InterruptOccurred += OnInterruptOccurred;
+ Line.RequestInterrupts(interruptMode, flags);
+ break;
+ default:
+ Line.RequestInput(flags);
+ break;
+ }
+
+ _interruptMode = interruptMode;
+ _resistorMode = resistorMode;
+ }
+
private void OnInterruptOccurred(LineInfo sender, gpiod_line_event e)
{
if (DebounceDuration.TotalMilliseconds > 0)
@@ -107,11 +116,18 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
+ ///
+ public override InterruptMode InterruptMode
+ {
+ get => _interruptMode;
+ set => SetInterruptOrInputMode(Resistor, value);
+ }
+
///
public override ResistorMode Resistor
{
- get => ResistorMode.Disabled;
- set => throw new NotSupportedException("Resistor Mode not supported on this platform");
+ get => _resistorMode;
+ set => SetInterruptOrInputMode(value, InterruptMode);
}
///
diff --git a/source/implementations/linux/Meadow.Linux/gpiod/GpiodDigitalOutputPort.cs b/source/implementations/linux/Meadow.Linux/gpiod/GpiodDigitalOutputPort.cs
index 5067eea18..aeb3852fd 100644
--- a/source/implementations/linux/Meadow.Linux/gpiod/GpiodDigitalOutputPort.cs
+++ b/source/implementations/linux/Meadow.Linux/gpiod/GpiodDigitalOutputPort.cs
@@ -1,5 +1,6 @@
using Meadow.Hardware;
using System;
+using System.Runtime.InteropServices;
namespace Meadow;
@@ -30,18 +31,28 @@ internal GpiodDigitalOutputPort(Gpiod driver, IPin pin, bool initialState)
if (pin is GpiodPin { } gp)
{
Line = Driver.GetLine(gp);
- Line.Request(Gpiod.Interop.line_direction.GPIOD_LINE_DIRECTION_OUTPUT);
}
else if (pin is LinuxFlexiPin { } fp)
{
Line = Driver.GetLine(fp);
- Line.Request(Gpiod.Interop.line_direction.GPIOD_LINE_DIRECTION_OUTPUT);
}
else
{
throw new NativeException($"Pin {pin.Name} does not support GPIOD operations");
}
+ var result = Line.RequestOutput(Gpiod.Interop.line_request_flags.GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE, initialState);
+ if (!result)
+ {
+ var err = Marshal.GetLastWin32Error();
+
+ if ((Interop.Errors)err == Interop.Errors.DeviceBusy)
+ {
+ throw new NativeException($"Pin {pin.Name} already in use");
+ }
+
+ throw new NativeException($"Failed to request line for Pin {pin.Name} (error {err})", err);
+ }
}
///
diff --git a/source/implementations/linux/Meadow.Linux/gpiod/LineInfo.cs b/source/implementations/linux/Meadow.Linux/gpiod/LineInfo.cs
index 129263a70..4c63e559d 100644
--- a/source/implementations/linux/Meadow.Linux/gpiod/LineInfo.cs
+++ b/source/implementations/linux/Meadow.Linux/gpiod/LineInfo.cs
@@ -4,193 +4,211 @@
using System.Threading.Tasks;
using static Meadow.Gpiod.Interop;
-namespace Meadow
-{
- internal delegate void LineEventHandler(LineInfo lineInfo, gpiod_line_event evt);
-
- internal class LineInfo
- {
- private IntPtr Handle { get; set; }
- private gpiod_line? Line { get; set; }
- public string Name { get; private set; } = string.Empty;
- public string Consumer { get; private set; } = string.Empty;
- public int Offset { get; }
- public line_direction Direction { get; private set; }
- public line_active_state ActiveState { get; private set; }
+namespace Meadow;
- public bool IsInvalid => Handle.ToInt64() <= 0;
+internal delegate void LineEventHandler(LineInfo lineInfo, gpiod_line_event evt);
- private bool _istIsRunning = false;
- private bool _istShouldStop = false;
- public event LineEventHandler InterruptOccurred = delegate { };
+internal class LineInfo
+{
+ private IntPtr Handle { get; set; }
+ private gpiod_line? Line { get; set; }
+ public string Name { get; private set; } = string.Empty;
+ public string Consumer { get; private set; } = string.Empty;
+ public int Offset { get; }
+ public line_direction Direction { get; private set; }
+ public line_active_state ActiveState { get; private set; }
- public LineInfo(ChipInfo chip, int offset)
- {
- Offset = offset;
- Handle = gpiod_chip_get_line(chip.Handle, Offset);
+ public bool IsInvalid => Handle.ToInt64() <= 0;
- if (!IsInvalid)
- {
- UpdatePropsFromHandle();
- }
- }
+ private bool _istIsRunning = false;
+ private bool _istShouldStop = false;
+ public event LineEventHandler InterruptOccurred = delegate { };
- private void UpdatePropsFromHandle()
- {
- Line = Marshal.PtrToStructure(Handle);
- Name = Line.Value.name;
- Consumer = Line.Value.consumer;
- Direction = Line.Value.direction;
- ActiveState = Line.Value.active_state;
- }
+ public LineInfo(ChipInfo chip, int offset)
+ {
+ Offset = offset;
+ Handle = gpiod_chip_get_line(chip.Handle, Offset);
- public void Update()
+ if (!IsInvalid)
{
- gpiod_line_update(Handle);
UpdatePropsFromHandle();
}
+ }
- private const string MeadowConsumer = "Meadow";
+ private void UpdatePropsFromHandle()
+ {
+ Line = Marshal.PtrToStructure(Handle);
+ Name = Line.Value.name;
+ Consumer = Line.Value.consumer;
+ Direction = Line.Value.direction;
+ ActiveState = Line.Value.active_state;
+ }
- public void Request(line_direction direction)
- {
- // TODO: check for free?
- int result;
+ public void Update()
+ {
+ gpiod_line_update(Handle);
+ UpdatePropsFromHandle();
+ }
- if (direction == line_direction.GPIOD_LINE_DIRECTION_INPUT)
- {
- result = gpiod_line_request_input(Handle, MeadowConsumer);
- }
- else
- {
- result = gpiod_line_request_output(Handle, MeadowConsumer);
- }
+ private const string MeadowConsumer = "Meadow";
- if (result == -1)
- {
- throw new NativeException("Failed to request line", Marshal.GetLastWin32Error());
- }
- }
+ public void Request(line_direction direction)
+ {
+ // TODO: check for free?
+ int result;
- public void RequestInput(line_request_flags flags)
+ if (direction == line_direction.GPIOD_LINE_DIRECTION_INPUT)
{
- // TODO: check for free?
- var result = gpiod_line_request_input_flags(Handle, MeadowConsumer, flags);
-
- if (result == -1)
- {
- throw new NativeException("Failed to request line", Marshal.GetLastWin32Error());
- }
+ result = gpiod_line_request_input(Handle, MeadowConsumer);
}
-
- public void RequestInterrupts(InterruptMode mode, line_request_flags flags)
+ else
{
- if (_istIsRunning) return;
-
- _istShouldStop = false;
-
- int result;
-
- switch (mode)
- {
- case InterruptMode.EdgeRising:
- result = gpiod_line_request_rising_edge_events_flags(Handle, MeadowConsumer, flags);
- break;
- case InterruptMode.EdgeFalling:
- result = gpiod_line_request_falling_edge_events(Handle, MeadowConsumer);
- break;
- case InterruptMode.EdgeBoth:
- result = gpiod_line_request_both_edges_events(Handle, MeadowConsumer);
- break;
- default:
- return;
- }
+ result = gpiod_line_request_output(Handle, MeadowConsumer, 0);
+ }
- if (result == -1)
+ if (result == -1)
+ {
+ var errorCode = Marshal.GetLastWin32Error();
+ if (errorCode == 16)
{
- throw new NativeException($"Failed to request interrupts: {Marshal.GetLastWin32Error()}", Marshal.GetLastWin32Error());
+ // resource is busy - we might have crashed leaving the driver with the pin locked
+ // or some other app, etc might be using it
+ throw new NativeException($"Unable to access output pin. Pin {Line?.name} is in use", errorCode);
}
else
{
- Task.Run(() => IST());
+ throw new NativeException($"Unable to access output pin. Error code {errorCode}", errorCode);
}
}
+ }
+
+ public void RequestInput(line_request_flags flags)
+ {
+ // TODO: check for free?
+ var result = gpiod_line_request_input_flags(Handle, MeadowConsumer, flags);
- private void IST()
+ if (result == -1)
{
- _istIsRunning = true;
- timespec timeout = new timespec { tv_sec = 1 }; // 1-second timeout
- gpiod_line_event evnt = new gpiod_line_event();
+ var err = Marshal.GetLastWin32Error();
+ throw new NativeException("Failed to request line", Marshal.GetLastWin32Error());
+ }
+ }
- while (!_istShouldStop)
- {
- var result = gpiod_line_event_wait(Handle, ref timeout);
-
- switch (result)
- {
- case 0: // timeout
- // nop, just wait again
- break;
- case 1: // event
- result = gpiod_line_event_read(Handle, ref evnt);
-
- if (result == 0)
- {
- InterruptOccurred?.Invoke(this, evnt);
- }
- else
- {
- throw new NativeException("Failed to read interrupt data", Marshal.GetLastWin32Error());
- }
- break;
- case -1: // error
- default: //undefined
- throw new NativeException("Waiting for interrupt event failed", Marshal.GetLastWin32Error());
- }
+ public bool RequestOutput(line_request_flags flags, bool initialState)
+ {
+ // TODO: check for free?
+ var result = gpiod_line_request_output_flags(Handle, MeadowConsumer, flags, initialState ? 1 : 0);
- }
+ return result != -1;
+ }
- _istIsRunning = false;
+ public void RequestInterrupts(InterruptMode mode, line_request_flags flags)
+ {
+ if (_istIsRunning) return;
+
+ _istShouldStop = false;
+
+ int result;
+
+ switch (mode)
+ {
+ case InterruptMode.EdgeRising:
+ result = gpiod_line_request_rising_edge_events_flags(Handle, MeadowConsumer, flags);
+ break;
+ case InterruptMode.EdgeFalling:
+ result = gpiod_line_request_falling_edge_events_flags(Handle, MeadowConsumer, flags);
+ break;
+ case InterruptMode.EdgeBoth:
+ result = gpiod_line_request_both_edges_events_flags(Handle, MeadowConsumer, flags);
+ break;
+ default:
+ return;
}
- public void Release()
+ if (result == -1)
{
- _istShouldStop = true;
- gpiod_line_release(Handle);
+ throw new NativeException($"Failed to request interrupts: {Marshal.GetLastWin32Error()}", Marshal.GetLastWin32Error());
}
+ else
+ {
+ Task.Run(() => IST());
+ }
+ }
+
+ private void IST()
+ {
+ _istIsRunning = true;
+ timespec timeout = new timespec { tv_sec = 1 }; // 1-second timeout
+ gpiod_line_event evnt = new gpiod_line_event();
- public void SetValue(bool state)
+ while (!_istShouldStop)
{
- var result = gpiod_line_set_value(Handle, state ? 1 : 0);
+ var result = gpiod_line_event_wait(Handle, ref timeout);
- if (result == -1)
+ switch (result)
{
- throw new NativeException("Failed to set line value", Marshal.GetLastWin32Error());
+ case 0: // timeout
+ // nop, just wait again
+ break;
+ case 1: // event
+ result = gpiod_line_event_read(Handle, ref evnt);
+
+ if (result == 0)
+ {
+ InterruptOccurred?.Invoke(this, evnt);
+ }
+ else
+ {
+ throw new NativeException("Failed to read interrupt data", Marshal.GetLastWin32Error());
+ }
+ break;
+ case -1: // error
+ default: //undefined
+ throw new NativeException("Waiting for interrupt event failed", Marshal.GetLastWin32Error());
}
+
}
- public bool GetValue()
- {
- var result = gpiod_line_get_value(Handle);
+ _istIsRunning = false;
+ }
- if (result == -1)
- {
- throw new NativeException("Failed to set line value", Marshal.GetLastWin32Error());
- }
+ public void Release()
+ {
+ _istShouldStop = true;
+ gpiod_line_release(Handle);
+ }
- return result == 1;
+ public void SetValue(bool state)
+ {
+ var result = gpiod_line_set_value(Handle, state ? 1 : 0);
+
+ if (result == -1)
+ {
+ throw new NativeException("Failed to set line value", Marshal.GetLastWin32Error());
}
+ }
+
+ public bool GetValue()
+ {
+ var result = gpiod_line_get_value(Handle);
- public override string ToString()
+ if (result == -1)
{
- // same format as gpioinfo
- // gpiochip0 [pinctrl-bcm2711] (58 lines)
- var c = string.IsNullOrEmpty(Consumer) ? "unused" : $"\"{Consumer}\"";
- var n = $"\"{Name}\"";
- var d = Direction == line_direction.GPIOD_LINE_DIRECTION_INPUT ? " input" : "output";
- var s = ActiveState == line_active_state.GPIOD_LINE_ACTIVE_STATE_LOW ? "active-low" : "active-high";
-
- return $"line {Offset:00}: {n,24}{c,24} {d} {s}";
+ throw new NativeException("Failed to set line value", Marshal.GetLastWin32Error());
}
+
+ return result == 1;
+ }
+
+ public override string ToString()
+ {
+ // same format as gpioinfo
+ // gpiochip0 [pinctrl-bcm2711] (58 lines)
+ var c = string.IsNullOrEmpty(Consumer) ? "unused" : $"\"{Consumer}\"";
+ var n = $"\"{Name}\"";
+ var d = Direction == line_direction.GPIOD_LINE_DIRECTION_INPUT ? " input" : "output";
+ var s = ActiveState == line_active_state.GPIOD_LINE_ACTIVE_STATE_LOW ? "active-low" : "active-high";
+
+ return $"line {Offset:00}: {n,24}{c,24} {d} {s}";
}
}
diff --git a/source/implementations/linux/README.md b/source/implementations/linux/README.md
index 98debb39a..87c5aeef8 100644
--- a/source/implementations/linux/README.md
+++ b/source/implementations/linux/README.md
@@ -11,119 +11,35 @@ Wilderness Labs Meadow.Linux is a .NET Framework for running IoT applications on
## Supported Platforms and Distributions
-Currently tested platforms and distributions:
+Linux Embedded Hardware is a broad space and we've made efforts to test support on a broad number of devices. because Meadow is modular and interface-based, adding new platform support is generally simple, requiring only a few days to port and test with hardware.
-| Hardware | Distro | Meadow.Core Version tested |
-| :---: | :---: | :---: |
-| Raspberry Pi 4 | Raspberry Pi OS | Beta 6.2 |
-| Raspberry Pi Zero 2 W | Raspberry Pi OS | Beta 6.2 |
-| Jetson Nano | Ubuntu 20.04 | Beta 6.2 |
-| Jetson Xavier AGX | Ubuntu 18.04 | RC-2 |
-| KRTKL Snickerdoodle Black | Ubuntu 20.04 | RC-1 |
-| AMD64 Ubuntu 20.04 under WSL2 | Ubuntu 20.04 | RC-1 |
+The table below shows the platforms we have done at least some work on and to what degree we have tested subsystems.
-## License
-
-Apache 2.0
-
-See [LICENSE File](/LICENSE)
-
-## Assumptions
-
-For this documentation, we assume you are developing your application code on a machine *separate* from your target hardware. You can certainly develop and compile your application directly on your target hardware, but the possible variations and permutations make documenting the process more difficult. If you are new to .NET development on IoT devices, we recommend starting with a separate development machine first.
-
-## Prepare Your Target Hardware
-
-`Meadow.Linux` runs applications using the .NET 6.0 Core Runtime, so you must install it on the target.
-
-### Install .NET 7.0
-
-Follow the instructions based on your distro:
-
-https://docs.microsoft.com/en-us/dotnet/core/install/linux
-
-```console
-$ dotnet --list-runtimes
-Microsoft.NETCore.App 7.0.2 [/opt/dotnet/shared/Microsoft.NETCore.App]
-```
-
-### Enable Hardware Access
-
-Different platforms will have different rules for enabling application access to hardware devices such as GPIO, SPI and I2C. Consult your platform documentation for specifics, but as an example Raspberry Pi OS requires that you run `raspi-config` to enable the peripherals you want, and *additionally* add your user to specific groups for the peripherals. If you get permissions errors while first running your application, this is the place to start looking.
-
-## Develop your Application
+| Platform | OS Tested | GPIO | I2C | SPI | ADC | UART | PWM |
+| --- | --- | --- | --- | --- | --- | --- |
+| Raspberry Pi 3b+ | Rasberry Pi OS xxx 64-bit Lite | X | X | X | - | U | O |
+| Raspberry Pi 4 | Rasberry Pi OS xxx 64-bit Lite | X | X | X | - | U | O |
+| Raspberry Pi 5 | Rasberry Pi OS xxx 64-bit Lite | X | X | X | - | X | O |
+| Raspberry Pi Zero 2 W | Rasberry Pi OS Lite 64 (kernel 6.6.20+rpt-rpi-v8, Debian Bookworm 1:6.6.20-1+rpt1) | X | X | X | - | X | O |
+| BeagleBone Black | Debian Bullseye (kernel 5.10.168-ti-r72) | X | X | X | X | O | X |
+| NVIDIA Jetson Xavier Nano | Ubuntu 18.04 | X | O | O | - | O | O |
+| NVIDIA Jetson Xavier AGX | Ubuntu 18.04 | X | X | U | - | O | O |
+| krtkl Snickerdoodle Black | Ubuntu 20.04 | X | O | O | - | O | O |
+| AMD64 Ubuntu 20.04 under WSL2 | Ubuntu 20.04 | - | - | - | - | U | - |
-- Create a .NET 7 Core Application Project
-- Add NuGet references to Meadow.Linux and any other requirements (e.g Meadow.Foundation and peripheral drivers)
-- Develop/Compile
+X: Implemented and tested
+U: Implemented but untested
+O: Not implemented/Not Tested
+-: Not supported by hardware
-## Deploying
+## Platform-Specific Instructions and Details
-When you are ready to run your application you will need to copy it, and all dependencies, to the target device.
+### [BeagleBone Black](beaglebone.md)
+### [Raspberry Pi](raspberrypi.md)
-The simplest, most reliable way to collect all of those binaries is by using `dotnet publish`.
-
-For example, the `Bme280_Sample` in this repo can be published to a local folder like this:
-
-```console
-C:\repos\wilderness\Meadow.Linux\src\samples\Bme280_Sample>dotnet publish -c Release -o publish
-Microsoft (R) Build Engine version 17.2.0+41abc5629 for .NET
-Copyright (C) Microsoft Corporation. All rights reserved.
-
- Determining projects to restore...
- All projects are up-to-date for restore.
- Meadow.Linux -> C:\repos\wilderness\Meadow.Linux\src\lib\bin\Release\netstandard2.1\Meadow.Linux.dll
- Bme280_Sample -> C:\repos\wilderness\Meadow.Linux\src\samples\Bme280_Sample\bin\Release\net7.0\App.dll
- Bme280_Sample -> C:\repos\wilderness\Meadow.Linux\src\samples\Bme280_Sample\publish\
-```
-
-The output folder contains all of the files and assemblies you need to copy to your target hardware.
-
-Now use a tool such as SCP to copy all of these files to a folder on you target hardware.
-
-[add example command / winscp sceen cap]
-
-[todo: this is a note from my previous work, check if App.deps is a problem]
-- Copy over your application binaries and `App.runtimeconfig.json`. Do *not* copy over App.deps.json
-
-Here's an example of all of the binaries for the Bme280_Sample aaplication after deployment to a target Raspberry Pi:
-
-```console
-$ ls -al
-total 472
-drwxr-xr-x 2 pi pi 4096 Nov 7 17:56 .
-drwxr-xr-x 16 pi pi 4096 Nov 7 17:55 ..
--rw-r--r-- 1 pi pi 7680 Nov 7 18:09 App.dll
--rw-r--r-- 1 pi pi 147 Nov 7 17:55 App.runtimeconfig.json
--rw-r--r-- 1 pi pi 17920 Nov 7 17:58 Bme280.dll
--rw-r--r-- 1 pi pi 69632 Nov 7 17:23 Meadow.Contracts.dll
--rw-r--r-- 1 pi pi 127488 Nov 7 17:23 Meadow.dll
--rw-r--r-- 1 pi pi 111104 Nov 7 17:23 Meadow.Foundation.dll
--rw-r--r-- 1 pi pi 38400 Nov 7 17:23 Meadow.Linux.dll
--rw-r--r-- 1 pi pi 83968 Nov 7 17:23 Meadow.Units.dll
-```
-
-## Running
-
-Starting with the `RC1.0` release, Meadow now has a defined lifecycle. This is imposed to allow automated application shutdown by the OtA update service. What this means, practically, is that your Meadow.Linux application must define an entry point in your `App` implementation and and manually start the MeadowOS stack.
-
-```console
-public class MeadowApp : App
-{
- public static async Task Main(string[] _)
- {
- await MeadowOS.Start();
- }
- ...
-}
-```
+## License
-Then launch the application using `dotnet`
+Apache 2.0
-```console
-$ dotnet MyAppAssembly.dll
-```
-
-## Work in Progress
+See [LICENSE File](/LICENSE)
-`Meadow.Linux` is currently an *Beta* product with several core features that are not yet implemented. Details are available in the [Issues Tab](https://github.com/WildernessLabs/Meadow.Linux/issues) and the source.
diff --git a/source/implementations/linux/beaglebone.md b/source/implementations/linux/beaglebone.md
new file mode 100644
index 000000000..3da783560
--- /dev/null
+++ b/source/implementations/linux/beaglebone.md
@@ -0,0 +1,148 @@
+# Meadow Linux on BeagleBone Black
+
+The BeagleBone Black does not have a large on-board eMMC (4GB) so the necessary bits will not fit once the OS is installed. You must boot from an SD card.
+
+## Download the BeagleBone OS
+
+https://www.beagleboard.org/distros
+
+Use the following file:
+
+```
+AM335x 11.8 2023-10-07 4GB microSD IoT
+```
+
+
+Use Balena Etcher to write it to an microSD card
+
+Plug the board into wired ethernet.
+
+Insert the SD card into an UNPOWERED device
+
+Press and hold the BOOT button (onthe obverse side of the board, right next to the position of SD slot)
+
+Power the device. It will boot from the SD Card.
+
+After a minute or so, you can SSH into the device
+
+```
+ssh debian@beaglebone.local
+```
+
+default username: debian
+default password: temppwd
+
+
+To prevent the device from booting to the smaller eMMC device, we must invalidate it.
+
+Write zeros over the first 1MB of the eMMC
+```
+sudo dd if=/dev/zero of=/dev/mmcblk1 bs=1M count=1
+run sync
+run sudo shutdown now
+```
+
+Disconnect and then reconnect power to boot again from SD without the need of the `BOOT` button.
+
+## Install .NET
+
+Create and edit a new shell script
+```
+nano raspi-dotnet.sh
+```
+
+```
+#!/bin/bash
+
+add_to_bashrc() {
+ local string="$1"
+ if ! grep -q "$string" "$HOME/.bashrc"; then
+ echo "$string" >> $HOME/.bashrc
+ fi
+}
+
+wget --inet4-only https://dot.net/v1/dotnet-install.sh -O - | bash /dev/stdin --version latest
+add_to_bashrc "export DOTNET_ROOT=\$HOME/.dotnet"
+add_to_bashrc "export PATH=\$PATH:\$HOME/.dotnet"
+source ~/.bashrc
+```
+
+
+
+List the local files
+
+```
+ls -l
+```
+Which gives an output like this:
+```
+total 4
+-rw-r--r-- 1 pi pi 364 May 28 20:22 raspi-dotnet.sh
+```
+
+Make the shell script executable
+
+```
+chmod +x raspi-dotnet.sh
+```
+The file list will change
+```
+ls -l
+total 4
+-rwxr-xr-x 1 pi pi 364 May 28 20:22 raspi-dotnet.sh
+```
+
+Execute the newly-created script
+
+```
+./raspi-dotnet.sh
+```
+
+This takes a long time, like over 5 minutes long. Be patient. In the end you should see something like
+
+```
+dotnet-install: Installation finished successfully.
+```
+
+Now re-load your environment and verify the install
+
+```
+source ~/.bashrc
+dotnet --version
+```
+This wll give the installed .NET version.
+```
+8.0.301
+```
+
+## Configuring Pins
+
+If you want to use a pin on the BeagleBone for a function other than its default configuration, you must change its behavior. In example of this is PWM. None of the BeagleBone pins are configured for PWM by default, they are GPIO. If you create a `PwmOutputPort` in your application, you will get no errors, but you also won't see any PWM signal.
+
+You must reconfigure the pin to be used for PWM. For example, to change `P9_42` from `GPIO7` to `ECAPPWM0` you must run this:
+
+```
+sudo config-pin P9.42 pwm
+```
+
+## Configuring for SPI
+
+`SPI0` takes a little extra work to access
+
+First, the pins must be configured for SPI:
+
+```
+sudo config-pin P9.18 spi
+sudo config-pin P9.21 spi
+sudo config-pin P9.22 spi_sclk_
+```
+
+The the `spi` group must be created, applied to the driver, and the use radded to the group:
+
+```
+sudo groupadd spi
+sudo adduser debian spi
+sudo chgrp spi /dev/spidev0.0
+sudo chmod 660 /dev/spidev0.0
+newgrp spi
+```
\ No newline at end of file
diff --git a/source/implementations/linux/raspberrypi.md b/source/implementations/linux/raspberrypi.md
new file mode 100644
index 000000000..42de673a8
--- /dev/null
+++ b/source/implementations/linux/raspberrypi.md
@@ -0,0 +1,165 @@
+# Meadow Linux on Raspberry Pi
+
+## Assumptions
+
+For this documentation, we assume you are developing your application code on a machine *separate* from your target hardware. You can certainly develop and compile your application directly on your target hardware, but the possible variations and permutations make documenting the process more difficult. If you are new to .NET development on IoT devices, we recommend starting with a separate development machine first.
+
+## Prepare Your Target Hardware
+
+`Meadow.Linux` runs applications using the .NET 8.0 Core Runtime, so you must install it on the target.
+
+### Install .NET
+
+Create and edit a new shell script
+```
+nano raspi-dotnet.sh
+```
+
+```
+#!/bin/bash
+
+add_to_bashrc() {
+ local string="$1"
+ if ! grep -q "$string" "$HOME/.bashrc"; then
+ echo "$string" >> $HOME/.bashrc
+ fi
+}
+
+wget --inet4-only https://dot.net/v1/dotnet-install.sh -O - | bash /dev/stdin --version latest
+add_to_bashrc "export DOTNET_ROOT=\$HOME/.dotnet"
+add_to_bashrc "export PATH=\$PATH:\$HOME/.dotnet"
+```
+
+
+
+List the local files
+
+```
+ls -l
+```
+Which gives an output like this:
+```
+total 4
+-rw-r--r-- 1 pi pi 364 May 28 20:22 raspi-dotnet.sh
+```
+
+Make the shell script executable
+
+```
+chmod +x raspi-dotnet.sh
+```
+The file list will change
+```
+ls -l
+total 4
+-rwxr-xr-x 1 pi pi 364 May 28 20:22 raspi-dotnet.sh
+```
+
+Execute the newly-created script
+
+```
+./raspi-dotnet.sh
+```
+
+This takes a long time, like over 5 minutes long. Be patient. In the end you should see something like
+
+```
+dotnet-install: Installation finished successfully.
+```
+
+Now re-load your environment and verify the install
+
+```
+source ~/.bashrc
+dotnet --version
+```
+This wll give the installed .NET version.
+```
+8.0.301
+```
+
+### Enable Hardware Things
+
+Now you have to enable access to things like the hardware busses and GPIO
+
+```
+sudo raspi-config
+```
+
+### Enable Hardware Access
+
+Use `raspi-config` to enable I2C and SPI on the platform.
+
+## Develop your Application
+
+- Create a .NET 8 Core Application Project
+- Add NuGet references to Meadow.Linux and any other requirements (e.g Meadow.Foundation and peripheral drivers)
+- Develop/Compile
+
+## Deploying
+
+When you are ready to run your application you will need to copy it, and all dependencies, to the target device.
+
+The simplest, most reliable way to collect all of those binaries is by using `dotnet publish`.
+
+For example, the `Bme280_Sample` in this repo can be published to a local folder like this:
+
+```console
+C:\repos\wilderness\Meadow.Linux\src\samples\Bme280_Sample>dotnet publish -c Release -o publish
+Microsoft (R) Build Engine version 17.2.0+41abc5629 for .NET
+Copyright (C) Microsoft Corporation. All rights reserved.
+
+ Determining projects to restore...
+ All projects are up-to-date for restore.
+ Meadow.Linux -> C:\repos\wilderness\Meadow.Linux\src\lib\bin\Release\netstandard2.1\Meadow.Linux.dll
+ Bme280_Sample -> C:\repos\wilderness\Meadow.Linux\src\samples\Bme280_Sample\bin\Release\net7.0\App.dll
+ Bme280_Sample -> C:\repos\wilderness\Meadow.Linux\src\samples\Bme280_Sample\publish\
+```
+
+The output folder contains all of the files and assemblies you need to copy to your target hardware.
+
+Now use a tool such as SCP to copy all of these files to a folder on you target hardware.
+
+[add example command / winscp sceen cap]
+
+[todo: this is a note from my previous work, check if App.deps is a problem]
+- Copy over your application binaries and `App.runtimeconfig.json`. Do *not* copy over App.deps.json
+
+Here's an example of all of the binaries for the Bme280_Sample aaplication after deployment to a target Raspberry Pi:
+
+```console
+$ ls -al
+total 472
+drwxr-xr-x 2 pi pi 4096 Nov 7 17:56 .
+drwxr-xr-x 16 pi pi 4096 Nov 7 17:55 ..
+-rw-r--r-- 1 pi pi 7680 Nov 7 18:09 App.dll
+-rw-r--r-- 1 pi pi 147 Nov 7 17:55 App.runtimeconfig.json
+-rw-r--r-- 1 pi pi 17920 Nov 7 17:58 Bme280.dll
+-rw-r--r-- 1 pi pi 69632 Nov 7 17:23 Meadow.Contracts.dll
+-rw-r--r-- 1 pi pi 127488 Nov 7 17:23 Meadow.dll
+-rw-r--r-- 1 pi pi 111104 Nov 7 17:23 Meadow.Foundation.dll
+-rw-r--r-- 1 pi pi 38400 Nov 7 17:23 Meadow.Linux.dll
+-rw-r--r-- 1 pi pi 83968 Nov 7 17:23 Meadow.Units.dll
+```
+
+## Running
+
+Starting with the `RC1.0` release, Meadow now has a defined lifecycle. This is imposed to allow automated application shutdown by the OtA update service. What this means, practically, is that your Meadow.Linux application must define an entry point in your `App` implementation and and manually start the MeadowOS stack.
+
+```console
+public class MeadowApp : App
+{
+ public static async Task Main(string[] _)
+ {
+ await MeadowOS.Start();
+ }
+ ...
+}
+```
+
+Then launch the application using `dotnet`
+
+```console
+$ dotnet MyAppAssembly.dll
+```
+
\ No newline at end of file
diff --git a/source/implementations/mac/Meadow.Mac/Mac.cs b/source/implementations/mac/Meadow.Mac/Mac.cs
index 76b2a7dd9..e0e1d7daf 100644
--- a/source/implementations/mac/Meadow.Mac/Mac.cs
+++ b/source/implementations/mac/Meadow.Mac/Mac.cs
@@ -108,6 +108,11 @@ public IDigitalOutputPort CreateDigitalOutputPort(IPin pin, bool initialState =
throw new NotSupportedException("Add an IO Expander to your platform");
}
+ ///
+ public IDigitalSignalAnalyzer CreateDigitalSignalAnalyzer(IPin pin, bool captureDutyCycle)
+ {
+ throw new NotSupportedException("Add an IO Expander to your platform");
+ }
///
public ISerialPort CreateSerialPort(string portName, int baudRate = 9600, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One, int readBufferSize = 1024)
diff --git a/source/implementations/mac/Meadow.Mac/Meadow.Mac.csproj b/source/implementations/mac/Meadow.Mac/Meadow.Mac.csproj
index fe8b4fc0e..a50c02681 100644
--- a/source/implementations/mac/Meadow.Mac/Meadow.Mac.csproj
+++ b/source/implementations/mac/Meadow.Mac/Meadow.Mac.csproj
@@ -18,10 +18,13 @@
An implementation of the Meadow software stack for Apple Macintosh targets
-
+
+
+
-
-
+
+
+
diff --git a/source/implementations/simulation/Meadow.Simulation/Meadow.Simulation.csproj b/source/implementations/simulation/Meadow.Simulation/Meadow.Simulation.csproj
index 2ccf90ca5..332d54222 100644
--- a/source/implementations/simulation/Meadow.Simulation/Meadow.Simulation.csproj
+++ b/source/implementations/simulation/Meadow.Simulation/Meadow.Simulation.csproj
@@ -19,11 +19,11 @@
-
+
-
-
+
+
-
+
diff --git a/source/implementations/simulation/Meadow.Simulation/SerialPortProxy.cs b/source/implementations/simulation/Meadow.Simulation/SerialPortProxy.cs
index b2e03228f..75783ad04 100644
--- a/source/implementations/simulation/Meadow.Simulation/SerialPortProxy.cs
+++ b/source/implementations/simulation/Meadow.Simulation/SerialPortProxy.cs
@@ -6,8 +6,8 @@ namespace Meadow.Simulation;
public class SerialPortProxy : ISerialPort
{
- public event Hardware.SerialDataReceivedEventHandler DataReceived;
- public event EventHandler BufferOverrun;
+ public event Hardware.SerialDataReceivedEventHandler? DataReceived;
+ public event EventHandler? BufferOverrun;
private SerialPort _port;
diff --git a/source/implementations/simulation/Meadow.Simulation/SimulatedMeadow.cs b/source/implementations/simulation/Meadow.Simulation/SimulatedMeadow.cs
index ab6f89124..dd863d04f 100644
--- a/source/implementations/simulation/Meadow.Simulation/SimulatedMeadow.cs
+++ b/source/implementations/simulation/Meadow.Simulation/SimulatedMeadow.cs
@@ -37,38 +37,48 @@ public SimulatedMeadow()
Information = new SimulationInformation();
}
+ ///
public TPinDefinitions Pins { get; }
+
+ ///
public IDeviceInformation Information { get; }
+ ///
public IPlatformOS PlatformOS => _platformOS;
+
+ ///
public DeviceCapabilities Capabilities { get; private set; } = new DeviceCapabilities(
new AnalogCapabilities(false, null),
new NetworkCapabilities(false, true),
new StorageCapabilities(true)
);
+ ///
public INetworkAdapterCollection NetworkAdapters { get; private set; } = new NativeNetworkAdapterCollection();
private void LaunchUI()
{
-
}
+ ///
public void DrivePinVoltage(IPin pin, Voltage voltage)
{
_simulationEngine.SetPinVoltage(pin, voltage);
}
+ ///
public void DrivePinState(IPin pin, bool state)
{
_simulationEngine.SetDiscrete(pin, state);
}
+ ///
public bool ReadPinState(IPin pin)
{
return _simulationEngine.GetDiscrete(pin);
}
+ ///
public IAnalogInputPort CreateAnalogInputPort(IPin pin, int sampleCount, TimeSpan sampleInterval, Meadow.Units.Voltage voltageReference)
{
var dc = pin.SupportedChannels?.FirstOrDefault(i => i is IAnalogChannelInfo) as AnalogChannelInfo;
@@ -82,6 +92,7 @@ public IAnalogInputPort CreateAnalogInputPort(IPin pin, int sampleCount, TimeSpa
throw new NotSupportedException();
}
+ ///
public IBiDirectionalPort CreateBiDirectionalPort(IPin pin, bool initialState, ResistorMode resistorMode, PortDirectionType initialDirection, TimeSpan debounceDuration, TimeSpan glitchDuration, OutputType output = OutputType.PushPull)
{
var dc = pin.SupportedChannels?.FirstOrDefault(i => i is IDigitalChannelInfo) as DigitalChannelInfo;
@@ -93,11 +104,13 @@ public IBiDirectionalPort CreateBiDirectionalPort(IPin pin, bool initialState, R
throw new NotSupportedException();
}
+ ///
public IDigitalInputPort CreateDigitalInputPort(IPin pin)
{
return CreateDigitalInputPort(pin, ResistorMode.Disabled);
}
+ ///
public IDigitalInputPort CreateDigitalInputPort(IPin pin, ResistorMode resistorMode)
{
var dci = pin.SupportedChannels?.FirstOrDefault(i => i is IDigitalChannelInfo) as DigitalChannelInfo;
@@ -111,6 +124,7 @@ public IDigitalInputPort CreateDigitalInputPort(IPin pin, ResistorMode resistorM
throw new NotSupportedException();
}
+ ///
public IDigitalOutputPort CreateDigitalOutputPort(IPin pin, bool initialState = false, OutputType initialOutputType = OutputType.PushPull)
{
var dco = pin.SupportedChannels?.FirstOrDefault(i => i is IDigitalChannelInfo) as DigitalChannelInfo;
@@ -128,6 +142,7 @@ public IDigitalOutputPort CreateDigitalOutputPort(IPin pin, bool initialState =
throw new NotSupportedException();
}
+ ///
public ISerialMessagePort CreateSerialMessagePort(SerialPortName portName, byte[] suffixDelimiter, bool preserveDelimiter, int baudRate = 9600, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One, int readBufferSize = 512)
{
return SerialMessagePort.From(
@@ -145,103 +160,128 @@ public ISerialMessagePort CreateSerialMessagePort(SerialPortName portName, byte[
messageLength);
}
+ ///
public ISerialPort CreateSerialPort(SerialPortName portName, int baudRate = 9600, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One, int readBufferSize = 1024)
{
return new SerialPortProxy(portName, baudRate, dataBits, parity, stopBits, readBufferSize);
}
+ ///
public IPin GetPin(string name)
{
return Pins[name];
}
+ ///
+ public IDigitalSignalAnalyzer CreateDigitalSignalAnalyzer(IPin pin, bool captureDutyCycle)
+ {
+ return new SoftDigitalSignalAnalyzer(pin, captureDutyCycle: captureDutyCycle);
+ }
- // ========= not implemented below here =========
+ // ========= not implemented below here =========
+ ///
public II2cBus CreateI2cBus(int busNumber = 0)
{
throw new NotImplementedException();
}
+ ///
public IPwmPort CreatePwmPort(IPin pin, float frequency = 100, float dutyCycle = 0.5F, bool invert = false)
{
throw new NotImplementedException();
}
+ ///
public ISpiBus CreateSpiBus(IPin clock, IPin mosi, IPin miso, SpiClockConfiguration config)
{
throw new NotImplementedException();
}
+ ///
public ISpiBus CreateSpiBus(IPin clock, IPin mosi, IPin miso, Meadow.Units.Frequency speed)
{
throw new NotImplementedException();
}
+ ///
public void Initialize(MeadowPlatform detectedPlatform)
{
}
+ ///
public void Reset()
{
throw new NotImplementedException();
}
+ ///
public void SetClock(DateTime dateTime)
{
throw new NotImplementedException();
}
+ ///
public void WatchdogEnable(TimeSpan timeout)
{
throw new NotImplementedException();
}
+ ///
public void WatchdogReset()
{
throw new NotImplementedException();
}
+ ///
public void Sleep(int seconds = -1)
{
throw new NotImplementedException();
}
+ ///
public BatteryInfo GetBatteryInfo()
{
throw new NotImplementedException();
}
+ ///
public Temperature GetProcessorTemperature()
{
throw new NotImplementedException();
}
+ ///
public IPwmPort CreatePwmPort(IPin pin, Frequency frequency, float dutyCycle = 0.5F, bool invert = false)
{
throw new NotImplementedException();
}
+ ///
public ICounter CreateCounter(IPin pin, InterruptMode edge)
{
throw new NotImplementedException();
}
+ ///
public II2cBus CreateI2cBus(int busNumber, I2cBusSpeed busSpeed)
{
throw new NotImplementedException();
}
+ ///
public II2cBus CreateI2cBus(IPin[] pins, I2cBusSpeed busSpeed)
{
throw new NotImplementedException();
}
+ ///
public II2cBus CreateI2cBus(IPin clock, IPin data, I2cBusSpeed busSpeed)
{
throw new NotImplementedException();
}
+ ///
public IDigitalInterruptPort CreateDigitalInterruptPort(IPin pin, InterruptMode interruptMode)
{
return CreateDigitalInterruptPort(
@@ -269,21 +309,25 @@ public IBiDirectionalInterruptPort CreateBiDirectionalInterruptPort(IPin pin)
TimeSpan.Zero);
}
+ ///
public IBiDirectionalInterruptPort CreateBiDirectionalInterruptPort(IPin pin, bool initialState, InterruptMode interruptMode, ResistorMode resistorMode, PortDirectionType initialDirection, TimeSpan debounceDuration, TimeSpan glitchDuration, OutputType output = OutputType.PushPull)
{
throw new NotImplementedException();
}
+ ///
public IBiDirectionalPort CreateBiDirectionalPort(IPin pin, bool initialState = false)
{
throw new NotImplementedException();
}
+ ///
public IAnalogInputArray CreateAnalogInputArray(params IPin[] pins)
{
throw new NotImplementedException();
}
+ ///
public ISpiBus CreateSpiBus(int busNumber, Frequency speed)
{
throw new NotImplementedException();
diff --git a/source/implementations/simulation/SimulatedApp/SimulatedApp.csproj b/source/implementations/simulation/SimulatedApp/SimulatedApp.csproj
index 46e94e4f7..87b4d3472 100644
--- a/source/implementations/simulation/SimulatedApp/SimulatedApp.csproj
+++ b/source/implementations/simulation/SimulatedApp/SimulatedApp.csproj
@@ -12,6 +12,6 @@
App
-
+
diff --git a/source/implementations/simulation/SimulatorHost/SimulatorHost.csproj b/source/implementations/simulation/SimulatorHost/SimulatorHost.csproj
index 7ceb35c4c..a59f76a77 100644
--- a/source/implementations/simulation/SimulatorHost/SimulatorHost.csproj
+++ b/source/implementations/simulation/SimulatorHost/SimulatorHost.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/source/implementations/windows/Meadow.Windows/Meadow.Windows.csproj b/source/implementations/windows/Meadow.Windows/Meadow.Windows.csproj
index beb069053..a43a750f2 100644
--- a/source/implementations/windows/Meadow.Windows/Meadow.Windows.csproj
+++ b/source/implementations/windows/Meadow.Windows/Meadow.Windows.csproj
@@ -19,11 +19,16 @@
-
-
+
+
+
+
+
+
-
-
+
+
+
diff --git a/source/implementations/windows/Meadow.Windows/Windows.cs b/source/implementations/windows/Meadow.Windows/Windows.cs
index 2051209b9..d5cfdde67 100644
--- a/source/implementations/windows/Meadow.Windows/Windows.cs
+++ b/source/implementations/windows/Meadow.Windows/Windows.cs
@@ -95,6 +95,11 @@ public IDigitalOutputPort CreateDigitalOutputPort(IPin pin, bool initialState =
throw new NotSupportedException("Add an IO Expander to your platform");
}
+ ///
+ public IDigitalSignalAnalyzer CreateDigitalSignalAnalyzer(IPin pin, bool captureDutyCycle)
+ {
+ throw new NotSupportedException("Add an IO Expander to your platform");
+ }
public ISerialPort CreateSerialPort(string portName, int baudRate = 9600, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One, int readBufferSize = 1024)
{
diff --git a/source/ui/Meadow.Avalonia/Meadow.Avalonia.csproj b/source/ui/Meadow.Avalonia/Meadow.Avalonia.csproj
index 438c75491..da557de0e 100644
--- a/source/ui/Meadow.Avalonia/Meadow.Avalonia.csproj
+++ b/source/ui/Meadow.Avalonia/Meadow.Avalonia.csproj
@@ -19,10 +19,12 @@
Utility library for aiding in using Wilderness Labs' Meadow stack in an Avalonia application
-
+
+
+
-
+
diff --git a/source/ui/Meadow.Maui/Meadow.Maui.csproj b/source/ui/Meadow.Maui/Meadow.Maui.csproj
index a8b08ba7d..909685e37 100644
--- a/source/ui/Meadow.Maui/Meadow.Maui.csproj
+++ b/source/ui/Meadow.Maui/Meadow.Maui.csproj
@@ -26,6 +26,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
+