From 05161948af0f6bedbb32ca56f7e1fba28b83db79 Mon Sep 17 00:00:00 2001 From: Chris Tacke Date: Mon, 16 Oct 2023 15:55:03 -0500 Subject: [PATCH 1/5] working on sensor service --- source/Meadow.Core/MeadowOS.cs | 3 + source/Meadow.Core/SensorService.cs | 105 ++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 source/Meadow.Core/SensorService.cs diff --git a/source/Meadow.Core/MeadowOS.cs b/source/Meadow.Core/MeadowOS.cs index 75ca7ed3e..38b1aaf26 100644 --- a/source/Meadow.Core/MeadowOS.cs +++ b/source/Meadow.Core/MeadowOS.cs @@ -1,5 +1,6 @@ using Meadow.Cloud; using Meadow.Logging; +using Meadow.Peripherals.Sensors; using Meadow.Update; using System; using System.Collections.Generic; @@ -88,6 +89,8 @@ private static async Task Start(string[]? args, IApp? app) ReportAppException(e, "Device (system) Initialization Failure"); } + Resolver.Services.Add(new SensorService()); + if (systemInitialized) { var stepName = "App Initialize"; diff --git a/source/Meadow.Core/SensorService.cs b/source/Meadow.Core/SensorService.cs new file mode 100644 index 000000000..3c36391cf --- /dev/null +++ b/source/Meadow.Core/SensorService.cs @@ -0,0 +1,105 @@ +using Meadow.Peripherals.Sensors; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Meadow; + +internal class ThreadedPollingSensorMonitor : ISensorMonitor +{ + public EventHandler SampleAvailable = default!; + + private readonly List<(ISensor sensor, MethodInfo readMethod)> _monitorList = new(); + private Thread? _sampleThread; + private bool _stopRequested = false; + private PropertyInfo? _resultProperty; + private List _sensorList = new(); + private long _tick; + + + public ThreadedPollingSensorMonitor() + { + } + + public void AddSensor(ISensor sensor, TimeSpan updateInterval) + { + // find the Read method, if it exists + // this is just a limitation of C# generics - looking for a better way + var readMethod = sensor.GetType().GetMethod("Read", BindingFlags.Instance | BindingFlags.Public); + + if (readMethod != null) + { + _monitorList.Add((sensor, readMethod)); + } + } + + public void StartSampling(ISensor sensor) + { + if (_sampleThread == null) + { + _sampleThread = new Thread(SamplingThreadProc); + _sampleThread.Start(); + } + + _sensorList.Add(sensor); + } + + private async void SamplingThreadProc() + { + while (!_stopRequested) + { + foreach (var monitor in _monitorList) + { + if (_sensorList.Contains(monitor.sensor)) + { + // TODO: check to see if the sensor should be read + + var task = (Task)monitor.readMethod.Invoke(monitor.sensor, null); + await task.ConfigureAwait(false); + + if (_resultProperty == null) + { + _resultProperty = task.GetType().GetProperty("Result"); + } + SampleAvailable?.Invoke(monitor.sensor, _resultProperty.GetValue(task)); + + _tick++; + } + } + + Thread.Sleep(1000); + } + + _stopRequested = false; + _sampleThread = null; + } + + public void StopSampling(ISensor sensor) + { + _sensorList.Remove(sensor); + } +} + +internal class SensorService : ISensorService +{ + private ThreadedPollingSensorMonitor? _pollMonitor; + + internal SensorService() + { + } + + public void RegisterSensor(ISamplingSensor sensor) + { + if (_pollMonitor == null) + { + _pollMonitor = new ThreadedPollingSensorMonitor(); + } + + if (sensor is ISensor s) + { + _pollMonitor.StartSampling(s); + } + } +} From 58b4dcdbc194cb8b6b41e2a82f2fd712a490c911 Mon Sep 17 00:00:00 2001 From: Chris Tacke Date: Wed, 18 Oct 2023 09:30:19 -0500 Subject: [PATCH 2/5] added storage info --- source/Meadow.Core/StorageInformation.cs | 18 ++ .../f7/Meadow.F7/F7PlatformOS.Storage.cs | 56 +++- .../f7/Meadow.F7/F7PlatformOS.cs | 8 +- .../Interop/Interop.clock_gettime.cs | 3 +- .../Interop/Interop.clock_settime.cs | 3 +- .../f7/Meadow.F7/Interop/Interop.cs | 5 +- .../f7/Meadow.F7/Interop/Interop.gpio.cs | 1 - .../f7/Meadow.F7/Interop/Interop.ioctl.cs | 3 +- source/implementations/f7/Meadow.F7/UPD.cs | 9 +- .../PlatformOS/LinuxPlatformOS.cs | 10 + .../Meadow.Simulation/SimulatedPlatformOS.cs | 10 + .../Meadow.Windows/WindowsPlatformOS.cs | 279 +++++++++--------- 12 files changed, 248 insertions(+), 157 deletions(-) create mode 100644 source/Meadow.Core/StorageInformation.cs diff --git a/source/Meadow.Core/StorageInformation.cs b/source/Meadow.Core/StorageInformation.cs new file mode 100644 index 000000000..22a826b1f --- /dev/null +++ b/source/Meadow.Core/StorageInformation.cs @@ -0,0 +1,18 @@ +using Meadow.Units; + +namespace Meadow; + +/// +/// Information about available storage devices +/// +public abstract class StorageInformation : IStorageInformation +{ + /// + public string Name { get; protected set; } = default!; + + /// + public DigitalStorage SpaceAvailable { get; protected set; } = default!; + + /// + public DigitalStorage Size { get; protected set; } = default!; +} diff --git a/source/implementations/f7/Meadow.F7/F7PlatformOS.Storage.cs b/source/implementations/f7/Meadow.F7/F7PlatformOS.Storage.cs index aa334c036..82e9b620b 100644 --- a/source/implementations/f7/Meadow.F7/F7PlatformOS.Storage.cs +++ b/source/implementations/f7/Meadow.F7/F7PlatformOS.Storage.cs @@ -1,10 +1,64 @@ -namespace Meadow; +using Meadow.Devices; +using System.IO; +using System.Linq; + +namespace Meadow; + public partial class F7PlatformOS : IPlatformOS { + /// + /// Meadow F7-specific storage information + /// + public class F7StorageInformation : StorageInformation + { + internal static F7StorageInformation Create(IMeadowDevice device) + { + long totalFlashAvailable; + if (device is F7FeatherV1) + { + totalFlashAvailable = 33_554_432 // 32MB + - 3_145_728 // allocation for runtime + - 2_097_152; // OtA + + // 16_510_459_314_176.10 + // 29_686_813_949_952.1MB + } + else + { + totalFlashAvailable = 67_108_864 // 64MB + - 3_145_728 // allocation for runtime + - 2_097_152; // OtA + + + } + + var spaceConsumed = new DirectoryInfo("/meadow0") + .EnumerateFiles("*", SearchOption.AllDirectories) + .Sum(file => file.Length); + + return new F7StorageInformation + { + Name = "Internal Flash", + Size = new Units.DigitalStorage(totalFlashAvailable), + SpaceAvailable = new Units.DigitalStorage(totalFlashAvailable - spaceConsumed) + }; + } + } + /// /// Gets the file system information for the platform. /// public Meadow.IPlatformOS.FileSystemInfo FileSystem { get; private set; } = default!; + /// + public IStorageInformation[] GetStorageInformation() + { + // TODO: support external storage for MMC-enabled CCM devices + + return new IStorageInformation[] + { + F7StorageInformation.Create(Resolver.Device) + }; + } } diff --git a/source/implementations/f7/Meadow.F7/F7PlatformOS.cs b/source/implementations/f7/Meadow.F7/F7PlatformOS.cs index 95e441265..f0887a03a 100644 --- a/source/implementations/f7/Meadow.F7/F7PlatformOS.cs +++ b/source/implementations/f7/Meadow.F7/F7PlatformOS.cs @@ -87,11 +87,9 @@ public AllocationInfo GetMemoryAllocationInfo() return Core.Interop.Nuttx.mallinfo(); } - /// - /// Retrieves the current processor usage (as a percentage in the range of 0-100) - /// - public int ProcessorLoad() + /// + public int[] GetProcessorUtilization() { - return 100 - Core.Interop.Nuttx.meadow_idle_monitor_get_value(); + return new int[100 - Core.Interop.Nuttx.meadow_idle_monitor_get_value()]; } } diff --git a/source/implementations/f7/Meadow.F7/Interop/Interop.clock_gettime.cs b/source/implementations/f7/Meadow.F7/Interop/Interop.clock_gettime.cs index 4fb4ba992..7a3a5038f 100644 --- a/source/implementations/f7/Meadow.F7/Interop/Interop.clock_gettime.cs +++ b/source/implementations/f7/Meadow.F7/Interop/Interop.clock_gettime.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace Meadow.Core { diff --git a/source/implementations/f7/Meadow.F7/Interop/Interop.clock_settime.cs b/source/implementations/f7/Meadow.F7/Interop/Interop.clock_settime.cs index 4162e4aad..e9073ad33 100644 --- a/source/implementations/f7/Meadow.F7/Interop/Interop.clock_settime.cs +++ b/source/implementations/f7/Meadow.F7/Interop/Interop.clock_settime.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace Meadow.Core { diff --git a/source/implementations/f7/Meadow.F7/Interop/Interop.cs b/source/implementations/f7/Meadow.F7/Interop/Interop.cs index 45076b2f3..98b19f6f1 100644 --- a/source/implementations/f7/Meadow.F7/Interop/Interop.cs +++ b/source/implementations/f7/Meadow.F7/Interop/Interop.cs @@ -1,7 +1,4 @@ -using System; -using System.Runtime.InteropServices; - -namespace Meadow.Core +namespace Meadow.Core { internal static partial class Interop { diff --git a/source/implementations/f7/Meadow.F7/Interop/Interop.gpio.cs b/source/implementations/f7/Meadow.F7/Interop/Interop.gpio.cs index 4dbdbc45f..3f1d0aafa 100644 --- a/source/implementations/f7/Meadow.F7/Interop/Interop.gpio.cs +++ b/source/implementations/f7/Meadow.F7/Interop/Interop.gpio.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; namespace Meadow.Core { diff --git a/source/implementations/f7/Meadow.F7/Interop/Interop.ioctl.cs b/source/implementations/f7/Meadow.F7/Interop/Interop.ioctl.cs index 26e74f59d..e30d5b6de 100644 --- a/source/implementations/f7/Meadow.F7/Interop/Interop.ioctl.cs +++ b/source/implementations/f7/Meadow.F7/Interop/Interop.ioctl.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.InteropServices; using System.Text; -using Meadow.Devices.Esp32.MessagePayloads; namespace Meadow.Core { @@ -58,7 +57,7 @@ public enum GpioIoctlFn [DllImport(LIBRARY_NAME, SetLastError = true)] public static extern int ioctl(IntPtr fd, UpdIoctlFn request, ref Nuttx.UpdSleepCommand command); - + /// /// Configures the Universal Platform Driver to catch GPIO interrupts /// diff --git a/source/implementations/f7/Meadow.F7/UPD.cs b/source/implementations/f7/Meadow.F7/UPD.cs index d380d8bbf..4715e4e06 100644 --- a/source/implementations/f7/Meadow.F7/UPD.cs +++ b/source/implementations/f7/Meadow.F7/UPD.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using static Meadow.Core.Interop; -using Meadow.Devices.Esp32.MessagePayloads; namespace Meadow.Devices { @@ -168,7 +167,7 @@ public static int Ioctl(Nuttx.UpdIoctlFn request, Nuttx.UpdSleepCommand command) } return result; } - + public static int Ioctl(Nuttx.UpdIoctlFn request, ref Nuttx.UpdGpioInterruptConfiguration interruptConfig) { @@ -274,7 +273,7 @@ public static int Ioctl(Nuttx.UpdIoctlFn request, ref Nuttx.UpdEsp32Command cmd) { var err = GetLastError(); Resolver.Log.Error($"ioctl {request} failed {err}"); - return (int) err; + return (int)err; } return result; } @@ -286,7 +285,7 @@ public static int Ioctl(Nuttx.UpdIoctlFn request, ref Nuttx.UpdEsp32EventDataPay { var err = GetLastError(); Resolver.Log.Error($"ioctl {request} failed {err}"); - return (int) err; + return (int)err; } return result; } @@ -298,7 +297,7 @@ public static int Ioctl(Nuttx.UpdIoctlFn request, ref Nuttx.UpdConfigurationValu { var err = GetLastError(); Resolver.Log.Error($"ioctl {request} failed {err}"); - return (int) err; + return (int)err; } return result; } diff --git a/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxPlatformOS.cs b/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxPlatformOS.cs index b3d905daf..fdd300a7a 100644 --- a/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxPlatformOS.cs +++ b/source/implementations/linux/Meadow.Linux/PlatformOS/LinuxPlatformOS.cs @@ -212,4 +212,14 @@ public byte[] AesDecrypt(byte[] encryptedValue, byte[] key, byte[] iv) } } } + + public int[] GetProcessorUtilization() + { + throw new NotImplementedException(); + } + + public IStorageInformation[] GetStorageInformation() + { + throw new NotImplementedException(); + } } diff --git a/source/implementations/simulation/Meadow.Simulation/SimulatedPlatformOS.cs b/source/implementations/simulation/Meadow.Simulation/SimulatedPlatformOS.cs index 87f575832..2e7766ed9 100644 --- a/source/implementations/simulation/Meadow.Simulation/SimulatedPlatformOS.cs +++ b/source/implementations/simulation/Meadow.Simulation/SimulatedPlatformOS.cs @@ -156,4 +156,14 @@ public byte[] AesDecrypt(byte[] encryptedValue, byte[] key, byte[] iv) } } } + + public int[] GetProcessorUtilization() + { + throw new NotImplementedException(); + } + + public IStorageInformation[] GetStorageInformation() + { + throw new NotImplementedException(); + } } diff --git a/source/implementations/windows/Meadow.Windows/WindowsPlatformOS.cs b/source/implementations/windows/Meadow.Windows/WindowsPlatformOS.cs index d576a945c..3210a0f40 100644 --- a/source/implementations/windows/Meadow.Windows/WindowsPlatformOS.cs +++ b/source/implementations/windows/Meadow.Windows/WindowsPlatformOS.cs @@ -7,95 +7,95 @@ using System.Linq; using System.Security.Cryptography; -namespace Meadow +namespace Meadow; + +public class WindowsPlatformOS : IPlatformOS { - public class WindowsPlatformOS : IPlatformOS + /// + /// Event raised before a software reset + /// + public event PowerTransitionHandler BeforeReset = delegate { }; + /// + /// Event raised before Sleep mode + /// + public event PowerTransitionHandler BeforeSleep = delegate { }; + /// + /// Event raised after returning from Sleep mode + /// + public event PowerTransitionHandler AfterWake = delegate { }; + /// + /// Event raised when an external storage device event occurs. + /// + public event ExternalStorageEventHandler ExternalStorageEvent = delegate { }; + + /// + /// Gets the OS version. + /// + /// OS version. + public string OSVersion { get; } + /// + /// Gets the OS build date. + /// + /// OS build date. + public string OSBuildDate { get; } + /// + /// Get the current .NET runtime version being used to execute the application. + /// + /// Mono version. + public string RuntimeVersion { get; } + + /// + /// The command line arguments provided when the Meadow application was launched + /// + public string[]? LaunchArguments { get; private set; } + + public IPlatformOS.FileSystemInfo FileSystem { get; } + + /// + /// Default constructor for the WindowsPlatformOS object. + /// + internal WindowsPlatformOS() { - /// - /// Event raised before a software reset - /// - public event PowerTransitionHandler BeforeReset = delegate { }; - /// - /// Event raised before Sleep mode - /// - public event PowerTransitionHandler BeforeSleep = delegate { }; - /// - /// Event raised after returning from Sleep mode - /// - public event PowerTransitionHandler AfterWake = delegate { }; - /// - /// Event raised when an external storage device event occurs. - /// - public event ExternalStorageEventHandler ExternalStorageEvent = delegate { }; - - /// - /// Gets the OS version. - /// - /// OS version. - public string OSVersion { get; } - /// - /// Gets the OS build date. - /// - /// OS build date. - public string OSBuildDate { get; } - /// - /// Get the current .NET runtime version being used to execute the application. - /// - /// Mono version. - public string RuntimeVersion { get; } - - /// - /// The command line arguments provided when the Meadow application was launched - /// - public string[]? LaunchArguments { get; private set; } - - public IPlatformOS.FileSystemInfo FileSystem { get; } - - /// - /// Default constructor for the WindowsPlatformOS object. - /// - internal WindowsPlatformOS() - { - OSVersion = Environment.OSVersion.ToString(); - OSBuildDate = "Unknown"; - RuntimeVersion = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; - FileSystem = new WindowsFileSystemInfo(); - } + OSVersion = Environment.OSVersion.ToString(); + OSBuildDate = "Unknown"; + RuntimeVersion = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; + FileSystem = new WindowsFileSystemInfo(); + } - /// - /// Initialize the WindowsPlatformOS instance. - /// - /// - /// The command line arguments provided when the Meadow application was launched - public void Initialize(DeviceCapabilities capabilities, string[]? args) - { - // TODO: deal with capabilities - } + /// + /// Initialize the WindowsPlatformOS instance. + /// + /// + /// The command line arguments provided when the Meadow application was launched + public void Initialize(DeviceCapabilities capabilities, string[]? args) + { + // TODO: deal with capabilities + } - /// - /// Gets the name of all available serial ports on the platform - /// - /// A list of available serial port names - public SerialPortName[] GetSerialPortNames() - { - return SerialPort.GetPortNames().Select(n => - new SerialPortName(n, n, Resolver.Device)) - .ToArray(); - } + /// + /// Gets the name of all available serial ports on the platform + /// + /// A list of available serial port names + public SerialPortName[] GetSerialPortNames() + { + return SerialPort.GetPortNames().Select(n => + new SerialPortName(n, n, Resolver.Device)) + .ToArray(); + } - public Temperature GetCpuTemperature() - { - throw new PlatformNotSupportedException(); - } + public Temperature GetCpuTemperature() + { + throw new PlatformNotSupportedException(); + } - /// - /// Sets the platform OS clock - /// - /// - public void SetClock(DateTime dateTime) - { - throw new PlatformNotSupportedException(); - } + /// + /// Sets the platform OS clock + /// + /// + public void SetClock(DateTime dateTime) + { + throw new PlatformNotSupportedException(); + } @@ -104,70 +104,79 @@ public void SetClock(DateTime dateTime) - // TODO: implement everything below here + // TODO: implement everything below here - public string ReservedPins => string.Empty; - public IEnumerable ExternalStorage => throw new NotImplementedException(); - public INtpClient NtpClient => throw new NotImplementedException(); - public bool RebootOnUnhandledException => throw new NotImplementedException(); - public uint InitializationTimeout => throw new NotImplementedException(); - public bool AutomaticallyStartNetwork => throw new NotImplementedException(); - public IPlatformOS.NetworkConnectionType SelectedNetwork => throw new NotImplementedException(); - public bool SdStorageSupported => throw new NotImplementedException(); + public string ReservedPins => string.Empty; + public IEnumerable ExternalStorage => throw new NotImplementedException(); + public INtpClient NtpClient => throw new NotImplementedException(); + public bool RebootOnUnhandledException => throw new NotImplementedException(); + public uint InitializationTimeout => throw new NotImplementedException(); + public bool AutomaticallyStartNetwork => throw new NotImplementedException(); + public IPlatformOS.NetworkConnectionType SelectedNetwork => throw new NotImplementedException(); + public bool SdStorageSupported => throw new NotImplementedException(); - public T GetConfigurationValue(IPlatformOS.ConfigurationValues item) where T : struct - { - throw new NotImplementedException(); - } + public T GetConfigurationValue(IPlatformOS.ConfigurationValues item) where T : struct + { + throw new NotImplementedException(); + } - public void RegisterForSleep(ISleepAwarePeripheral peripheral) - { - throw new NotImplementedException(); - } + public void RegisterForSleep(ISleepAwarePeripheral peripheral) + { + throw new NotImplementedException(); + } - public void Reset() - { - throw new NotImplementedException(); - } + public void Reset() + { + throw new NotImplementedException(); + } - public void SetConfigurationValue(IPlatformOS.ConfigurationValues item, T value) where T : struct - { - throw new NotImplementedException(); - } + public void SetConfigurationValue(IPlatformOS.ConfigurationValues item, T value) where T : struct + { + throw new NotImplementedException(); + } - public void Sleep(TimeSpan duration) - { - throw new NotImplementedException(); - } + public void Sleep(TimeSpan duration) + { + throw new NotImplementedException(); + } - public byte[] RsaDecrypt(byte[] encryptedValue) - { - var rsa = RSA.Create(); - return rsa.Decrypt(encryptedValue, RSAEncryptionPadding.Pkcs1); - } + public byte[] RsaDecrypt(byte[] encryptedValue) + { + var rsa = RSA.Create(); + return rsa.Decrypt(encryptedValue, RSAEncryptionPadding.Pkcs1); + } - public byte[] AesDecrypt(byte[] encryptedValue, byte[] key, byte[] iv) + public byte[] AesDecrypt(byte[] encryptedValue, byte[] key, byte[] iv) + { + // Create an Aes object + // with the specified key and IV. + using (Aes aesAlg = Aes.Create()) { - // Create an Aes object - // with the specified key and IV. - using (Aes aesAlg = Aes.Create()) + aesAlg.Key = key; + aesAlg.IV = iv; + + // Create a decryptor to perform the stream transform. + var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); + + // Create the streams used for decryption. + using (var msDecrypt = new MemoryStream(encryptedValue)) + using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) { - aesAlg.Key = key; - aesAlg.IV = iv; - - // Create a decryptor to perform the stream transform. - var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); - - // Create the streams used for decryption. - using (var msDecrypt = new MemoryStream(encryptedValue)) - using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) - { - var buffer = new byte[csDecrypt.Length]; - csDecrypt.Read(buffer, 0, buffer.Length); - return buffer; - } + var buffer = new byte[csDecrypt.Length]; + csDecrypt.Read(buffer, 0, buffer.Length); + return buffer; } } } + + public int[] GetProcessorUtilization() + { + throw new NotImplementedException(); + } + + public IStorageInformation[] GetStorageInformation() + { + throw new NotImplementedException(); + } } From 1780c8eb8ed080cbf2e8d26e46d8b11656575f04 Mon Sep 17 00:00:00 2001 From: Chris Tacke Date: Thu, 19 Oct 2023 10:57:49 -0500 Subject: [PATCH 3/5] single-thread sensor monitor --- source/Meadow.Core/SensorService.cs | 93 ++----------- .../ThreadedPollingSensorMonitor.cs | 130 ++++++++++++++++++ .../f7/Meadow.F7/F7PlatformOS.Storage.cs | 39 ------ 3 files changed, 141 insertions(+), 121 deletions(-) create mode 100644 source/Meadow.Core/ThreadedPollingSensorMonitor.cs diff --git a/source/Meadow.Core/SensorService.cs b/source/Meadow.Core/SensorService.cs index 3c36391cf..ec97cbef3 100644 --- a/source/Meadow.Core/SensorService.cs +++ b/source/Meadow.Core/SensorService.cs @@ -1,87 +1,7 @@ using Meadow.Peripherals.Sensors; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; namespace Meadow; -internal class ThreadedPollingSensorMonitor : ISensorMonitor -{ - public EventHandler SampleAvailable = default!; - - private readonly List<(ISensor sensor, MethodInfo readMethod)> _monitorList = new(); - private Thread? _sampleThread; - private bool _stopRequested = false; - private PropertyInfo? _resultProperty; - private List _sensorList = new(); - private long _tick; - - - public ThreadedPollingSensorMonitor() - { - } - - public void AddSensor(ISensor sensor, TimeSpan updateInterval) - { - // find the Read method, if it exists - // this is just a limitation of C# generics - looking for a better way - var readMethod = sensor.GetType().GetMethod("Read", BindingFlags.Instance | BindingFlags.Public); - - if (readMethod != null) - { - _monitorList.Add((sensor, readMethod)); - } - } - - public void StartSampling(ISensor sensor) - { - if (_sampleThread == null) - { - _sampleThread = new Thread(SamplingThreadProc); - _sampleThread.Start(); - } - - _sensorList.Add(sensor); - } - - private async void SamplingThreadProc() - { - while (!_stopRequested) - { - foreach (var monitor in _monitorList) - { - if (_sensorList.Contains(monitor.sensor)) - { - // TODO: check to see if the sensor should be read - - var task = (Task)monitor.readMethod.Invoke(monitor.sensor, null); - await task.ConfigureAwait(false); - - if (_resultProperty == null) - { - _resultProperty = task.GetType().GetProperty("Result"); - } - SampleAvailable?.Invoke(monitor.sensor, _resultProperty.GetValue(task)); - - _tick++; - } - } - - Thread.Sleep(1000); - } - - _stopRequested = false; - _sampleThread = null; - } - - public void StopSampling(ISensor sensor) - { - _sensorList.Remove(sensor); - } -} - internal class SensorService : ISensorService { private ThreadedPollingSensorMonitor? _pollMonitor; @@ -90,16 +10,25 @@ internal SensorService() { } - public void RegisterSensor(ISamplingSensor sensor) + public void RegisterSensor(ISensor sensor) { if (_pollMonitor == null) { _pollMonitor = new ThreadedPollingSensorMonitor(); } - if (sensor is ISensor s) + if (sensor is IPollingSensor s) { + s.SensorMonitor?.StopSampling(s); + + s.StopUpdating(); + + Resolver.Log.Info($"Replacing monitor on {s.GetType().Name}"); + + s.SensorMonitor = _pollMonitor; + _pollMonitor.StartSampling(s); } } } + diff --git a/source/Meadow.Core/ThreadedPollingSensorMonitor.cs b/source/Meadow.Core/ThreadedPollingSensorMonitor.cs new file mode 100644 index 000000000..e307c3f40 --- /dev/null +++ b/source/Meadow.Core/ThreadedPollingSensorMonitor.cs @@ -0,0 +1,130 @@ +using Meadow.Peripherals.Sensors; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Meadow; + +internal class ThreadedPollingSensorMonitor : ISensorMonitor +{ + public event EventHandler SampleAvailable = default!; + + private readonly List _monitorList = new(); + private Thread _sampleThread; + private bool _stopRequested = false; + + private class SensorMeta + { + public SensorMeta(ISamplingSensor sensor, MethodInfo readmethod) + { + Sensor = sensor; + ReadMethod = readmethod; + } + + public ISamplingSensor Sensor { get; set; } + public MethodInfo ReadMethod { get; set; } + public PropertyInfo? ResultProperty { get; set; } + public TimeSpan NextReading { get; set; } + public bool EnableReading { get; set; } + } + + public ThreadedPollingSensorMonitor() + { + _sampleThread = new Thread(SamplingThreadProc); + _sampleThread.Start(); + } + + public void StartSampling(ISamplingSensor sensor) + { + var existing = _monitorList.FirstOrDefault(s => s.Sensor.Equals(sensor)); + + if (existing == null) + { + // find the Read method, if it exists + // this is just a limitation of C# generics - looking for a better way + var readMethod = sensor.GetType().GetMethod("Read", BindingFlags.Instance | BindingFlags.Public); + + if (readMethod != null) + { + var meta = new SensorMeta(sensor, readMethod) + { + EnableReading = true, + NextReading = sensor.UpdateInterval + }; + + _monitorList.Add(meta); + } + } + else + { + existing.NextReading = sensor.UpdateInterval; + existing.EnableReading = true; + } + } + + private async void SamplingThreadProc() + { + while (!_stopRequested) + { + foreach (var monitor in _monitorList) + { + // skip "disabled" (i.e. StopUpdating has been called) sensors + if (!monitor.EnableReading) continue; + + // check for the reading due tiime (1s granularity for now) + var remaining = monitor.NextReading.Subtract(TimeSpan.FromSeconds(1)); + + if (remaining.TotalSeconds <= 0) + { + // reset the next reading + monitor.NextReading = monitor.Sensor.UpdateInterval; + + // read the sensor + try + { + var task = (Task)monitor.ReadMethod.Invoke(monitor.Sensor, null); + await task.ConfigureAwait(false); + + if (monitor.ResultProperty == null) + { + monitor.ResultProperty = task.GetType().GetProperty("Result"); + } + + var value = monitor.ResultProperty.GetValue(task); + + // raise an event - not ideal as all sensors get events for all other sensors + // fixing this requires eitehr exposing a "set" method, which I'd prefer be kept internal + // or using reflection to find a set method, which is fragile + SampleAvailable?.Invoke(monitor.Sensor, value); + } + catch (Exception ex) + { + Resolver.Log.Error($"Unhandled exception reading sensor type {monitor.Sensor.GetType().Name}: {ex.Message}"); + } + } + else + { + monitor.NextReading = remaining; + } + } + + Thread.Sleep(1000); // TODO: improve this algorithm + } + + _stopRequested = false; + } + + public void StopSampling(ISamplingSensor sensor) + { + var existing = _monitorList.FirstOrDefault(s => s.Sensor.Equals(sensor)); + + if (existing != null) + { + existing.EnableReading = false; + } + } +} + diff --git a/source/implementations/f7/Meadow.F7/F7PlatformOS.Storage.cs b/source/implementations/f7/Meadow.F7/F7PlatformOS.Storage.cs index 44cb771c1..afe147165 100644 --- a/source/implementations/f7/Meadow.F7/F7PlatformOS.Storage.cs +++ b/source/implementations/f7/Meadow.F7/F7PlatformOS.Storage.cs @@ -42,45 +42,6 @@ internal static F7StorageInformation Create(IMeadowDevice device) } } - /// - /// Meadow F7-specific storage information - /// - public class F7StorageInformation : StorageInformation - { - internal static F7StorageInformation Create(IMeadowDevice device) - { - long totalFlashAvailable; - if (device is F7FeatherV1) - { - totalFlashAvailable = 33_554_432 // 32MB - - 3_145_728 // allocation for runtime - - 2_097_152; // OtA - - // 16_510_459_314_176.10 - // 29_686_813_949_952.1MB - } - else - { - totalFlashAvailable = 67_108_864 // 64MB - - 3_145_728 // allocation for runtime - - 2_097_152; // OtA - - - } - - var spaceConsumed = new DirectoryInfo("/meadow0") - .EnumerateFiles("*", SearchOption.AllDirectories) - .Sum(file => file.Length); - - return new F7StorageInformation - { - Name = "Internal Flash", - Size = new Units.DigitalStorage(totalFlashAvailable), - SpaceAvailable = new Units.DigitalStorage(totalFlashAvailable - spaceConsumed) - }; - } - } - /// /// Gets the file system information for the platform. /// From af61f471e71b0f4882f73e467557520684a4a494 Mon Sep 17 00:00:00 2001 From: Chris Tacke Date: Thu, 19 Oct 2023 16:15:53 -0500 Subject: [PATCH 4/5] sensor service cleaning --- source/Meadow.Core/SensorService.cs | 2 - .../ThreadedPollingSensorMonitor.cs | 45 ++++++++++--------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/source/Meadow.Core/SensorService.cs b/source/Meadow.Core/SensorService.cs index ec97cbef3..c992cff68 100644 --- a/source/Meadow.Core/SensorService.cs +++ b/source/Meadow.Core/SensorService.cs @@ -23,8 +23,6 @@ public void RegisterSensor(ISensor sensor) s.StopUpdating(); - Resolver.Log.Info($"Replacing monitor on {s.GetType().Name}"); - s.SensorMonitor = _pollMonitor; _pollMonitor.StartSampling(s); diff --git a/source/Meadow.Core/ThreadedPollingSensorMonitor.cs b/source/Meadow.Core/ThreadedPollingSensorMonitor.cs index e307c3f40..5b9a6a3e3 100644 --- a/source/Meadow.Core/ThreadedPollingSensorMonitor.cs +++ b/source/Meadow.Core/ThreadedPollingSensorMonitor.cs @@ -12,9 +12,10 @@ internal class ThreadedPollingSensorMonitor : ISensorMonitor { public event EventHandler SampleAvailable = default!; - private readonly List _monitorList = new(); - private Thread _sampleThread; - private bool _stopRequested = false; + private readonly List _metaList = new(); + private readonly Thread _sampleThread; + private readonly Random _random = new(); + private readonly TimeSpan PollPeriod = TimeSpan.FromSeconds(1); private class SensorMeta { @@ -39,7 +40,7 @@ public ThreadedPollingSensorMonitor() public void StartSampling(ISamplingSensor sensor) { - var existing = _monitorList.FirstOrDefault(s => s.Sensor.Equals(sensor)); + var existing = _metaList.FirstOrDefault(s => s.Sensor.Equals(sensor)); if (existing == null) { @@ -49,13 +50,15 @@ public void StartSampling(ISamplingSensor sensor) if (readMethod != null) { + // wait a random period to attempt to spread the updates over time + var firstInterval = _random.Next(0, (int)(sensor.UpdateInterval.TotalSeconds + 1)); var meta = new SensorMeta(sensor, readMethod) { EnableReading = true, - NextReading = sensor.UpdateInterval + NextReading = TimeSpan.FromSeconds(firstInterval) }; - _monitorList.Add(meta); + _metaList.Add(meta); } } else @@ -67,59 +70,57 @@ public void StartSampling(ISamplingSensor sensor) private async void SamplingThreadProc() { - while (!_stopRequested) + while (true) { - foreach (var monitor in _monitorList) + foreach (var meta in _metaList) { // skip "disabled" (i.e. StopUpdating has been called) sensors - if (!monitor.EnableReading) continue; + if (!meta.EnableReading) continue; // check for the reading due tiime (1s granularity for now) - var remaining = monitor.NextReading.Subtract(TimeSpan.FromSeconds(1)); + var remaining = meta.NextReading.Subtract(TimeSpan.FromSeconds(1)); if (remaining.TotalSeconds <= 0) { // reset the next reading - monitor.NextReading = monitor.Sensor.UpdateInterval; + meta.NextReading = meta.Sensor.UpdateInterval; // read the sensor try { - var task = (Task)monitor.ReadMethod.Invoke(monitor.Sensor, null); + var task = (Task)meta.ReadMethod.Invoke(meta.Sensor, null); await task.ConfigureAwait(false); - if (monitor.ResultProperty == null) + if (meta.ResultProperty == null) { - monitor.ResultProperty = task.GetType().GetProperty("Result"); + meta.ResultProperty = task.GetType().GetProperty("Result"); } - var value = monitor.ResultProperty.GetValue(task); + var value = meta.ResultProperty.GetValue(task); // raise an event - not ideal as all sensors get events for all other sensors // fixing this requires eitehr exposing a "set" method, which I'd prefer be kept internal // or using reflection to find a set method, which is fragile - SampleAvailable?.Invoke(monitor.Sensor, value); + SampleAvailable?.Invoke(meta.Sensor, value); } catch (Exception ex) { - Resolver.Log.Error($"Unhandled exception reading sensor type {monitor.Sensor.GetType().Name}: {ex.Message}"); + Resolver.Log.Error($"Unhandled exception reading sensor type {meta.Sensor.GetType().Name}: {ex.Message}"); } } else { - monitor.NextReading = remaining; + meta.NextReading = remaining; } } - Thread.Sleep(1000); // TODO: improve this algorithm + Thread.Sleep(PollPeriod); // TODO: improve this algorithm } - - _stopRequested = false; } public void StopSampling(ISamplingSensor sensor) { - var existing = _monitorList.FirstOrDefault(s => s.Sensor.Equals(sensor)); + var existing = _metaList.FirstOrDefault(s => s.Sensor.Equals(sensor)); if (existing != null) { From 679485ffff9937b12c46e68e61ff6c6014f98f7f Mon Sep 17 00:00:00 2001 From: Chris Tacke Date: Thu, 19 Oct 2023 17:50:50 -0500 Subject: [PATCH 5/5] expanding sensor service --- source/Meadow.Core/SensorService.cs | 75 ++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/source/Meadow.Core/SensorService.cs b/source/Meadow.Core/SensorService.cs index c992cff68..0ace96d8e 100644 --- a/source/Meadow.Core/SensorService.cs +++ b/source/Meadow.Core/SensorService.cs @@ -1,4 +1,7 @@ using Meadow.Peripherals.Sensors; +using System; +using System.Collections.Generic; +using System.Linq; namespace Meadow; @@ -6,26 +9,84 @@ internal class SensorService : ISensorService { private ThreadedPollingSensorMonitor? _pollMonitor; + private List _sensors = new(); + internal SensorService() { } - public void RegisterSensor(ISensor sensor) + /// + public IEnumerable GetSensorsOfType() + where TSensor : ISensor { - if (_pollMonitor == null) + foreach (var sensor in _sensors) { - _pollMonitor = new ThreadedPollingSensorMonitor(); + if (sensor is TSensor s) + { + yield return s; + } + } + } + + /// + public IEnumerable GetSensorsWithData() + where TUnit : struct + { + // var list = new List(); + + foreach (var sensor in _sensors) + { + var typeToCheck = sensor.GetType(); + + do + { + if (typeToCheck + .GetGenericArguments() + .SelectMany(a => a + .GetFields() + .Where(f => f.FieldType.Equals(typeof(TUnit)) || Nullable.GetUnderlyingType(f.FieldType).Equals(typeof(TUnit)))) + .Any()) + { + yield return sensor; + break; + } + + typeToCheck = typeToCheck.BaseType; + } while (!typeToCheck.Equals(typeof(object))); } + // return list; + } + + /// + public void RegisterSensor(ISensor sensor) + { if (sensor is IPollingSensor s) { - s.SensorMonitor?.StopSampling(s); + if (s.UpdateInterval.TotalSeconds >= 1) + { + if (_pollMonitor == null) + { + _pollMonitor = new ThreadedPollingSensorMonitor(); + } - s.StopUpdating(); + // don't migrate fast-polling sensors to the threaded monitor + s.SensorMonitor?.StopSampling(s); - s.SensorMonitor = _pollMonitor; + s.StopUpdating(); - _pollMonitor.StartSampling(s); + s.SensorMonitor = _pollMonitor; + + _pollMonitor.StartSampling(s); + } + } + + lock (_sensors) + { + if (!_sensors.Contains(sensor)) + { + _sensors.Add(sensor); + } } } }