diff --git a/Toolkit.Foundation/SerialContext.cs b/Toolkit.Foundation/SerialContext.cs index ce67c41..0e3de7e 100644 --- a/Toolkit.Foundation/SerialContext.cs +++ b/Toolkit.Foundation/SerialContext.cs @@ -18,7 +18,7 @@ public class SerialContext(IMessenger messenger, IsOpen = true; - _ = ReadAsync(); + _ = Task.Run(ReadAsync); return true; } diff --git a/Toolkit.Foundation/SerialContextFactory.cs b/Toolkit.Foundation/SerialContextFactory.cs index bf6c966..b4a2bb6 100644 --- a/Toolkit.Foundation/SerialContextFactory.cs +++ b/Toolkit.Foundation/SerialContextFactory.cs @@ -21,9 +21,13 @@ public class SerialContextFactory(IServiceProvider provider, return (ISerialContext)context; } - SerialPort serialPort = new(configuration.PortName, configuration.BaudRate); - serialPort.ReadTimeout = 500; // Prevents blocking if no data is available - serialPort.WriteTimeout = 500; + SerialPort serialPort = new(configuration.PortName, configuration.BaudRate) + { + ReadTimeout = SerialPort.InfiniteTimeout, + WriteTimeout = SerialPort.InfiniteTimeout, + DtrEnable = false, + RtsEnable = false + }; SerialConnection connection = new(serialPort); SerialStreamer streamer = new(serialPort); diff --git a/Toolkit.Foundation/SerialEventArgs1.cs b/Toolkit.Foundation/SerialEventArgs1.cs deleted file mode 100644 index 2dd65af..0000000 --- a/Toolkit.Foundation/SerialEventArgs1.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Toolkit.Foundation; - -public record SerialEventArgs(byte Type, short Value); diff --git a/Toolkit.Foundation/SerialEventReader.cs b/Toolkit.Foundation/SerialEventReader.cs deleted file mode 100644 index 55a609f..0000000 --- a/Toolkit.Foundation/SerialEventReader.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Buffers; -using System.IO.Pipelines; - -namespace Toolkit.Foundation; - -public class SerialEventReader(Stream stream) : - SerialReader(stream) -{ - private readonly PipeReader reader = PipeReader.Create(stream); - - public override async IAsyncEnumerable ReadAsync() - { - while (true) - { - ReadResult result; - try - { - result = await reader.ReadAsync(); - } - catch (Exception) - { - continue; - } - - ReadOnlySequence 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 buffer, - out SerialEventArgs serialEvent) - { - SequenceReader 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; - } -} diff --git a/Toolkit.Foundation/SerialPortStream.cs b/Toolkit.Foundation/SerialPortStream.cs new file mode 100644 index 0000000..25ce061 --- /dev/null +++ b/Toolkit.Foundation/SerialPortStream.cs @@ -0,0 +1,72 @@ +using System.IO.Ports; + +namespace Toolkit.Foundation; + +public class SerialPortStream : + Stream +{ + private readonly SerialPort serialPort; + + public SerialPortStream(SerialPort serialPort) + { + this.serialPort = serialPort ?? throw new ArgumentNullException(nameof(serialPort)); + if (!this.serialPort.IsOpen) this.serialPort.Open(); + } + + public override bool CanRead => serialPort.IsOpen && serialPort.BaseStream.CanRead; + + public override bool CanSeek => false; + + public override bool CanWrite => serialPort.IsOpen && serialPort.BaseStream.CanWrite; + + public bool HasData => serialPort.BytesToRead > 0; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public override void Flush() => serialPort.BaseStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => + HasData ? serialPort.BaseStream.Read(buffer, offset, count) : 0; + + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + await WaitForDataAsync(cancellationToken); + return await serialPort.BaseStream.ReadAsync(buffer, cancellationToken); + } + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + await WaitForDataAsync(cancellationToken); + return await serialPort.BaseStream.ReadAsync(buffer.AsMemory(offset, count), cancellationToken); + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => + serialPort.BaseStream.Write(buffer, offset, count); + + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => + await serialPort.BaseStream.WriteAsync(buffer, cancellationToken); + + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + await serialPort.BaseStream.WriteAsync(buffer.AsMemory(offset, count), cancellationToken); + + protected override void Dispose(bool disposing) + { + if (disposing) serialPort?.Dispose(); + base.Dispose(disposing); + } + + private async Task WaitForDataAsync(CancellationToken cancellationToken) + { + while (!HasData) + { + await Task.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + } + } +} diff --git a/Toolkit.Foundation/SerialStreamer.cs b/Toolkit.Foundation/SerialStreamer.cs index 40e183c..2a282c5 100644 --- a/Toolkit.Foundation/SerialStreamer.cs +++ b/Toolkit.Foundation/SerialStreamer.cs @@ -5,5 +5,5 @@ namespace Toolkit.Foundation; public class SerialStreamer(SerialPort serialPort) : ISerialStreamer { - public Stream Create() => serialPort.BaseStream; + public Stream Create() => new SerialPortStream(serialPort); } diff --git a/Toolkit.Foundation/SerialStructEventArgs.cs b/Toolkit.Foundation/SerialStructEventArgs.cs new file mode 100644 index 0000000..7585344 --- /dev/null +++ b/Toolkit.Foundation/SerialStructEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record SerialStructEventArgs(byte Type, short Value); diff --git a/Toolkit.Foundation/SerialStructReader.cs b/Toolkit.Foundation/SerialStructReader.cs new file mode 100644 index 0000000..ab5e786 --- /dev/null +++ b/Toolkit.Foundation/SerialStructReader.cs @@ -0,0 +1,66 @@ +using System.Buffers; +using System.IO.Pipelines; + +namespace Toolkit.Foundation; + +public class SerialStructReader(Stream stream) : + SerialReader(stream) +{ + private readonly PipeReader reader = PipeReader.Create(stream); + + public override async IAsyncEnumerable ReadAsync() + { + while (true) + { + ReadResult? result = default; + + try + { + result = await reader.ReadAsync(); + } + catch (IOException) + { + continue; + } + catch (OperationCanceledException) + { + yield break; + } + catch (Exception) + { + yield break; + } + + if (result.HasValue) + { + ReadOnlySequence buffer = result.Value.Buffer; + while (TryParse(ref buffer, out SerialStructEventArgs serialEvent)) + { + yield return serialEvent; + } + + reader.AdvanceTo(buffer.Start, buffer.End); + } + + } + } + + private bool TryParse(ref ReadOnlySequence buffer, out SerialStructEventArgs serialEvent) + { + SequenceReader 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 SerialStructEventArgs(type, value); + buffer = buffer.Slice(reader.Position); + return true; + } +}