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
56 changes: 36 additions & 20 deletions sample/SampleServerClientTcp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ static async Task Main(string[] args)
loggingBuilder.SetMinimumLevel(LogLevel.Debug);
loggingBuilder.AddConsole();
});

bool needServer = false;
var serverLogger = loggerFactory.CreateLogger("Server");
var clientLogger = loggerFactory.CreateLogger("Client");

Expand All @@ -34,28 +34,32 @@ static async Task Main(string[] args)

/* run Modbus TCP server */
var cts = new CancellationTokenSource();
server.Start();
serverLogger.LogInformation("Server started.");

var task_server = Task.Run(async () =>
Task? task_server = default;
if (needServer)
{
while (!cts.IsCancellationRequested)
server.Start();
serverLogger.LogInformation("Server started.");

task_server = Task.Run(async () =>
{
// lock is required to synchronize buffer access between this application and one or more Modbus clients
lock (server.Lock)
while (!cts.IsCancellationRequested)
{
DoServerWork(server);
// lock is required to synchronize buffer access between this application and one or more Modbus clients
lock (server.Lock)
{
DoServerWork(server);
}

// update server register content once per second
await Task.Delay(TimeSpan.FromSeconds(1));
}

// update server register content once per second
await Task.Delay(TimeSpan.FromSeconds(1));
}
}, cts.Token);
}, cts.Token);
}

/* run Modbus TCP client */
var task_client = Task.Run(() =>
{
client.Connect();
client.Connect("127.0.0.1",ModbusEndianness.BigEndian);

try
{
Expand All @@ -77,10 +81,12 @@ static async Task Main(string[] args)

// stop server
cts.Cancel();
await task_server;

server.Stop();
serverLogger.LogInformation("Server stopped.");
if(needServer && task_server != null)
{
await task_server;
server.Stop();
serverLogger.LogInformation("Server stopped.");
}
}

static void DoServerWork(ModbusTcpServer server)
Expand Down Expand Up @@ -113,32 +119,42 @@ static void DoClientWork(ModbusTcpClient client, ILogger logger)
Span<byte> data;

var sleepTime = TimeSpan.FromMilliseconds(100);
var unitIdentifier = 0x00;
var unitIdentifier = 0x01;
var startingAddress = 0;
var registerAddress = 0;

// ReadHoldingRegisters = 0x03, // FC03
data = client.ReadHoldingRegisters<byte>(unitIdentifier, startingAddress, 10);
var data1 = client.ReadHoldingRegisters<short>(unitIdentifier, 10, 3).ToArray();
var data2 = client.ReadHoldingRegisters<int>(unitIdentifier, 16, 4).ToArray();
var data3 = client.ReadHoldingRegisters<float>(unitIdentifier, 24, 5).ToArray();
logger.LogInformation("FC03 - ReadHoldingRegisters: Done");
client.WriteSingleRegister(unitIdentifier, 10, 41);


Thread.Sleep(sleepTime);

// WriteMultipleRegisters = 0x10, // FC16
client.WriteMultipleRegisters(unitIdentifier, startingAddress, new byte[] { 10, 00, 20, 00, 30, 00, 255, 00, 255, 01 });
client.WriteMultipleRegisters(unitIdentifier, 12, [41, 42]);
logger.LogInformation("FC16 - WriteMultipleRegisters: Done");
Thread.Sleep(sleepTime);

// ReadCoils = 0x01, // FC01
data = client.ReadCoils(unitIdentifier, startingAddress, 10);
var data5 = client.ReadCoils2(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC01 - ReadCoils: Done");
Thread.Sleep(sleepTime);

// ReadDiscreteInputs = 0x02, // FC02
data = client.ReadDiscreteInputs(unitIdentifier, startingAddress, 10);
var data6 = client.ReadDiscreteInputs2(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC02 - ReadDiscreteInputs: Done");
Thread.Sleep(sleepTime);

// ReadInputRegisters = 0x04, // FC04
data = client.ReadInputRegisters<byte>(unitIdentifier, startingAddress, 10);
var data4 = client.ReadInputRegisters<int>(unitIdentifier, 12, 10);
logger.LogInformation("FC04 - ReadInputRegisters: Done");
Thread.Sleep(sleepTime);

Expand Down
150 changes: 121 additions & 29 deletions src/FluentModbus/Client/ModbusClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,19 +121,28 @@ private ushort ConvertUshort(int value)
/// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param>
/// <param name="startingAddress">The holding register start address for the read operation.</param>
/// <param name="count">The number of elements of type <typeparamref name="T"/> to read.</param>
public Span<T> ReadHoldingRegisters<T>(int unitIdentifier, int startingAddress, int count) where T : unmanaged
/// <param name="registerOrder">Desired Word Order (Low Register first or High Register first</param>
public Span<T> ReadHoldingRegisters<T>(int unitIdentifier, int startingAddress, int count, RegisterOrder registerOrder = RegisterOrder.LowHigh) where T : unmanaged
{
var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier);
var startingAddress_converted = ConvertUshort(startingAddress);
var count_converted = ConvertUshort(count);

var dataset = MemoryMarshal.Cast<byte, T>(
ReadHoldingRegisters(unitIdentifier_converted, startingAddress_converted, ConvertSize<T>(count_converted)));
var bytes = ReadHoldingRegisters(unitIdentifier_converted, startingAddress_converted, ConvertSize<T>(count_converted));

if (typeof(T) == typeof(byte))
{
return MemoryMarshal.Cast<byte, T>(bytes);
}

if (SwapBytes)
ModbusUtils.SwitchEndianness(dataset);
ModbusUtils.SwitchRegistersBytes(bytes);

var registers = MemoryMarshal.Cast<byte, short>(bytes);

return dataset;

var result = ModbusUtils.ConvertRegistersTo<T>(registers, registerOrder);
return result;
}

/// <summary>
Expand Down Expand Up @@ -173,15 +182,28 @@ public Span<byte> ReadHoldingRegisters(byte unitIdentifier, ushort startingAddre
/// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param>
/// <param name="startingAddress">The holding register start address for the write operation.</param>
/// <param name="dataset">The data of type <typeparamref name="T"/> to write to the server.</param>
public void WriteMultipleRegisters<T>(int unitIdentifier, int startingAddress, T[] dataset) where T : unmanaged
/// <param name="registerOrder">Desired Word Order (Low Register first or High Register first</param>
public void WriteMultipleRegisters<T>(int unitIdentifier, int startingAddress, T[] dataset, RegisterOrder registerOrder = RegisterOrder.LowHigh) where T : unmanaged
{
var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier);
var startingAddress_converted = ConvertUshort(startingAddress);

if (SwapBytes)
ModbusUtils.SwitchEndianness(dataset.AsSpan());
Span<byte> bytes;

if (typeof(T) == typeof(byte))
{
bytes = MemoryMarshal.Cast<T, byte>(new Span<T>(dataset));
}
else
{
var registers = ModbusUtils.ConvertToRegisters(dataset, registerOrder);
bytes = MemoryMarshal.Cast<short, byte>(registers);

WriteMultipleRegisters(unitIdentifier_converted, startingAddress_converted, MemoryMarshal.Cast<T, byte>(dataset).ToArray());
if (SwapBytes)
ModbusUtils.SwitchRegistersBytes(bytes);
}

WriteMultipleRegisters(unitIdentifier_converted, startingAddress_converted, bytes.ToArray());
}

/// <summary>
Expand Down Expand Up @@ -255,6 +277,30 @@ public Span<byte> ReadCoils(int unitIdentifier, int startingAddress, int quantit
return buffer.Slice(2);
}

/// <summary>
/// Reads the specified number of coils as byte array. Each bit of the returned array represents a single coil.
/// </summary>
/// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param>
/// <param name="startingAddress">The coil start address for the read operation.</param>
/// <param name="quantity">The number of coils to read.</param>
public Span<bool> ReadCoils2(int unitIdentifier, int startingAddress, int quantity)
{
var buffer = ReadCoils(unitIdentifier, startingAddress, quantity);

bool[] array4 = new bool[quantity];
for (int i = 0; i < quantity; i++)
{
int num2 = buffer[unchecked(i / 8)];
unchecked
{
int num3 = Convert.ToInt32(Math.Pow(2.0, i % 8));
array4[i] = Convert.ToBoolean((num2 & num3) / num3);
}
}

return array4;
}

/// <summary>
/// Reads the specified number of discrete inputs as byte array. Each bit of the returned array represents a single discrete input.
/// </summary>
Expand Down Expand Up @@ -289,26 +335,55 @@ public Span<byte> ReadDiscreteInputs(int unitIdentifier, int startingAddress, in
return buffer.Slice(2);
}

/// <summary>
/// Reads the specified number of discrete inputs as byte array. Each bit of the returned array represents a single discrete input.
/// </summary>
/// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param>
/// <param name="startingAddress">The discrete input start address for the read operation.</param>
/// <param name="quantity">The number of discrete inputs to read.</param>
public Span<bool> ReadDiscreteInputs2(int unitIdentifier, int startingAddress, int quantity)
{
var buffer = ReadDiscreteInputs(unitIdentifier, startingAddress, quantity);

bool[] array4 = new bool[quantity];
for (int i = 0; i < quantity; i++)
{
int num2 = buffer[unchecked(i / 8)];
unchecked
{
int num3 = Convert.ToInt32(Math.Pow(2.0, i % 8));
array4[i] = Convert.ToBoolean((num2 & num3) / num3);
}
}

return array4;
}

/// <summary>
/// Reads the specified number of values of type <typeparamref name="T"/> from the input registers.
/// </summary>
/// <typeparam name="T">Determines the type of the returned data.</typeparam>
/// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param>
/// <param name="startingAddress">The input register start address for the read operation.</param>
/// <param name="count">The number of elements of type <typeparamref name="T"/> to read.</param>
public Span<T> ReadInputRegisters<T>(int unitIdentifier, int startingAddress, int count) where T : unmanaged
/// <param name="registerOrder">Desired Word Order (Low Register first or High Register first</param>
public Span<T> ReadInputRegisters<T>(int unitIdentifier, int startingAddress, int count, RegisterOrder registerOrder = RegisterOrder.LowHigh) where T : unmanaged
{
var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier);
var startingAddress_converted = ConvertUshort(startingAddress);
var count_converted = ConvertUshort(count);

var dataset = MemoryMarshal.Cast<byte, T>(
ReadInputRegisters(unitIdentifier_converted, startingAddress_converted, ConvertSize<T>(count_converted)));

var bytes = ReadInputRegisters(unitIdentifier_converted, startingAddress_converted, ConvertSize<T>(count_converted));
if (typeof(T) == typeof(byte))
{
return MemoryMarshal.Cast<byte, T>(bytes);
}
if (SwapBytes)
ModbusUtils.SwitchEndianness(dataset);
ModbusUtils.SwitchRegistersBytes(bytes);
var registers = MemoryMarshal.Cast<byte, short>(bytes);
var result = ModbusUtils.ConvertRegistersTo<T>(registers, registerOrder);

return dataset;
return result;
}

/// <summary>
Expand Down Expand Up @@ -380,10 +455,11 @@ public void WriteSingleRegister(int unitIdentifier, int registerAddress, short v
var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier);
var registerAddress_converted = ConvertUshort(registerAddress);

var bytes = MemoryMarshal.Cast<short, byte>(new Span<short>([value]));
if (SwapBytes)
value = ModbusUtils.SwitchEndianness(value);
ModbusUtils.SwitchRegistersBytes(bytes);

WriteSingleRegister(unitIdentifier_converted, registerAddress_converted, MemoryMarshal.Cast<short, byte>(new [] { value }).ToArray());
WriteSingleRegister(unitIdentifier_converted, registerAddress_converted, bytes.ToArray());
}

/// <summary>
Expand All @@ -397,10 +473,11 @@ public void WriteSingleRegister(int unitIdentifier, int registerAddress, ushort
var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier);
var registerAddress_converted = ConvertUshort(registerAddress);

var bytes = MemoryMarshal.Cast<ushort, byte>(new Span<ushort>([value]));
if (SwapBytes)
value = ModbusUtils.SwitchEndianness(value);
ModbusUtils.SwitchRegistersBytes(bytes);

WriteSingleRegister(unitIdentifier_converted, registerAddress_converted, MemoryMarshal.Cast<ushort, byte>(new[] { value }).ToArray());
WriteSingleRegister(unitIdentifier_converted, registerAddress_converted, bytes.ToArray());
}

/// <summary>
Expand Down Expand Up @@ -505,26 +582,41 @@ public void MaskWriteRegister()
/// <param name="readCount">The number of elements of type <typeparamref name="TRead"/> to read.</param>
/// <param name="writeStartingAddress">The holding register start address for the write operation.</param>
/// <param name="dataset">The data of type <typeparamref name="TWrite"/> to write to the server.</param>
public Span<TRead> ReadWriteMultipleRegisters<TRead, TWrite>(int unitIdentifier, int readStartingAddress, int readCount, int writeStartingAddress, TWrite[] dataset) where TRead : unmanaged
where TWrite : unmanaged
/// <param name="registerOrder">Desired Word Order (Low Register first or High Register first</param>
public Span<TRead> ReadWriteMultipleRegisters<TRead, TWrite>(int unitIdentifier, int readStartingAddress, int readCount, int writeStartingAddress, TWrite[] dataset, RegisterOrder registerOrder = RegisterOrder.LowHigh) where TRead : unmanaged
where TWrite : unmanaged
{
var unitIdentifier_converted = ConvertUnitIdentifier(unitIdentifier);
var readStartingAddress_converted = ConvertUshort(readStartingAddress);
var readCount_converted = ConvertUshort(readCount);
var writeStartingAddress_converted = ConvertUshort(writeStartingAddress);

if (SwapBytes)
ModbusUtils.SwitchEndianness(dataset.AsSpan());

var readQuantity = ConvertSize<TRead>(readCount_converted);
var byteData = MemoryMarshal.Cast<TWrite, byte>(dataset).ToArray();
Span<byte> writeBytes;
if (typeof(TWrite) == typeof(byte))
{
writeBytes = MemoryMarshal.Cast<TWrite, byte>(new Span<TWrite>(dataset));
}
else
{
var writeRegisters = ModbusUtils.ConvertToRegisters(dataset, registerOrder);
writeBytes = MemoryMarshal.Cast<short, byte>(writeRegisters);

var dataset2 = MemoryMarshal.Cast<byte, TRead>(ReadWriteMultipleRegisters(unitIdentifier_converted, readStartingAddress_converted, readQuantity, writeStartingAddress_converted, byteData));
if (SwapBytes)
ModbusUtils.SwitchRegistersBytes(writeBytes);
}

var readQuantity = ConvertSize<TRead>(readCount_converted);
var readBytes = ReadWriteMultipleRegisters(unitIdentifier_converted, readStartingAddress_converted, readQuantity, writeStartingAddress_converted, writeBytes.ToArray());
if (typeof(TRead) == typeof(byte))
{
return MemoryMarshal.Cast<byte, TRead>(readBytes);
}
if (SwapBytes)
ModbusUtils.SwitchEndianness(dataset2);
ModbusUtils.SwitchRegistersBytes(readBytes);
var readRegisters = MemoryMarshal.Cast<byte, short>(readBytes);
var result = ModbusUtils.ConvertRegistersTo<TRead>(readRegisters, registerOrder);

return dataset2;
return result;
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/FluentModbus/Client/ModbusRtuOverTcpClientAsync.tt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<#@ template language="C#" #>
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
Expand Down
14 changes: 14 additions & 0 deletions src/FluentModbus/Client/RegisterOrder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace FluentModbus;

/// <summary>
/// </summary>
public enum RegisterOrder
{
/// <summary>
/// </summary>
LowHigh,

/// <summary>
/// </summary>
HighLow
}
Loading