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..5106199 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,26 @@ 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; + } + } + + /// + /// Is Log Out Frame Data + /// + protected bool IsLogFrameData=false; + + #endregion #region Methods @@ -579,5 +601,92 @@ public void ReadFifoQueue() { throw new NotImplementedException(); } + + + /// + /// 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); + } + + /// + /// 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) + { + return ByteArrayToHexViaLookup32UnsafeDirect(bytes); + } +#endif + } } diff --git a/src/FluentModbus/Client/ModbusRtuClient.cs b/src/FluentModbus/Client/ModbusRtuClient.cs index e70c899..1db29d5 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 @@ -183,7 +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; - + if (IsLogFrameData) + LogFrame("Tx", _frameBuffer.Buffer.AsMemory(0, frameLength)); // send request _serialPort!.Value.Value.Write(_frameBuffer.Buffer, 0, frameLength); @@ -197,19 +199,29 @@ 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])) { + if (IsLogFrameData) + 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) + { + if (IsLogFrameData) + LogFrame("no valid", _frameBuffer.Buffer); frameLength = 0; + } } } diff --git a/src/FluentModbus/Client/ModbusRtuClientAsync.cs b/src/FluentModbus/Client/ModbusRtuClientAsync.cs index c376418..80fc4bf 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! @@ -44,7 +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; - + if (IsLogFrameData) + 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,29 @@ 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])) { + if (IsLogFrameData) + 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) + { + if (IsLogFrameData) + LogFrame("no valid", _frameBuffer.Buffer); frameLength = 0; + } } } @@ -84,6 +95,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 + + + +