diff --git a/src/FluentModbus/Client/ModbusClient.cs b/src/FluentModbus/Client/ModbusClient.cs index 48cdbf6..f7120c0 100755 --- a/src/FluentModbus/Client/ModbusClient.cs +++ b/src/FluentModbus/Client/ModbusClient.cs @@ -27,7 +27,7 @@ public abstract partial class ModbusClient /// The unit identifier. /// The function code. /// An action to be called to extend the prepared Modbus frame with function code specific data. - protected abstract Span TransceiveFrame(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame); + protected abstract Span TransceiveFrame(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame); internal void ProcessError(ModbusFunctionCode functionCode, ModbusExceptionCode exceptionCode) { @@ -214,7 +214,7 @@ public void WriteMultipleRegisters(byte unitIdentifier, ushort startingAddress, writer.Write((byte)(quantity * 2)); // 12 Byte Count = Quantity of Registers * 2 - writer.Write(dataset, 0, dataset.Length); + writer.Write(dataset); }); } @@ -422,7 +422,10 @@ public void WriteSingleRegister(byte unitIdentifier, ushort registerAddress, byt else writer.Write(registerAddress); // 08-09 Starting Address - writer.Write(value); // 10-11 Value + foreach (var b in value) + { + writer.Write(b); + } }); } @@ -463,7 +466,10 @@ public void WriteMultipleCoils(int unitIdentifier, int startingAddress, bool[] v writer.Write((byte)byteCount); // 12 Byte Count = Outputs - writer.Write(convertedData); + foreach (var b in convertedData) + { + writer.Write(b); + } }); } @@ -562,7 +568,10 @@ public Span ReadWriteMultipleRegisters(byte unitIdentifier, ushort readSta writer.Write((byte)(writeQuantity * 2)); // 16 Byte Count = Quantity to Write * 2 - writer.Write(dataset, 0, dataset.Length); + foreach (var b in dataset) + { + writer.Write(b); + } }); if (buffer.Length < readQuantity * 2 + 2) diff --git a/src/FluentModbus/Client/ModbusClientAsync.cs b/src/FluentModbus/Client/ModbusClientAsync.cs index 790ec66..a0716a7 100755 --- a/src/FluentModbus/Client/ModbusClientAsync.cs +++ b/src/FluentModbus/Client/ModbusClientAsync.cs @@ -17,7 +17,7 @@ public abstract partial class ModbusClient /// The function code. /// An action to be called to extend the prepared Modbus frame with function code specific data. /// The token to monitor for cancellation requests. The default value is . - protected abstract Task> TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame, CancellationToken cancellationToken = default); + protected abstract Task> TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame, CancellationToken cancellationToken = default); /// /// Reads the specified number of values of type from the holding registers. @@ -33,8 +33,8 @@ public async Task> ReadHoldingRegistersAsync(int unitIdentifier, in var startingAddress_converted = ConvertUshort(startingAddress); var count_converted = ConvertUshort(count); - var dataset = SpanExtensions.Cast(await - ReadHoldingRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted), cancellationToken).ConfigureAwait(false)); + var dataset = MemoryMarshal.Cast((await + ReadHoldingRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted), cancellationToken).ConfigureAwait(false)).Span); if (SwapBytes) ModbusUtils.SwitchEndianness(dataset); @@ -125,7 +125,7 @@ await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.WriteMultipleRegis writer.Write((byte)(quantity * 2)); // 12 Byte Count = Quantity of Registers * 2 - writer.Write(dataset, 0, dataset.Length); + writer.Write(dataset); }, cancellationToken).ConfigureAwait(false); } @@ -213,8 +213,8 @@ public async Task> ReadInputRegistersAsync(int unitIdentifier, int var startingAddress_converted = ConvertUshort(startingAddress); var count_converted = ConvertUshort(count); - var dataset = SpanExtensions.Cast(await - ReadInputRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted), cancellationToken).ConfigureAwait(false)); + var dataset = MemoryMarshal.Cast((await + ReadInputRegistersAsync(unitIdentifier_converted, startingAddress_converted, ConvertSize(count_converted), cancellationToken).ConfigureAwait(false)).Span); if (SwapBytes) ModbusUtils.SwitchEndianness(dataset); @@ -339,7 +339,7 @@ await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.WriteSingleRegiste else writer.Write(registerAddress); // 08-09 Starting Address - writer.Write(value); // 10-11 Value + writer.Write(value); }, cancellationToken).ConfigureAwait(false); } @@ -350,7 +350,7 @@ await TransceiveFrameAsync(unitIdentifier, ModbusFunctionCode.WriteSingleRegiste /// The coil register start address for the write operation. /// The values to write to the server. /// The token to monitor for cancellation requests. The default value is . - public void WriteMultipleCoilsAsync(int unitIdentifier, int startingAddress, bool[] values, CancellationToken cancellationToken = default) + public async Task WriteMultipleCoilsAsync(int unitIdentifier, int startingAddress, bool[] values, CancellationToken cancellationToken = default) { var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier); var startingAddress_converted = ConvertUshort(startingAddress); @@ -361,7 +361,7 @@ public void WriteMultipleCoilsAsync(int unitIdentifier, int startingAddress, boo new BitArray(values) .CopyTo(convertedData, 0); - TransceiveFrameAsync(unitIdentifier_converted, ModbusFunctionCode.WriteMultipleCoils, writer => + await TransceiveFrameAsync(unitIdentifier_converted, ModbusFunctionCode.WriteMultipleCoils, writer => { writer.Write((byte)ModbusFunctionCode.WriteMultipleCoils); // 07 Function Code @@ -380,7 +380,7 @@ public void WriteMultipleCoilsAsync(int unitIdentifier, int startingAddress, boo writer.Write((byte)byteCount); // 12 Byte Count = Outputs writer.Write(convertedData); - }, cancellationToken); + }, cancellationToken).ConfigureAwait(false); } /// @@ -435,7 +435,7 @@ public async Task> ReadWriteMultipleRegistersAsync( var readQuantity = ConvertSize(readCount_converted); var byteData = MemoryMarshal.Cast(dataset).ToArray(); - var dataset2 = SpanExtensions.Cast(await ReadWriteMultipleRegistersAsync(unitIdentifier_converted, readStartingAddress_converted, readQuantity, writeStartingAddress_converted, byteData, cancellationToken).ConfigureAwait(false)); + var dataset2 = MemoryMarshal.Cast((await ReadWriteMultipleRegistersAsync(unitIdentifier_converted, readStartingAddress_converted, readQuantity, writeStartingAddress_converted, byteData, cancellationToken).ConfigureAwait(false)).Span); if (SwapBytes) ModbusUtils.SwitchEndianness(dataset2); @@ -480,7 +480,7 @@ public async Task> ReadWriteMultipleRegistersAsync(byte unitIdentif writer.Write((byte)(writeQuantity * 2)); // 16 Byte Count = Quantity to Write * 2 - writer.Write(dataset, 0, dataset.Length); + writer.Write(dataset); }, cancellationToken).ConfigureAwait(false); if (buffer.Length < readQuantity * 2 + 2) diff --git a/src/FluentModbus/Client/ModbusRtuClient.cs b/src/FluentModbus/Client/ModbusRtuClient.cs index c6f46d2..0f92f37 100755 --- a/src/FluentModbus/Client/ModbusRtuClient.cs +++ b/src/FluentModbus/Client/ModbusRtuClient.cs @@ -145,7 +145,7 @@ public void Close() } /// - protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame) + protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame) { // WARNING: IF YOU EDIT THIS METHOD, REFLECT ALL CHANGES ALSO IN TransceiveFrameAsync! @@ -174,32 +174,31 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio } } - _frameBuffer.Writer.Seek(0, SeekOrigin.Begin); - _frameBuffer.Writer.Write(unitIdentifier); // 00 Unit Identifier - extendFrame(_frameBuffer.Writer); - frameLength = (int)_frameBuffer.Writer.BaseStream.Position; + var writer = new SpanWriter(_frameBuffer.Buffer.Span); + writer.Write(unitIdentifier); // 00 Unit Identifier + extendFrame(writer); + frameLength = writer.Position; // add CRC - crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.AsMemory()[..frameLength]); - _frameBuffer.Writer.Write(crc); - frameLength = (int)_frameBuffer.Writer.BaseStream.Position; + crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.Slice(0, frameLength)); + writer.Write(crc); + frameLength = writer.Position; // send request - _serialPort!.Value.Value.Write(_frameBuffer.Buffer, 0, frameLength); + _serialPort!.Value.Value.Write(_frameBuffer.Buffer.Slice(0, frameLength)); // special case: broadcast (only for write commands) if (unitIdentifier == 0) - return _frameBuffer.Buffer.AsSpan(0, 0); + return _frameBuffer.Buffer.Span.Slice(0, 0); // wait for and process response frameLength = 0; - _frameBuffer.Reader.BaseStream.Seek(0, SeekOrigin.Begin); while (true) { - frameLength += _serialPort!.Value.Value.Read(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength); + frameLength += _serialPort!.Value.Value.Read(_frameBuffer.Buffer.Slice(frameLength)); - if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory()[..frameLength])) + if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.Slice(0, frameLength))) { break; } @@ -213,16 +212,17 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio } } - _ = _frameBuffer.Reader.ReadByte(); - rawFunctionCode = _frameBuffer.Reader.ReadByte(); + var reader = new SpanReader(_frameBuffer.Buffer.Span); + _ = reader.ReadByte(); + rawFunctionCode = reader.ReadByte(); if (rawFunctionCode == (byte)ModbusFunctionCode.Error + (byte)functionCode) - ProcessError(functionCode, (ModbusExceptionCode)_frameBuffer.Buffer[2]); + ProcessError(functionCode, (ModbusExceptionCode)_frameBuffer.Buffer.Span[2]); else if (rawFunctionCode != (byte)functionCode) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseFunctionCode); - return _frameBuffer.Buffer.AsSpan(1, frameLength - 3); + return _frameBuffer.Buffer.Span.Slice(1, frameLength - 3); } #endregion diff --git a/src/FluentModbus/Client/ModbusRtuClientAsync.cs b/src/FluentModbus/Client/ModbusRtuClientAsync.cs index c376418..cf44937 100755 --- a/src/FluentModbus/Client/ModbusRtuClientAsync.cs +++ b/src/FluentModbus/Client/ModbusRtuClientAsync.cs @@ -6,7 +6,7 @@ namespace FluentModbus public partial class ModbusRtuClient { /// - protected override async Task> TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame, CancellationToken cancellationToken = default) + protected override async Task> TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame, CancellationToken cancellationToken = default) { // WARNING: IF YOU EDIT THIS METHOD, REFLECT ALL CHANGES ALSO IN TransceiveFrameAsync! @@ -35,32 +35,31 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti } } - _frameBuffer.Writer.Seek(0, SeekOrigin.Begin); - _frameBuffer.Writer.Write(unitIdentifier); // 00 Unit Identifier - extendFrame(_frameBuffer.Writer); - frameLength = (int)_frameBuffer.Writer.BaseStream.Position; + var writer = new SpanWriter(_frameBuffer.Buffer.Span); + writer.Write(unitIdentifier); // 00 Unit Identifier + extendFrame(writer); + frameLength = writer.Position; // add CRC - crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.AsMemory()[..frameLength]); - _frameBuffer.Writer.Write(crc); - frameLength = (int)_frameBuffer.Writer.BaseStream.Position; + crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.Slice(0, frameLength)); + writer.Write(crc); + frameLength = writer.Position; // send request - await _serialPort!.Value.Value.WriteAsync(_frameBuffer.Buffer, 0, frameLength, cancellationToken).ConfigureAwait(false); + await _serialPort!.Value.Value.WriteAsync(_frameBuffer.Buffer.Slice(0, frameLength), cancellationToken).ConfigureAwait(false); // special case: broadcast (only for write commands) if (unitIdentifier == 0) - return _frameBuffer.Buffer.AsMemory(0, 0); + return _frameBuffer.Buffer.Slice(0, 0); // wait for and process response frameLength = 0; - _frameBuffer.Reader.BaseStream.Seek(0, SeekOrigin.Begin); while (true) { - frameLength += await _serialPort!.Value.Value.ReadAsync(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength, cancellationToken).ConfigureAwait(false); + frameLength += await _serialPort!.Value.Value.ReadAsync(_frameBuffer.Buffer.Slice(frameLength), cancellationToken).ConfigureAwait(false); - if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory()[..frameLength])) + if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.Slice(0, frameLength))) { break; } @@ -74,16 +73,17 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti } } - _ = _frameBuffer.Reader.ReadByte(); - rawFunctionCode = _frameBuffer.Reader.ReadByte(); + var reader = new SpanReader(_frameBuffer.Buffer.Span); + _ = reader.ReadByte(); + rawFunctionCode = reader.ReadByte(); if (rawFunctionCode == (byte)ModbusFunctionCode.Error + (byte)functionCode) - ProcessError(functionCode, (ModbusExceptionCode)_frameBuffer.Buffer[2]); + ProcessError(functionCode, (ModbusExceptionCode)_frameBuffer.Buffer.Span[2]); else if (rawFunctionCode != (byte)functionCode) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseFunctionCode); - return _frameBuffer.Buffer.AsMemory(1, frameLength - 3); + return _frameBuffer.Buffer.Slice(1, frameLength - 3); } } } \ No newline at end of file diff --git a/src/FluentModbus/Client/ModbusTcpClient.cs b/src/FluentModbus/Client/ModbusTcpClient.cs index a103a54..9239c31 100755 --- a/src/FluentModbus/Client/ModbusTcpClient.cs +++ b/src/FluentModbus/Client/ModbusTcpClient.cs @@ -202,7 +202,7 @@ public void Disconnect() } /// - protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame) + protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame) { // WARNING: IF YOU EDIT THIS METHOD, REFLECT ALL CHANGES ALSO IN TransceiveFrameAsync! @@ -216,21 +216,15 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio bool isParsed; - ModbusFrameBuffer frameBuffer; - ExtendedBinaryWriter writer; - ExtendedBinaryReader reader; - bytesFollowing = 0; - frameBuffer = _frameBuffer; - writer = _frameBuffer.Writer; - reader = _frameBuffer.Reader; // build request - writer.Seek(7, SeekOrigin.Begin); + var writer = new SpanWriter(_frameBuffer.Buffer.Span); + writer.Seek(7); extendFrame(writer); - frameLength = (int)writer.BaseStream.Position; + frameLength = writer.Position; - writer.Seek(0, SeekOrigin.Begin); + writer.Seek(0); if (BitConverter.IsLittleEndian) { @@ -248,12 +242,11 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio writer.Write(unitIdentifier); // 06 Unit Identifier // send request - _networkStream.Write(frameBuffer.Buffer, 0, frameLength); + _networkStream.Write(_frameBuffer.Buffer.Span.Slice(0, frameLength)); // wait for and process response frameLength = 0; isParsed = false; - reader.BaseStream.Seek(0, SeekOrigin.Begin); while (true) { @@ -266,7 +259,7 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio // ASYNC-ONLY: { // ASYNC-ONLY: try // ASYNC-ONLY: { - partialLength = _networkStream.Read(frameBuffer.Buffer, frameLength, frameBuffer.Buffer.Length - frameLength); + partialLength = _networkStream.Read(_frameBuffer.Buffer.Span.Slice(frameLength)); // ASYNC-ONLY: } // ASYNC-ONLY: catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) // ASYNC-ONLY: { @@ -300,6 +293,8 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio { if (!isParsed) // read MBAP header only once { + var reader = new SpanReader(_frameBuffer.Buffer.Span); + // read MBAP header _ = reader.ReadUInt16Reverse(); // 00-01 Transaction Identifier protocolIdentifier = reader.ReadUInt16Reverse(); // 02-03 Protocol Identifier @@ -318,15 +313,17 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio } } - rawFunctionCode = reader.ReadByte(); + var finalReader = new SpanReader(_frameBuffer.Buffer.Span); + finalReader.Seek(7); + rawFunctionCode = finalReader.ReadByte(); if (rawFunctionCode == (byte)ModbusFunctionCode.Error + (byte)functionCode) - ProcessError(functionCode, (ModbusExceptionCode)frameBuffer.Buffer[8]); + ProcessError(functionCode, (ModbusExceptionCode)_frameBuffer.Buffer.Span[8]); else if (rawFunctionCode != (byte)functionCode) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseFunctionCode); - return frameBuffer.Buffer.AsSpan(7, frameLength - 7); + return _frameBuffer.Buffer.Span.Slice(7, frameLength - 7); } private ushort GetTransactionIdentifier() diff --git a/src/FluentModbus/Client/ModbusTcpClientAsync.cs b/src/FluentModbus/Client/ModbusTcpClientAsync.cs index bb17428..e6de14e 100755 --- a/src/FluentModbus/Client/ModbusTcpClientAsync.cs +++ b/src/FluentModbus/Client/ModbusTcpClientAsync.cs @@ -6,7 +6,7 @@ namespace FluentModbus public partial class ModbusTcpClient { /// - protected override async Task> TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame, CancellationToken cancellationToken = default) + protected override async Task> TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action extendFrame, CancellationToken cancellationToken = default) { // WARNING: IF YOU EDIT THIS METHOD, REFLECT ALL CHANGES ALSO IN TransceiveFrameAsync! @@ -20,44 +20,37 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti bool isParsed; - ModbusFrameBuffer frameBuffer; - ExtendedBinaryWriter writer; - ExtendedBinaryReader reader; - bytesFollowing = 0; - frameBuffer = _frameBuffer; - writer = _frameBuffer.Writer; - reader = _frameBuffer.Reader; // build request - writer.Seek(7, SeekOrigin.Begin); + var writer = new SpanWriter(_frameBuffer.Buffer.Span); + writer.Seek(7); extendFrame(writer); - frameLength = (int)writer.BaseStream.Position; + frameLength = writer.Position; - writer.Seek(0, SeekOrigin.Begin); + writer.Seek(0); if (BitConverter.IsLittleEndian) { - writer.WriteReverse(GetTransactionIdentifier()); // 00-01 Transaction Identifier - writer.WriteReverse((ushort)0); // 02-03 Protocol Identifier - writer.WriteReverse((ushort)(frameLength - 6)); // 04-05 Length + writer.WriteReverse(GetTransactionIdentifier()); // 00-01 Transaction Identifier + writer.WriteReverse((ushort)0); // 02-03 Protocol Identifier + writer.WriteReverse((ushort)(frameLength - 6)); // 04-05 Length } else { - writer.Write(GetTransactionIdentifier()); // 00-01 Transaction Identifier - writer.Write((ushort)0); // 02-03 Protocol Identifier - writer.Write((ushort)(frameLength - 6)); // 04-05 Length + writer.Write(GetTransactionIdentifier()); // 00-01 Transaction Identifier + writer.Write((ushort)0); // 02-03 Protocol Identifier + writer.Write((ushort)(frameLength - 6)); // 04-05 Length } - writer.Write(unitIdentifier); // 06 Unit Identifier + writer.Write(unitIdentifier); // 06 Unit Identifier // send request - await _networkStream.WriteAsync(frameBuffer.Buffer, 0, frameLength, cancellationToken).ConfigureAwait(false); + await _networkStream.WriteAsync(_frameBuffer.Buffer.Slice(0, frameLength), cancellationToken).ConfigureAwait(false); // wait for and process response frameLength = 0; isParsed = false; - reader.BaseStream.Seek(0, SeekOrigin.Begin); while (true) { @@ -70,7 +63,7 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti { try { - partialLength = await _networkStream.ReadAsync(frameBuffer.Buffer, frameLength, frameBuffer.Buffer.Length - frameLength, cancellationToken).ConfigureAwait(false); + partialLength = await _networkStream.ReadAsync(_frameBuffer.Buffer.Slice(frameLength), cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { @@ -104,6 +97,8 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti { if (!isParsed) // read MBAP header only once { + var reader = new SpanReader(_frameBuffer.Buffer.Span); + // read MBAP header _ = reader.ReadUInt16Reverse(); // 00-01 Transaction Identifier protocolIdentifier = reader.ReadUInt16Reverse(); // 02-03 Protocol Identifier @@ -122,15 +117,17 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti } } - rawFunctionCode = reader.ReadByte(); + var finalReader = new SpanReader(_frameBuffer.Buffer.Span); + finalReader.Seek(7); + rawFunctionCode = finalReader.ReadByte(); if (rawFunctionCode == (byte)ModbusFunctionCode.Error + (byte)functionCode) - ProcessError(functionCode, (ModbusExceptionCode)frameBuffer.Buffer[8]); + ProcessError(functionCode, (ModbusExceptionCode)_frameBuffer.Buffer.Span[8]); else if (rawFunctionCode != (byte)functionCode) throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseFunctionCode); - return frameBuffer.Buffer.AsMemory(7, frameLength - 7); + return _frameBuffer.Buffer.Slice(7, frameLength - 7); } } } \ No newline at end of file diff --git a/src/FluentModbus/IModbusRtuSerialPort.cs b/src/FluentModbus/IModbusRtuSerialPort.cs index 5871fd7..f2f9180 100644 --- a/src/FluentModbus/IModbusRtuSerialPort.cs +++ b/src/FluentModbus/IModbusRtuSerialPort.cs @@ -16,6 +16,13 @@ public interface IModbusRtuSerialPort /// The number of bytes read. int Read(byte[] buffer, int offset, int count); + /// + /// Reads from the serial port input buffer and writes the bytes into a memory block. + /// + /// The memory block to write the input to. + /// The number of bytes read. + int Read(Memory buffer); + /// /// Asynchronously reads a number of bytes from the serial port input buffer and writes those bytes into a byte array at the specified offset. /// @@ -26,6 +33,14 @@ public interface IModbusRtuSerialPort /// The number of bytes read. Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken token); + /// + /// Asynchronously reads from the serial port input buffer and writes the bytes into a memory block. + /// + /// The memory block to write the input to. + /// A token to cancel the current operation. + /// The number of bytes read. + ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken); + /// /// Writes a specified number of bytes to the serial port using data from a buffer. /// @@ -34,6 +49,12 @@ public interface IModbusRtuSerialPort /// The number of bytes to write. void Write(byte[] buffer, int offset, int count); + /// + /// Writes the provided data to the serial port. + /// + /// The data to write to the port. + void Write(ReadOnlyMemory buffer); + /// /// Asynchronously writes a specified number of bytes to the serial port using data from a buffer. /// @@ -43,6 +64,14 @@ public interface IModbusRtuSerialPort /// A token to cancel the current operation. Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token); + /// + // + /// Asynchronously writes the provided data to the serial port. + /// + /// The data to write to the port. + /// A token to cancel the current operation. + ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken); + /// /// Opens a new serial port connection. /// diff --git a/src/FluentModbus/ModbusFrameBuffer.cs b/src/FluentModbus/ModbusFrameBuffer.cs index c19d605..bc6bee7 100644 --- a/src/FluentModbus/ModbusFrameBuffer.cs +++ b/src/FluentModbus/ModbusFrameBuffer.cs @@ -4,30 +4,15 @@ namespace FluentModbus; internal class ModbusFrameBuffer : IDisposable { - #region Constructors + private IMemoryOwner _memoryOwner; + private bool _disposedValue = false; public ModbusFrameBuffer(int size) { - Buffer = ArrayPool.Shared.Rent(size); - - Writer = new ExtendedBinaryWriter(new MemoryStream(Buffer)); - Reader = new ExtendedBinaryReader(new MemoryStream(Buffer)); + _memoryOwner = MemoryPool.Shared.Rent(size); } - #endregion - - #region Properties - - public byte[] Buffer { get; } - - public ExtendedBinaryWriter Writer { get; } - public ExtendedBinaryReader Reader { get; } - - #endregion - - #region IDisposable Support - - private bool _disposedValue = false; + public Memory Buffer => _memoryOwner.Memory; protected virtual void Dispose(bool disposing) { @@ -35,10 +20,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - Writer.Dispose(); - Reader.Dispose(); - - ArrayPool.Shared.Return(Buffer); + _memoryOwner.Dispose(); } _disposedValue = true; @@ -48,7 +30,6 @@ protected virtual void Dispose(bool disposing) public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } - - #endregion } \ No newline at end of file diff --git a/src/FluentModbus/ModbusRtuSerialPort.cs b/src/FluentModbus/ModbusRtuSerialPort.cs index 3e9ff0f..902920e 100644 --- a/src/FluentModbus/ModbusRtuSerialPort.cs +++ b/src/FluentModbus/ModbusRtuSerialPort.cs @@ -70,6 +70,22 @@ public int Read(byte[] buffer, int offset, int count) return _serialPort.Read(buffer, offset, count); } + /// + public int Read(Memory buffer) + { + if (MemoryMarshal.TryGetArray(buffer, out var segment)) + { + return Read(segment.Array, segment.Offset, segment.Count); + } + else + { + var array = buffer.ToArray(); + var result = Read(array, 0, array.Length); + array.CopyTo(buffer); + return result; + } + } + /// /// Asynchronously reads from the input buffer. /// @@ -113,6 +129,38 @@ public async Task ReadAsync(byte[] buffer, int offset, int count, Cancellat } } + /// + public async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) + { + using var timeoutCts = new CancellationTokenSource(_serialPort.ReadTimeout); + + /* _serialPort.DiscardInBuffer is essential here to cancel the operation */ + using (timeoutCts.Token.Register(() => + { + if (IsOpen) + _serialPort.DiscardInBuffer(); + })) + using (cancellationToken.Register(() => timeoutCts.Cancel())) + { + try + { + return await _serialPort.BaseStream.ReadAsync(buffer, timeoutCts.Token); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested) + { + throw new TimeoutException("The asynchronous read operation timed out."); + } + catch (IOException) when (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + throw new TimeoutException("The asynchronous read operation timed out."); + } + } + } + /// /// Writes data to the serial port output buffer. /// @@ -124,6 +172,20 @@ public void Write(byte[] buffer, int offset, int count) _serialPort.Write(buffer, offset, count); } + /// + public void Write(ReadOnlyMemory buffer) + { + if (MemoryMarshal.TryGetArray(buffer, out var segment)) + { + Write(segment.Array, segment.Offset, segment.Count); + } + else + { + var array = buffer.ToArray(); + Write(array, 0, array.Length); + } + } + /// /// Asynchronously writes data to the serial port output buffer. /// @@ -161,5 +223,33 @@ public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationT } } + /// + public async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + using var timeoutCts = new CancellationTokenSource(_serialPort.WriteTimeout); + + /* _serialPort.DiscardInBuffer is essential here to cancel the operation */ + using (timeoutCts.Token.Register(() => _serialPort.DiscardOutBuffer())) + using (cancellationToken.Register(() => timeoutCts.Cancel())) + { + try + { + await _serialPort.BaseStream.WriteAsync(buffer, timeoutCts.Token); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested) + { + throw new TimeoutException("The asynchronous write operation timed out."); + } + catch (IOException) when (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + throw new TimeoutException("The asynchronous write operation timed out."); + } + } + } + #endregion } diff --git a/src/FluentModbus/SpanReader.cs b/src/FluentModbus/SpanReader.cs new file mode 100644 index 0000000..8062d2d --- /dev/null +++ b/src/FluentModbus/SpanReader.cs @@ -0,0 +1,184 @@ +using System.Runtime.InteropServices; + +namespace FluentModbus; + +internal ref struct SpanReader +{ + private ReadOnlySpan _span; + private int _position; + + public SpanReader(ReadOnlySpan span) + { + _span = span; + _position = 0; + } + + public int Position => _position; + + public void Seek(int offset) + { + _position = offset; + } + + public byte ReadByte() + { + var value = _span[_position]; + _position += 1; + return value; + } + + public short ReadInt16() + { + var value = MemoryMarshal.Read(_span.Slice(_position)); + _position += 2; + return value; + } + + public ushort ReadUInt16() + { + var value = MemoryMarshal.Read(_span.Slice(_position)); + _position += 2; + return value; + } + + public int ReadInt32() + { + var value = MemoryMarshal.Read(_span.Slice(_position)); + _position += 4; + return value; + } + + public uint ReadUInt32() + { + var value = MemoryMarshal.Read(_span.Slice(_position)); + _position += 4; + return value; + } + + public long ReadInt64() + { + var value = MemoryMarshal.Read(_span.Slice(_position)); + _position += 8; + return value; + } + + public ulong ReadUInt64() + { + var value = MemoryMarshal.Read(_span.Slice(_position)); + _position += 8; + return value; + } + + public float ReadFloat32() + { + var value = MemoryMarshal.Read(_span.Slice(_position)); + _position += 4; + return value; + } + + public double ReadFloat64() + { + var value = MemoryMarshal.Read(_span.Slice(_position)); + _position += 8; + return value; + } + + public short ReadInt16Reverse() + { + var value = ReadInt16(); + return BitConverter.IsLittleEndian ? Reverse(value) : value; + } + + public ushort ReadUInt16Reverse() + { + var value = ReadUInt16(); + return BitConverter.IsLittleEndian ? Reverse(value) : value; + } + + public int ReadInt32Reverse() + { + var value = ReadInt32(); + return BitConverter.IsLittleEndian ? Reverse(value) : value; + } + + public uint ReadUInt32Reverse() + { + var value = ReadUInt32(); + return BitConverter.IsLittleEndian ? Reverse(value) : value; + } + + public long ReadInt64Reverse() + { + var value = ReadInt64(); + return BitConverter.IsLittleEndian ? Reverse(value) : value; + } + + public ulong ReadUInt64Reverse() + { + var value = ReadUInt64(); + return BitConverter.IsLittleEndian ? Reverse(value) : value; + } + + public float ReadFloat32Reverse() + { + var value = ReadFloat32(); + return BitConverter.IsLittleEndian ? Reverse(value) : value; + } + + public double ReadFloat64Reverse() + { + var value = ReadFloat64(); + return BitConverter.IsLittleEndian ? Reverse(value) : value; + } + + private T ReadReverse(byte[] data) where T : struct + { + data.AsSpan().Reverse(); + return MemoryMarshal.Cast(data)[0]; + } + + private static unsafe short Reverse(short value) + { + return (short)((value & 0xFF00) >> 8 | (value & 0x00FF) << 8); + } + + private static unsafe ushort Reverse(ushort value) + { + return (ushort)((value & 0xFF00) >> 8 | (value & 0x00FF) << 8); + } + + private static unsafe int Reverse(int value) + { + return (int)Reverse((uint)value); + } + + private static unsafe uint Reverse(uint value) + { + value = (value >> 16) | (value << 16); + return ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8); + } + + private static unsafe long Reverse(long value) + { + return (long)Reverse((ulong)value); + } + + private static unsafe ulong Reverse(ulong value) + { + value = (value >> 32) | (value << 32); + value = ((value & 0xFFFF0000FFFF0000) >> 16) | ((value & 0x0000FFFF0000FFFF) << 16); + return ((value & 0xFF00FF00FF00FF00) >> 8) | ((value & 0x00FF00FF00FF00FF) << 8); + } + + private static unsafe float Reverse(float value) + { + uint temp = Reverse(*(uint*)&value); + return *(float*)&temp; + } + + private static unsafe double Reverse(double value) + { + ulong temp = Reverse(*(ulong*)&value); + return *(double*)&temp; + } +} diff --git a/src/FluentModbus/SpanWriter.cs b/src/FluentModbus/SpanWriter.cs new file mode 100644 index 0000000..331fb9f --- /dev/null +++ b/src/FluentModbus/SpanWriter.cs @@ -0,0 +1,129 @@ +using System.Runtime.InteropServices; + +namespace FluentModbus; + +internal ref struct SpanWriter +{ + private Span _span; + private int _position; + + public SpanWriter(Span span) + { + _span = span; + _position = 0; + } + + public int Position => _position; + + public void Seek(int offset) + { + _position = offset; + } + + public void Write(byte value) + { + _span[_position] = value; + _position += 1; + } + + public void Write(short value) + { + MemoryMarshal.Write(_span.Slice(_position), ref value); + _position += 2; + } + + public void Write(ushort value) + { + MemoryMarshal.Write(_span.Slice(_position), ref value); + _position += 2; + } + + public void Write(int value) + { + MemoryMarshal.Write(_span.Slice(_position), ref value); + _position += 4; + } + + public void Write(uint value) + { + MemoryMarshal.Write(_span.Slice(_position), ref value); + _position += 4; + } + + public void Write(long value) + { + MemoryMarshal.Write(_span.Slice(_position), ref value); + _position += 8; + } + + public void Write(ulong value) + { + MemoryMarshal.Write(_span.Slice(_position), ref value); + _position += 8; + } + + public void Write(float value) + { + MemoryMarshal.Write(_span.Slice(_position), ref value); + _position += 4; + } + + public void Write(double value) + { + MemoryMarshal.Write(_span.Slice(_position), ref value); + _position += 8; + } + + public void WriteReverse(short value) + { + WriteReverse(BitConverter.GetBytes(value)); + } + + public void WriteReverse(ushort value) + { + WriteReverse(BitConverter.GetBytes(value)); + } + + public void WriteReverse(int value) + { + WriteReverse(BitConverter.GetBytes(value)); + } + + public void WriteReverse(uint value) + { + WriteReverse(BitConverter.GetBytes(value)); + } + + public void WriteReverse(long value) + { + WriteReverse(BitConverter.GetBytes(value)); + } + + public void WriteReverse(ulong value) + { + WriteReverse(BitConverter.GetBytes(value)); + } + + public void WriteReverse(float value) + { + WriteReverse(BitConverter.GetBytes(value)); + } + + public void WriteReverse(double value) + { + WriteReverse(BitConverter.GetBytes(value)); + } + + public void Write(ReadOnlySpan data) + { + data.CopyTo(_span.Slice(_position)); + _position += data.Length; + } + + private void WriteReverse(byte[] data) + { + Array.Reverse(data); + data.CopyTo(_span.Slice(_position)); + _position += data.Length; + } +}