From a4c700108a7786b5f2bb8cf104b4e526dcbea055 Mon Sep 17 00:00:00 2001 From: zh3305 Date: Tue, 28 May 2024 14:14:28 +0800 Subject: [PATCH 1/3] feat: int RTU communication message Log --- .gitignore | 3 ++- sample/SampleServerClientRtu/Program.cs | 3 ++- src/FluentModbus/Client/ModbusClient.cs | 18 ++++++++++++++- src/FluentModbus/Client/ModbusRtuClient.cs | 15 +++++++++---- .../Client/ModbusRtuClientAsync.cs | 22 +++++++++++++------ src/FluentModbus/FluentModbus.csproj | 4 ++++ 6 files changed, 51 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index e95dc5b..6475f65 100755 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ doc/api/* *.suo *.user -*.pyc \ No newline at end of file +*.pyc +/_ReSharper.Caches/ diff --git a/sample/SampleServerClientRtu/Program.cs b/sample/SampleServerClientRtu/Program.cs index 2c6e84b..1654ac1 100644 --- a/sample/SampleServerClientRtu/Program.cs +++ b/sample/SampleServerClientRtu/Program.cs @@ -25,7 +25,7 @@ static async Task Main(string[] args) /* create logger */ var loggerFactory = LoggerFactory.Create(loggingBuilder => { - loggingBuilder.SetMinimumLevel(LogLevel.Debug); + loggingBuilder.SetMinimumLevel(LogLevel.Trace); loggingBuilder.AddConsole(); }); @@ -47,6 +47,7 @@ static async Task Main(string[] args) /* create Modbus RTU client */ var client = new ModbusRtuClient(); + client.Logger = clientLogger; /* run Modbus RTU server */ var cts = new CancellationTokenSource(); diff --git a/src/FluentModbus/Client/ModbusClient.cs b/src/FluentModbus/Client/ModbusClient.cs index a83a722..eaf05aa 100755 --- a/src/FluentModbus/Client/ModbusClient.cs +++ b/src/FluentModbus/Client/ModbusClient.cs @@ -1,4 +1,6 @@ -using System.Collections; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System.Collections; using System.Runtime.InteropServices; namespace FluentModbus @@ -17,6 +19,13 @@ public abstract partial class ModbusClient protected private bool SwapBytes { get; set; } + /// + /// Get or set the logger + /// + + public ILogger Logger { get; set; } = NullLogger.Instance; + + #endregion #region Methods @@ -579,5 +588,12 @@ public void ReadFifoQueue() { throw new NotImplementedException(); } + + + + protected void LogFrame(string prefix, byte[] frame) + { + Logger.LogTrace($"{prefix}: {string.Join(" ", frame.Select(b => b.ToString("X2")))}"); + } } } diff --git a/src/FluentModbus/Client/ModbusRtuClient.cs b/src/FluentModbus/Client/ModbusRtuClient.cs index e70c899..cfa492a 100755 --- a/src/FluentModbus/Client/ModbusRtuClient.cs +++ b/src/FluentModbus/Client/ModbusRtuClient.cs @@ -1,4 +1,6 @@ -using System.IO.Ports; +using Microsoft.Extensions.Logging; +using System.IO.Ports; +using Microsoft.Extensions.Logging.Abstractions; namespace FluentModbus { @@ -21,7 +23,6 @@ public partial class ModbusRtuClient : ModbusClient, IDisposable /// public ModbusRtuClient() { - // } #endregion @@ -184,6 +185,7 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio _frameBuffer.Writer.Write(crc); frameLength = (int)_frameBuffer.Writer.BaseStream.Position; + LogFrame("Tx", _frameBuffer.Buffer.AsSpan(0, frameLength).ToArray()); // send request _serialPort!.Value.Value.Write(_frameBuffer.Buffer, 0, frameLength); @@ -197,13 +199,18 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio while (true) { - frameLength += _serialPort!.Value.Value.Read(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength); + int numBytesRead = _serialPort!.Value.Value.Read(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength); + if (numBytesRead == 0) + { + throw new IOException("Read resulted in 0 bytes returned."); + } + frameLength += numBytesRead; if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory()[..frameLength])) { + LogFrame("Rx",_frameBuffer.Buffer.AsMemory()[..frameLength].ToArray()); break; } - else { // reset length because one or more chunks of data were received and written to diff --git a/src/FluentModbus/Client/ModbusRtuClientAsync.cs b/src/FluentModbus/Client/ModbusRtuClientAsync.cs index c376418..1ed5595 100755 --- a/src/FluentModbus/Client/ModbusRtuClientAsync.cs +++ b/src/FluentModbus/Client/ModbusRtuClientAsync.cs @@ -3,9 +3,9 @@ namespace FluentModbus { - public partial class ModbusRtuClient - { - /// + public partial class ModbusRtuClient + { + /// 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! @@ -45,6 +45,7 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti _frameBuffer.Writer.Write(crc); frameLength = (int)_frameBuffer.Writer.BaseStream.Position; + LogFrame("Tx", _frameBuffer.Buffer.AsMemory(0, frameLength).ToArray()); // send request await _serialPort!.Value.Value.WriteAsync(_frameBuffer.Buffer, 0, frameLength, cancellationToken).ConfigureAwait(false); @@ -58,19 +59,26 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti while (true) { - frameLength += await _serialPort!.Value.Value.ReadAsync(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength, cancellationToken).ConfigureAwait(false); + int numBytesRead = await _serialPort!.Value.Value.ReadAsync(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength, cancellationToken).ConfigureAwait(false); + if (numBytesRead == 0) + { + throw new IOException("Read resulted in 0 bytes returned."); + } + frameLength += numBytesRead; if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory()[..frameLength])) { + LogFrame("Rx",_frameBuffer.Buffer.AsMemory()[..frameLength].ToArray()); break; } - else { // reset length because one or more chunks of data were received and written to // the buffer, but no valid Modbus frame could be detected and now the buffer is full if (frameLength == _frameBuffer.Buffer.Length) + { frameLength = 0; + } } } @@ -84,6 +92,6 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseFunctionCode); return _frameBuffer.Buffer.AsMemory(1, frameLength - 3); - } - } + } + } } \ No newline at end of file diff --git a/src/FluentModbus/FluentModbus.csproj b/src/FluentModbus/FluentModbus.csproj index fe56da7..0878e34 100644 --- a/src/FluentModbus/FluentModbus.csproj +++ b/src/FluentModbus/FluentModbus.csproj @@ -79,5 +79,9 @@ FluentModbus + + + + From a3456c69f7799cd013b3f704ca964fe305f72f3d Mon Sep 17 00:00:00 2001 From: zh3305 Date: Tue, 28 May 2024 17:13:35 +0800 Subject: [PATCH 2/3] feat : Log no valid Modbus frame --- src/FluentModbus/Client/ModbusRtuClient.cs | 3 +++ src/FluentModbus/Client/ModbusRtuClientAsync.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/FluentModbus/Client/ModbusRtuClient.cs b/src/FluentModbus/Client/ModbusRtuClient.cs index cfa492a..f941129 100755 --- a/src/FluentModbus/Client/ModbusRtuClient.cs +++ b/src/FluentModbus/Client/ModbusRtuClient.cs @@ -216,7 +216,10 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio // reset length because one or more chunks of data were received and written to // the buffer, but no valid Modbus frame could be detected and now the buffer is full if (frameLength == _frameBuffer.Buffer.Length) + { + LogFrame("no valid", _frameBuffer.Buffer); frameLength = 0; + } } } diff --git a/src/FluentModbus/Client/ModbusRtuClientAsync.cs b/src/FluentModbus/Client/ModbusRtuClientAsync.cs index 1ed5595..13904c8 100755 --- a/src/FluentModbus/Client/ModbusRtuClientAsync.cs +++ b/src/FluentModbus/Client/ModbusRtuClientAsync.cs @@ -77,6 +77,7 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti // the buffer, but no valid Modbus frame could be detected and now the buffer is full if (frameLength == _frameBuffer.Buffer.Length) { + LogFrame("no valid", _frameBuffer.Buffer); frameLength = 0; } } From e5673de1f6c9980d04859127bb8646a24cec1d0f Mon Sep 17 00:00:00 2001 From: zh3305 Date: Sat, 1 Jun 2024 14:34:29 +0800 Subject: [PATCH 3/3] Improve LogFrame performance --- src/FluentModbus/Client/ModbusClient.cs | 99 ++++++++++++++++++- src/FluentModbus/Client/ModbusRtuClient.cs | 10 +- .../Client/ModbusRtuClientAsync.cs | 10 +- 3 files changed, 108 insertions(+), 11 deletions(-) diff --git a/src/FluentModbus/Client/ModbusClient.cs b/src/FluentModbus/Client/ModbusClient.cs index eaf05aa..5106199 100755 --- a/src/FluentModbus/Client/ModbusClient.cs +++ b/src/FluentModbus/Client/ModbusClient.cs @@ -19,11 +19,24 @@ public abstract partial class ModbusClient protected private bool SwapBytes { get; set; } + private ILogger _logger = NullLogger.Instance; /// /// Get or set the logger /// + public ILogger Logger + { + get => _logger; + set + { + IsLogFrameData = value != NullLogger.Instance; + _logger = value; + } + } - public ILogger Logger { get; set; } = NullLogger.Instance; + /// + /// Is Log Out Frame Data + /// + protected bool IsLogFrameData=false; #endregion @@ -590,10 +603,90 @@ public void ReadFifoQueue() } + /// + /// Log the frame in hexadecimal format. + /// + protected void LogFrame(string prefix, Memory frame) + { + Logger.LogTrace("{prefix}:{frameHex}", prefix, ByteToHex(frame)); + } + +#if NET5_0_OR_GREATER + /// + /// Bytes Convert To Hex String + /// + public string ByteToHex(Memory bytes) + { + return Convert.ToHexString(bytes.Span); + } +#else + + private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe(); + private static readonly unsafe uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe, GCHandleType.Pinned).AddrOfPinnedObject(); + private static uint[] CreateLookup32Unsafe() + { + var result = new uint[256]; + for (int i = 0; i < 256; i++) + { + string s = i.ToString("X2"); + if (System.BitConverter.IsLittleEndian) + result[i] = ((uint)s[0]) + ((uint)s[1] << 16); + else + result[i] = ((uint)s[1]) + ((uint)s[0] << 16); + } + return result; + } + /// + /// High performance C# byte array to hex string + /// https://stackoverflow.com/a/24343727/3161322 + /// + /// + /// + public unsafe string ByteArrayToHexViaLookup32Unsafe(Memory bytes) + { + var lookupP = _lookup32UnsafeP; + var result = new char[bytes.Length * 2]; + fixed (byte* bytesP = bytes.Span) + fixed (char* resultP = result) + { + uint* resultP2 = (uint*)resultP; + for (int i = 0; i < bytes.Length; i++) + { + resultP2[i] = lookupP[bytesP[i]]; + } + } + return new string(result); + } - protected void LogFrame(string prefix, byte[] frame) + /// + /// High performance C# byte array to hex string by write into the string directly + /// https://stackoverflow.com/a/24343727/3161322 + /// + /// + /// + public unsafe string ByteArrayToHexViaLookup32UnsafeDirect(Memory bytes) + { + var lookupP = _lookup32UnsafeP; + var result = new string((char)0, bytes.Length * 2); + fixed (byte* bytesP = bytes.Span) + fixed (char* resultP = result) + { + uint* resultP2 = (uint*)resultP; + for (int i = 0; i < bytes.Length; i++) + { + resultP2[i] = lookupP[bytesP[i]]; + } + } + return result; + } + /// + /// Bytes Convert To Hex String + /// + public string ByteToHex(Memory bytes) { - Logger.LogTrace($"{prefix}: {string.Join(" ", frame.Select(b => b.ToString("X2")))}"); + return ByteArrayToHexViaLookup32UnsafeDirect(bytes); } +#endif + } } diff --git a/src/FluentModbus/Client/ModbusRtuClient.cs b/src/FluentModbus/Client/ModbusRtuClient.cs index f941129..1db29d5 100755 --- a/src/FluentModbus/Client/ModbusRtuClient.cs +++ b/src/FluentModbus/Client/ModbusRtuClient.cs @@ -184,8 +184,8 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.AsMemory()[..frameLength]); _frameBuffer.Writer.Write(crc); frameLength = (int)_frameBuffer.Writer.BaseStream.Position; - - LogFrame("Tx", _frameBuffer.Buffer.AsSpan(0, frameLength).ToArray()); + if (IsLogFrameData) + LogFrame("Tx", _frameBuffer.Buffer.AsMemory(0, frameLength)); // send request _serialPort!.Value.Value.Write(_frameBuffer.Buffer, 0, frameLength); @@ -208,7 +208,8 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio frameLength += numBytesRead; if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory()[..frameLength])) { - LogFrame("Rx",_frameBuffer.Buffer.AsMemory()[..frameLength].ToArray()); + if (IsLogFrameData) + LogFrame("Rx",_frameBuffer.Buffer.AsMemory()[..frameLength].ToArray()); break; } else @@ -217,7 +218,8 @@ protected override Span TransceiveFrame(byte unitIdentifier, ModbusFunctio // the buffer, but no valid Modbus frame could be detected and now the buffer is full if (frameLength == _frameBuffer.Buffer.Length) { - LogFrame("no valid", _frameBuffer.Buffer); + if (IsLogFrameData) + LogFrame("no valid", _frameBuffer.Buffer); frameLength = 0; } } diff --git a/src/FluentModbus/Client/ModbusRtuClientAsync.cs b/src/FluentModbus/Client/ModbusRtuClientAsync.cs index 13904c8..80fc4bf 100755 --- a/src/FluentModbus/Client/ModbusRtuClientAsync.cs +++ b/src/FluentModbus/Client/ModbusRtuClientAsync.cs @@ -44,8 +44,8 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.AsMemory()[..frameLength]); _frameBuffer.Writer.Write(crc); frameLength = (int)_frameBuffer.Writer.BaseStream.Position; - - LogFrame("Tx", _frameBuffer.Buffer.AsMemory(0, frameLength).ToArray()); + if (IsLogFrameData) + LogFrame("Tx", _frameBuffer.Buffer.AsMemory(0, frameLength).ToArray()); // send request await _serialPort!.Value.Value.WriteAsync(_frameBuffer.Buffer, 0, frameLength, cancellationToken).ConfigureAwait(false); @@ -68,7 +68,8 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti frameLength += numBytesRead; if (ModbusUtils.DetectResponseFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory()[..frameLength])) { - LogFrame("Rx",_frameBuffer.Buffer.AsMemory()[..frameLength].ToArray()); + if (IsLogFrameData) + LogFrame("Rx",_frameBuffer.Buffer.AsMemory()[..frameLength].ToArray()); break; } else @@ -77,7 +78,8 @@ protected override async Task> TransceiveFrameAsync(byte unitIdenti // the buffer, but no valid Modbus frame could be detected and now the buffer is full if (frameLength == _frameBuffer.Buffer.Length) { - LogFrame("no valid", _frameBuffer.Buffer); + if (IsLogFrameData) + LogFrame("no valid", _frameBuffer.Buffer); frameLength = 0; } }