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
+
+
+
+