Refactor serials

This commit is contained in:
Dan Clark
2025-02-10 10:10:16 +00:00
parent 0afe621f59
commit 18ceb512f6
27 changed files with 168 additions and 283 deletions
+25 -12
View File
@@ -5,7 +5,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical; using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Text.Json; using System.Text.Json;
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
@@ -197,25 +196,39 @@ public static class IHostBuilderExtension
return builder; return builder;
} }
public static IHostBuilder ConfigureMicroControllers(this IHostBuilder hostBuilder, Action<IMicroControllerBuilder> builderDelegate) public static IHostBuilder AddSerial<TConfiguration, TReader, TRead, TEvent>(this IHostBuilder hostBuilder)
where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead>
where TEvent : SerialEventArgs<TRead>, new()
{ {
hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) => hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) =>
{ {
MicroControllerBuilder? builder = new(); serviceCollection.TryAddSingleton<ISerialContextFactory, SerialContextFactory>();
serviceCollection.AddSingleton<ISerialContext<TReader, TRead, TEvent>>(provider => provider.GetRequiredService<ISerialContextFactory>().Create<TConfiguration, TReader, TRead, TEvent>()
builderDelegate.Invoke(builder); ?? throw new NullReferenceException());
serviceCollection.TryAddSingleton<ISerialFactory, SerialFactory>();
serviceCollection.TryAddSingleton<IMicroControllerContextFactory, MicroControllerContextFactory>();
foreach (IMicroControllerBuilderConfiguration configuration in builder.Configurations)
{
serviceCollection.AddSingleton(provider => configuration.Factory.Invoke(provider) ?? throw new NullReferenceException());
}
}); });
return hostBuilder; return hostBuilder;
} }
//public static IHostBuilder ConfigureSerials(this IHostBuilder hostBuilder, Action<ISerialBuilder> builderDelegate)
//{
// hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) =>
// {
// SerialBuilder? builder = new();
// builderDelegate.Invoke(builder);
// serviceCollection.TryAddSingleton<ISerialContextFactory, SerialContextFactory>();
// foreach (ISerialBuilderConfiguration configuration in builder.Configurations)
// {
// serviceCollection.AddSingleton(provider => configuration.Factory.Invoke(provider) ?? throw new NullReferenceException());
// }
// });
// return hostBuilder;
//}
public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder, public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder,
string contentRoot, string contentRoot,
bool createDirectory) bool createDirectory)
@@ -1,9 +0,0 @@
namespace Toolkit.Foundation;
public interface IMicroControllerContextFactory
{
IMicroControllerContext<TRead, TEvent>? Create<TConfiguration, TReader, TRead, TEvent>(IReadOnlyCollection<IMicroControllerModuleDescriptor> modules)
where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead>
where TEvent : ISerialEventArgs<TRead>;
}
@@ -1,11 +0,0 @@
namespace Toolkit.Foundation;
public interface IMicroControllerBuilder
{
IReadOnlyCollection<IMicroControllerBuilderConfiguration> Configurations { get; }
IMicroControllerBuilderConfiguration<TConfiguration, TReader, TRead, TEvent> Add<TConfiguration, TReader, TRead, TEvent>()
where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead>
where TEvent : ISerialEventArgs<TRead>;
}
@@ -1,19 +0,0 @@
namespace Toolkit.Foundation;
public interface IMicroControllerBuilderConfiguration
{
IReadOnlyCollection<IMicroControllerModuleDescriptor> Modules { get; }
Func<IServiceProvider, IMicroControllerContext?> Factory { get; }
}
public interface IMicroControllerBuilderConfiguration<TConfiguration, TReader, TRead, IEvent> :
IMicroControllerBuilderConfiguration
where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead>
where IEvent : ISerialEventArgs<TRead>
{
IMicroControllerBuilderConfiguration<TConfiguration, TReader, TRead, IEvent> AddModule<TModule>()
where TModule : IMicroControllerModule, new();
}
@@ -1,7 +0,0 @@
namespace Toolkit.Foundation;
public interface IMicroControllerContext<TRead, TEvent> :
IMicroControllerContext
where TEvent : ISerialEventArgs<TRead>;
public interface IMicroControllerContext;
@@ -1,3 +0,0 @@
namespace Toolkit.Foundation;
public interface IMicroControllerModule;
@@ -1,13 +0,0 @@
namespace Toolkit.Foundation;
public interface IMicroControllerModuleDescriptor<TModule> :
IMicroControllerModuleDescriptor
where TModule : IMicroControllerModule
{
Func<TModule>? Factory { get; }
}
public interface IMicroControllerModuleDescriptor
{
Type Type { get; }
}
+5 -3
View File
@@ -1,10 +1,12 @@
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public interface ISerialContext<TReader, TRead> : public interface ISerialContext<TReader, TRead, TEvent> :
ISerialContext ISerialContext
where TReader : SerialReader<TRead>; where TReader : SerialReader<TRead>
where TEvent : SerialEventArgs<TRead>, new();
public interface ISerialContext public interface ISerialContext
{ {
void Open(); bool Open();
} }
@@ -0,0 +1,9 @@
namespace Toolkit.Foundation;
public interface ISerialContextFactory
{
ISerialContext<TReader, TRead, TEvent>? Create<TConfiguration, TReader, TRead, TEvent>()
where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead>
where TEvent : SerialEventArgs<TRead>, new();
}
-3
View File
@@ -1,3 +0,0 @@
namespace Toolkit.Foundation;
public interface ISerialEventArgs<TRead>;
-8
View File
@@ -1,8 +0,0 @@
namespace Toolkit.Foundation;
public interface ISerialFactory
{
ISerialContext<TReader, TRead>? Create<TConfiguration, TReader, TRead>()
where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead>;
}
-6
View File
@@ -1,6 +0,0 @@
namespace Toolkit.Foundation;
public interface ISerialResponse
{
ISerialContext Context { get; }
}
@@ -1,35 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
namespace Toolkit.Foundation;
public class MicroControllerContextFactory(IServiceProvider provider,
IServiceFactory factory,
ISerialFactory serialFactory) :
IMicroControllerContextFactory
{
private readonly Dictionary<ISerialConfiguration, IMicroControllerContext> cache = [];
public IMicroControllerContext<TRead, THandler>? Create<TConfiguration, TReader, TRead, THandler>(IReadOnlyCollection<IMicroControllerModuleDescriptor> modules)
where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead>
where THandler : ISerialEventArgs<TRead>
{
if (provider.GetRequiredService<TConfiguration>() is TConfiguration configuration)
{
if (cache.TryGetValue(configuration, out IMicroControllerContext? context))
{
return (IMicroControllerContext<TRead, THandler>)context;
}
if (serialFactory.Create<TConfiguration, TReader, TRead>() is ISerialContext<TReader, TRead> serialContext)
{
context = factory.Create<MicroControllerContext<TRead, THandler>>(modules, serialContext);
cache.Add(configuration, context);
return (IMicroControllerContext<TRead, THandler>)context;
}
}
return default;
}
}
@@ -1,23 +0,0 @@
using System.Collections.ObjectModel;
namespace Toolkit.Foundation;
public class MicroControllerBuilder :
IMicroControllerBuilder
{
private readonly List<IMicroControllerBuilderConfiguration> configurations = [];
public IReadOnlyCollection<IMicroControllerBuilderConfiguration> Configurations =>
new ReadOnlyCollection<IMicroControllerBuilderConfiguration>(configurations);
public IMicroControllerBuilderConfiguration<TConfiguration, TReader, TRead, TEvent> Add<TConfiguration, TReader, TRead, TEvent>()
where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead>
where TEvent : ISerialEventArgs<TRead>
{
MicroControllerBuilderConfiguration<TConfiguration, TReader, TRead, TEvent>? builderConfiguration = new();
configurations.Add(builderConfiguration);
return builderConfiguration;
}
}
@@ -1,25 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using System.Collections.ObjectModel;
namespace Toolkit.Foundation;
public class MicroControllerBuilderConfiguration<TConfiguration, TReader, TRead, TEvent> :
IMicroControllerBuilderConfiguration<TConfiguration, TReader, TRead, TEvent>
where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead>
where TEvent : ISerialEventArgs<TRead>
{
private readonly List<IMicroControllerModuleDescriptor> modules = [];
public Func<IServiceProvider, IMicroControllerContext?> Factory => (IServiceProvider provider) => provider.GetService<IMicroControllerContextFactory>()!
.Create<TConfiguration, TReader, TRead, TEvent>(Modules);
public IReadOnlyCollection<IMicroControllerModuleDescriptor> Modules => new ReadOnlyCollection<IMicroControllerModuleDescriptor>(modules);
public IMicroControllerBuilderConfiguration<TConfiguration, TReader, TRead, TEvent> AddModule<TModule>()
where TModule : IMicroControllerModule, new()
{
modules.Add(new MicroControllerModuleDescriptor<TModule>());
return this;
}
}
@@ -1,12 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace Toolkit.Foundation;
public class MicroControllerConfiguration :
ISerialConfiguration
{
[NotNull]
public string? PortName { get; set; }
public int BaudRate { get; set; }
}
@@ -1,24 +0,0 @@
using CommunityToolkit.Mvvm.Messaging;
namespace Toolkit.Foundation;
public class MicroControllerContext<TRead, TEvent>(IReadOnlyCollection<IMicroControllerModuleDescriptor> modules,
IMessenger messenger,
ISerialContext serialContext) :
IMicroControllerContext<TRead, TEvent>
where TEvent : ISerialEventArgs<TRead>
{
public async Task InitializeAsync()
{
//eventAggregator.Subscribe<SerialResponse<TRead>>(OnEvent, null, args => args.Context.Equals(serialContext));
serialContext.Open();
await Task.CompletedTask;
}
private async void OnEvent(SerialResponse<TRead> args)
{
//IMicrocontrollerModule? module = await messenger.SendAsync<IMicrocontrollerModule>(new TReadDeserializer { Read = args.Content }, modules);
//messenger.Send((dynamic?)module);
}
}
@@ -1,9 +0,0 @@
namespace Toolkit.Foundation;
public record MicroControllerModuleDescriptor<TModule> :
IMicroControllerModuleDescriptor<TModule> where TModule : IMicroControllerModule, new()
{
public Type Type => typeof(TModule);
public Func<TModule>? Factory => new(() => new TModule());
}
+28 -10
View File
@@ -2,24 +2,42 @@
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public class SerialContext<TSerialReader, TContent>(IMessenger messenger, public class SerialContext<TReader, TValue, TEvent>(IMessenger messenger,
ISerialConnection connection, ISerialConnection connection,
ISerialStreamer serialStreamer) : ISerialStreamer serialStreamer) :
ISerialContext<TSerialReader, TContent> where TSerialReader : SerialReader<TContent> ISerialContext<TReader, TValue, TEvent>
where TReader : SerialReader<TValue>
where TEvent : SerialEventArgs<TValue>, new()
{ {
public async void Open() public bool IsOpen { get; private set; }
{
if (connection.Open())
{
Stream stream = serialStreamer.Create();
if ((TSerialReader?)Activator.CreateInstance(typeof(TSerialReader), [stream]) is TSerialReader reader) public bool Open()
{
if (!connection.Open())
return false;
IsOpen = true;
_ = ReadAsync();
return true;
}
private async Task ReadAsync()
{
try
{
await using Stream stream = serialStreamer.Create();
if (Activator.CreateInstance(typeof(TReader), [stream]) is TReader reader)
{ {
await foreach (TContent content in reader.ReadAsync()) await foreach (TValue value in reader.ReadAsync())
{ {
messenger.Send(SerialResponse.Create(this, content)); messenger.Send(new SerialEventArgs<TValue> { Value = value });
} }
} }
} }
catch
{
IsOpen = false;
}
} }
} }
@@ -3,35 +3,35 @@ using System.IO.Ports;
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public class SerialFactory(IServiceProvider provider, public class SerialContextFactory(IServiceProvider provider,
IServiceFactory factory) : IServiceFactory factory) :
ISerialFactory ISerialContextFactory
{ {
private readonly Dictionary<ISerialConfiguration, ISerialContext> cache = []; private readonly Dictionary<ISerialConfiguration, ISerialContext> cache = [];
public ISerialContext<TReader, TRead>? Create<TConfiguration, TReader, TRead>() public ISerialContext<TReader, TValue, TEvent>? Create<TConfiguration, TReader, TValue, TEvent>()
where TConfiguration : ISerialConfiguration where TConfiguration : ISerialConfiguration
where TReader : SerialReader<TRead> where TReader : SerialReader<TValue>
where TEvent : SerialEventArgs<TValue>, new()
{ {
if (provider.GetRequiredService<TConfiguration>() is TConfiguration configuration) if (provider.GetRequiredService<TConfiguration>() is TConfiguration configuration)
{ {
if (cache.TryGetValue(configuration, out ISerialContext? context)) if (cache.TryGetValue(configuration, out ISerialContext? context))
{ {
return (ISerialContext<TReader, TRead>)context; return (ISerialContext<TReader, TValue, TEvent>)context;
} }
SerialPort serialPort = new(configuration.PortName, configuration.BaudRate) SerialPort serialPort = new(configuration.PortName, configuration.BaudRate);
{ serialPort.ReadTimeout = 500; // Prevents blocking if no data is available
DtrEnable = true serialPort.WriteTimeout = 500;
};
SerialConnection connection = new(serialPort); SerialConnection connection = new(serialPort);
SerialStreamer streamer = new(serialPort); SerialStreamer streamer = new(serialPort);
context = factory.Create<SerialContext<TReader, TRead>>(connection, streamer); context = factory.Create<SerialContext<TReader, TValue, TEvent>>(connection, streamer);
cache.Add(configuration, context); cache.Add(configuration, context);
return (ISerialContext<TReader, TRead>)context; return (ISerialContext<TReader, TValue, TEvent>)context;
} }
return default; return default;
+6
View File
@@ -0,0 +1,6 @@
namespace Toolkit.Foundation;
public record SerialEventArgs<TValue>
{
public TValue? Value { get; init; }
}
+3
View File
@@ -0,0 +1,3 @@
namespace Toolkit.Foundation;
public record SerialEventArgs(byte Type, short Value);
+61
View File
@@ -0,0 +1,61 @@
using System.Buffers;
using System.IO.Pipelines;
namespace Toolkit.Foundation;
public class SerialEventReader(Stream stream) :
SerialReader<SerialEventArgs>(stream)
{
private readonly PipeReader reader = PipeReader.Create(stream);
public override async IAsyncEnumerable<SerialEventArgs> ReadAsync()
{
while (true)
{
ReadResult result;
try
{
result = await reader.ReadAsync();
}
catch (Exception)
{
continue;
}
ReadOnlySequence<byte> buffer = result.Buffer;
while (TryParseEvent(ref buffer, out SerialEventArgs serialEvent))
{
yield return serialEvent;
}
reader.AdvanceTo(buffer.Start, buffer.End);
if (result.IsCompleted)
break;
}
await reader.CompleteAsync();
}
private bool TryParseEvent(ref ReadOnlySequence<byte> buffer,
out SerialEventArgs serialEvent)
{
SequenceReader<byte> reader = new(buffer);
serialEvent = default!;
if (reader.Remaining < 3)
return false;
if (!reader.TryRead(out byte type))
return false;
if (!reader.TryReadLittleEndian(out short value))
return false;
serialEvent = new SerialEventArgs(type, value);
buffer = buffer.Slice(reader.Position);
return true;
}
}
+17 -10
View File
@@ -4,7 +4,7 @@ using System.Text;
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public class SerialLineReader(Stream stream) : public class SerialLineReader(Stream stream) :
SerialReader<string>(stream) SerialReader<string>(stream)
{ {
private readonly PipeReader reader = PipeReader.Create(stream); private readonly PipeReader reader = PipeReader.Create(stream);
@@ -13,24 +13,31 @@ public class SerialLineReader(Stream stream) :
{ {
while (true) while (true)
{ {
ReadResult result = await reader.ReadAsync(); ReadResult result;
ReadOnlySequence<byte> buffer = result.Buffer; try
{
result = await reader.ReadAsync();
}
catch
{
continue;
}
ReadOnlySequence<byte> buffer = result.Buffer;
while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line)) while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line))
{ {
yield return EncodingExtensions.GetString(Encoding.UTF8, line); yield return EncodingExtensions.GetString(Encoding.UTF8, line);
} }
reader.AdvanceTo(buffer.Start, buffer.End); reader.AdvanceTo(buffer.Start, buffer.End);
if (result.IsCompleted)
{
break;
}
}
if (result.IsCompleted)
break;
}
} }
private bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line) private bool TryReadLine(ref ReadOnlySequence<byte> buffer,
out ReadOnlySequence<byte> line)
{ {
SequencePosition? position = buffer.PositionOf((byte)'\n'); SequencePosition? position = buffer.PositionOf((byte)'\n');
if (position == null) if (position == null)
@@ -43,4 +50,4 @@ public class SerialLineReader(Stream stream) :
buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
return true; return true;
} }
} }
+2 -2
View File
@@ -1,8 +1,8 @@
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public abstract class SerialReader<TRead>(Stream stream) public abstract class SerialReader<TValue>(Stream stream)
{ {
public Stream Stream { get; } = stream; public Stream Stream { get; } = stream;
public abstract IAsyncEnumerable<TRead> ReadAsync(); public abstract IAsyncEnumerable<TValue> ReadAsync();
} }
-24
View File
@@ -1,24 +0,0 @@
namespace Toolkit.Foundation;
public record SerialResponse<TContent> :
ISerialResponse
{
public SerialResponse(ISerialContext context,
TContent content)
{
Context = context;
Content = content;
}
public ISerialContext Context { get; }
public TContent Content { get; }
}
public record SerialResponse
{
public static SerialResponse<TContent> Create<TContent>(ISerialContext context, TContent content)
{
return new SerialResponse<TContent>(context, content);
}
}
+1 -4
View File
@@ -5,8 +5,5 @@ namespace Toolkit.Foundation;
public class SerialStreamer(SerialPort serialPort) : public class SerialStreamer(SerialPort serialPort) :
ISerialStreamer ISerialStreamer
{ {
public Stream Create() public Stream Create() => serialPort.BaseStream;
{
return serialPort.BaseStream;
}
} }