Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ doc/api/*

*.suo
*.user
*.pyc
*.pyc
/_ReSharper.Caches/
3 changes: 2 additions & 1 deletion sample/SampleServerClientRtu/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand All @@ -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();
Expand Down
111 changes: 110 additions & 1 deletion src/FluentModbus/Client/ModbusClient.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -17,6 +19,26 @@ public abstract partial class ModbusClient

protected private bool SwapBytes { get; set; }

private ILogger _logger = NullLogger.Instance;
/// <summary>
/// Get or set the logger
/// </summary>
public ILogger Logger
{
get => _logger;
set
{
IsLogFrameData = value != NullLogger.Instance;
_logger = value;
}
}

/// <summary>
/// Is Log Out Frame Data
/// </summary>
protected bool IsLogFrameData=false;


#endregion

#region Methods
Expand Down Expand Up @@ -579,5 +601,92 @@ public void ReadFifoQueue()
{
throw new NotImplementedException();
}


/// <summary>
/// Log the frame in hexadecimal format.
/// </summary>
protected void LogFrame(string prefix, Memory<byte> frame)
{
Logger.LogTrace("{prefix}:{frameHex}", prefix, ByteToHex(frame));
}

#if NET5_0_OR_GREATER
/// <summary>
/// Bytes Convert To Hex String
/// </summary>
public string ByteToHex(Memory<byte> 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;
}
/// <summary>
/// High performance C# byte array to hex string
/// https://stackoverflow.com/a/24343727/3161322
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public unsafe string ByteArrayToHexViaLookup32Unsafe(Memory<byte> 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);
}

/// <summary>
/// High performance C# byte array to hex string by write into the string directly
/// https://stackoverflow.com/a/24343727/3161322
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public unsafe string ByteArrayToHexViaLookup32UnsafeDirect(Memory<byte> 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;
}
/// <summary>
/// Bytes Convert To Hex String
/// </summary>
public string ByteToHex(Memory<byte> bytes)
{
return ByteArrayToHexViaLookup32UnsafeDirect(bytes);
}
#endif

}
}
22 changes: 17 additions & 5 deletions src/FluentModbus/Client/ModbusRtuClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.IO.Ports;
using Microsoft.Extensions.Logging;
using System.IO.Ports;
using Microsoft.Extensions.Logging.Abstractions;

namespace FluentModbus
{
Expand All @@ -21,7 +23,6 @@ public partial class ModbusRtuClient : ModbusClient, IDisposable
/// </summary>
public ModbusRtuClient()
{
//
}

#endregion
Expand Down Expand Up @@ -183,7 +184,8 @@ protected override Span<byte> 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);

Expand All @@ -197,19 +199,29 @@ protected override Span<byte> 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;
}
}
}

Expand Down
27 changes: 19 additions & 8 deletions src/FluentModbus/Client/ModbusRtuClientAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

namespace FluentModbus
{
public partial class ModbusRtuClient
{
///<inheritdoc/>
public partial class ModbusRtuClient
{
///<inheritdoc/>
protected override async Task<Memory<byte>> TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action<ExtendedBinaryWriter> extendFrame, CancellationToken cancellationToken = default)
{
// WARNING: IF YOU EDIT THIS METHOD, REFLECT ALL CHANGES ALSO IN TransceiveFrameAsync!
Expand Down Expand Up @@ -44,7 +44,8 @@ protected override async Task<Memory<byte>> 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);

Expand All @@ -58,19 +59,29 @@ protected override async Task<Memory<byte>> 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;
}
}
}

Expand All @@ -84,6 +95,6 @@ protected override async Task<Memory<byte>> TransceiveFrameAsync(byte unitIdenti
throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseFunctionCode);

return _frameBuffer.Buffer.AsMemory(1, frameLength - 3);
}
}
}
}
}
4 changes: 4 additions & 0 deletions src/FluentModbus/FluentModbus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,9 @@
<CustomToolNamespace>FluentModbus</CustomToolNamespace>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>

</Project>