diff --git a/AMWD.Protocols.Modbus.Common/Events/CoilWrittenEventArgs.cs b/AMWD.Protocols.Modbus.Common/Events/CoilWrittenEventArgs.cs
index a60b154..4755dd3 100644
--- a/AMWD.Protocols.Modbus.Common/Events/CoilWrittenEventArgs.cs
+++ b/AMWD.Protocols.Modbus.Common/Events/CoilWrittenEventArgs.cs
@@ -8,19 +8,26 @@ namespace AMWD.Protocols.Modbus.Common.Events
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class CoilWrittenEventArgs : EventArgs
{
+ internal CoilWrittenEventArgs(byte unitId, ushort address, bool value)
+ {
+ UnitId = unitId;
+ Address = address;
+ Value = value;
+ }
+
///
/// Gets or sets the unit id.
///
- public byte UnitId { get; set; }
+ public byte UnitId { get; }
///
/// Gets or sets the coil address.
///
- public ushort Address { get; set; }
+ public ushort Address { get; }
///
/// Gets or sets the coil value.
///
- public bool Value { get; set; }
+ public bool Value { get; }
}
}
diff --git a/AMWD.Protocols.Modbus.Common/Events/RegisterWrittenEventArgs.cs b/AMWD.Protocols.Modbus.Common/Events/RegisterWrittenEventArgs.cs
index 51ea589..cfc786d 100644
--- a/AMWD.Protocols.Modbus.Common/Events/RegisterWrittenEventArgs.cs
+++ b/AMWD.Protocols.Modbus.Common/Events/RegisterWrittenEventArgs.cs
@@ -8,29 +8,39 @@ namespace AMWD.Protocols.Modbus.Common.Events
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class RegisterWrittenEventArgs : EventArgs
{
+ internal RegisterWrittenEventArgs(byte unitId, ushort address, byte highByte, byte lowByte)
+ {
+ UnitId = unitId;
+ Address = address;
+ HighByte = highByte;
+ LowByte = lowByte;
+
+ Value = new[] { highByte, lowByte }.GetBigEndianUInt16();
+ }
+
///
/// Gets or sets the unit id.
///
- public byte UnitId { get; set; }
+ public byte UnitId { get; }
///
/// Gets or sets the address of the register.
///
- public ushort Address { get; set; }
+ public ushort Address { get; }
///
/// Gets or sets the value of the register.
///
- public ushort Value { get; set; }
+ public ushort Value { get; }
///
/// Gets or sets the high byte of the register.
///
- public byte HighByte { get; set; }
+ public byte HighByte { get; }
///
/// Gets or sets the low byte of the register.
///
- public byte LowByte { get; set; }
+ public byte LowByte { get; }
}
}
diff --git a/AMWD.Protocols.Modbus.Common/Extensions/ArrayExtensions.cs b/AMWD.Protocols.Modbus.Common/Extensions/ArrayExtensions.cs
index 78623d7..09535bc 100644
--- a/AMWD.Protocols.Modbus.Common/Extensions/ArrayExtensions.cs
+++ b/AMWD.Protocols.Modbus.Common/Extensions/ArrayExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
namespace AMWD.Protocols.Modbus.Common
@@ -12,14 +13,14 @@ namespace AMWD.Protocols.Modbus.Common
Array.Reverse(bytes);
}
- public static ushort GetBigEndianUInt16(this byte[] bytes, int offset = 0)
+ public static ushort GetBigEndianUInt16(this IReadOnlyList bytes, int offset = 0)
{
byte[] b = bytes.Skip(offset).Take(2).ToArray();
b.SwapBigEndian();
return BitConverter.ToUInt16(b, 0);
}
- public static byte[] ToBigEndianBytes(this ushort value)
+ public static IReadOnlyList ToBigEndianBytes(this ushort value)
{
byte[] b = BitConverter.GetBytes(value);
b.SwapBigEndian();
diff --git a/AMWD.Protocols.Modbus.Common/Models/HoldingRegister.cs b/AMWD.Protocols.Modbus.Common/Models/HoldingRegister.cs
index 67f9329..9f51a8f 100644
--- a/AMWD.Protocols.Modbus.Common/Models/HoldingRegister.cs
+++ b/AMWD.Protocols.Modbus.Common/Models/HoldingRegister.cs
@@ -17,15 +17,11 @@ namespace AMWD.Protocols.Modbus.Common
{
get
{
- byte[] blob = [HighByte, LowByte];
- blob.SwapBigEndian();
- return BitConverter.ToUInt16(blob, 0);
+ return new[] { HighByte, LowByte }.GetBigEndianUInt16();
}
set
{
- byte[] blob = BitConverter.GetBytes(value);
- blob.SwapBigEndian();
-
+ var blob = value.ToBigEndianBytes();
HighByte = blob[0];
LowByte = blob[1];
}
diff --git a/AMWD.Protocols.Modbus.Common/Models/InputRegister.cs b/AMWD.Protocols.Modbus.Common/Models/InputRegister.cs
index 8b2a186..7c24538 100644
--- a/AMWD.Protocols.Modbus.Common/Models/InputRegister.cs
+++ b/AMWD.Protocols.Modbus.Common/Models/InputRegister.cs
@@ -17,9 +17,7 @@ namespace AMWD.Protocols.Modbus.Common
{
get
{
- byte[] blob = [HighByte, LowByte];
- blob.SwapBigEndian();
- return BitConverter.ToUInt16(blob, 0);
+ return new[] { HighByte, LowByte }.GetBigEndianUInt16();
}
}
diff --git a/AMWD.Protocols.Modbus.Common/Models/ModbusDevice.cs b/AMWD.Protocols.Modbus.Common/Models/ModbusDevice.cs
index 97fc22e..e4f67db 100644
--- a/AMWD.Protocols.Modbus.Common/Models/ModbusDevice.cs
+++ b/AMWD.Protocols.Modbus.Common/Models/ModbusDevice.cs
@@ -11,7 +11,7 @@ namespace AMWD.Protocols.Modbus.Common.Models
/// Initializes a new instance of the class.
///
/// The ID.
- public class ModbusDevice(byte id) : IDisposable
+ internal class ModbusDevice(byte id) : IDisposable
{
private readonly ReaderWriterLockSlim _rwLockCoils = new();
private readonly ReaderWriterLockSlim _rwLockDiscreteInputs = new();
diff --git a/AMWD.Protocols.Modbus.Common/Protocols/AsciiProtocol.cs b/AMWD.Protocols.Modbus.Common/Protocols/AsciiProtocol.cs
index 03ebb6c..62c4342 100644
--- a/AMWD.Protocols.Modbus.Common/Protocols/AsciiProtocol.cs
+++ b/AMWD.Protocols.Modbus.Common/Protocols/AsciiProtocol.cs
@@ -92,11 +92,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
string request = $":{unitId:X2}{(byte)ModbusFunctionCode.ReadCoils:X2}";
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request += $"{addrBytes[0]:X2}{addrBytes[1]:X2}";
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request += $"{countBytes[0]:X2}{countBytes[1]:X2}";
// LRC
@@ -151,11 +151,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
string request = $":{unitId:X2}{(byte)ModbusFunctionCode.ReadDiscreteInputs:X2}";
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request += $"{addrBytes[0]:X2}{addrBytes[1]:X2}";
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request += $"{countBytes[0]:X2}{countBytes[1]:X2}";
// LRC
@@ -209,11 +209,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
string request = $":{unitId:X2}{(byte)ModbusFunctionCode.ReadHoldingRegisters:X2}";
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request += $"{addrBytes[0]:X2}{addrBytes[1]:X2}";
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request += $"{countBytes[0]:X2}{countBytes[1]:X2}";
// LRC
@@ -264,11 +264,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
string request = $":{unitId:X2}{(byte)ModbusFunctionCode.ReadInputRegisters:X2}";
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request += $"{addrBytes[0]:X2}{addrBytes[1]:X2}";
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request += $"{countBytes[0]:X2}{countBytes[1]:X2}";
// LRC
@@ -383,7 +383,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
string request = $":{unitId:X2}{(byte)ModbusFunctionCode.WriteSingleCoil:X2}";
// Starting address
- byte[] addrBytes = coil.Address.ToBigEndianBytes();
+ var addrBytes = coil.Address.ToBigEndianBytes();
request += $"{addrBytes[0]:X2}{addrBytes[1]:X2}";
// Value
@@ -426,7 +426,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
string request = $":{unitId:X2}{(byte)ModbusFunctionCode.WriteSingleRegister:X2}";
// Starting address
- byte[] addrBytes = register.Address.ToBigEndianBytes();
+ var addrBytes = register.Address.ToBigEndianBytes();
request += $"{addrBytes[0]:X2}{addrBytes[1]:X2}";
// Value
@@ -497,11 +497,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
string request = $":{unitId:X2}{(byte)ModbusFunctionCode.WriteMultipleCoils:X2}";
// Starting address
- byte[] addrBytes = firstAddress.ToBigEndianBytes();
+ var addrBytes = firstAddress.ToBigEndianBytes();
request += $"{addrBytes[0]:X2}{addrBytes[1]:X2}";
// Quantity
- byte[] countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
+ var countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
request += $"{countBytes[0]:X2}{countBytes[1]:X2}";
// Byte count
@@ -567,11 +567,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
string request = $":{unitId:X2}{(byte)ModbusFunctionCode.WriteMultipleRegisters:X2}";
// Starting address
- byte[] addrBytes = firstAddress.ToBigEndianBytes();
+ var addrBytes = firstAddress.ToBigEndianBytes();
request += $"{addrBytes[0]:X2}{addrBytes[1]:X2}";
// Quantity
- byte[] countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
+ var countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
request += $"{countBytes[0]:X2}{countBytes[1]:X2}";
// Byte count
diff --git a/AMWD.Protocols.Modbus.Common/Protocols/RtuOverTcpProtocol.cs b/AMWD.Protocols.Modbus.Common/Protocols/RtuOverTcpProtocol.cs
index 3815822..c85a94a 100644
--- a/AMWD.Protocols.Modbus.Common/Protocols/RtuOverTcpProtocol.cs
+++ b/AMWD.Protocols.Modbus.Common/Protocols/RtuOverTcpProtocol.cs
@@ -119,12 +119,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadCoils;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -182,12 +182,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadDiscreteInputs;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -245,12 +245,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -305,12 +305,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadInputRegisters;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -432,7 +432,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Function code
request[7] = (byte)ModbusFunctionCode.WriteSingleCoil;
- byte[] addrBytes = coil.Address.ToBigEndianBytes();
+ var addrBytes = coil.Address.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
@@ -479,7 +479,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Function code
request[7] = (byte)ModbusFunctionCode.WriteSingleRegister;
- byte[] addrBytes = register.Address.ToBigEndianBytes();
+ var addrBytes = register.Address.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
@@ -542,12 +542,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.WriteMultipleCoils;
// Starting address
- byte[] addrBytes = firstAddress.ToBigEndianBytes();
+ var addrBytes = firstAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
+ var countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -622,12 +622,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.WriteMultipleRegisters;
// Starting address
- byte[] addrBytes = firstAddress.ToBigEndianBytes();
+ var addrBytes = firstAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
+ var countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -747,7 +747,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Transaction id
ushort txId = GetNextTransacitonId();
- byte[] txBytes = txId.ToBigEndianBytes();
+ var txBytes = txId.ToBigEndianBytes();
header[0] = txBytes[0];
header[1] = txBytes[1];
@@ -756,7 +756,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
header[3] = 0x00;
// Number of following bytes
- byte[] countBytes = ((ushort)followingBytes).ToBigEndianBytes();
+ var countBytes = ((ushort)followingBytes).ToBigEndianBytes();
header[4] = countBytes[0];
header[5] = countBytes[1];
diff --git a/AMWD.Protocols.Modbus.Common/Protocols/RtuProtocol.cs b/AMWD.Protocols.Modbus.Common/Protocols/RtuProtocol.cs
index 5a7109a..cdd9a0b 100644
--- a/AMWD.Protocols.Modbus.Common/Protocols/RtuProtocol.cs
+++ b/AMWD.Protocols.Modbus.Common/Protocols/RtuProtocol.cs
@@ -96,12 +96,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[1] = (byte)ModbusFunctionCode.ReadCoils;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[2] = addrBytes[0];
request[3] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[4] = countBytes[0];
request[5] = countBytes[1];
@@ -156,12 +156,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[1] = (byte)ModbusFunctionCode.ReadDiscreteInputs;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[2] = addrBytes[0];
request[3] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[4] = countBytes[0];
request[5] = countBytes[1];
@@ -216,12 +216,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[1] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[2] = addrBytes[0];
request[3] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[4] = countBytes[0];
request[5] = countBytes[1];
@@ -273,12 +273,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[1] = (byte)ModbusFunctionCode.ReadInputRegisters;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[2] = addrBytes[0];
request[3] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[4] = countBytes[0];
request[5] = countBytes[1];
@@ -394,7 +394,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Function code
request[1] = (byte)ModbusFunctionCode.WriteSingleCoil;
- byte[] addrBytes = coil.Address.ToBigEndianBytes();
+ var addrBytes = coil.Address.ToBigEndianBytes();
request[2] = addrBytes[0];
request[3] = addrBytes[1];
@@ -438,7 +438,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Function code
request[1] = (byte)ModbusFunctionCode.WriteSingleRegister;
- byte[] addrBytes = register.Address.ToBigEndianBytes();
+ var addrBytes = register.Address.ToBigEndianBytes();
request[2] = addrBytes[0];
request[3] = addrBytes[1];
@@ -495,11 +495,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[1] = (byte)ModbusFunctionCode.WriteMultipleCoils;
- byte[] addrBytes = firstAddress.ToBigEndianBytes();
+ var addrBytes = firstAddress.ToBigEndianBytes();
request[2] = addrBytes[0];
request[3] = addrBytes[1];
- byte[] countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
+ var countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
request[4] = countBytes[0];
request[5] = countBytes[1];
@@ -565,11 +565,11 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[0] = unitId;
request[1] = (byte)ModbusFunctionCode.WriteMultipleRegisters;
- byte[] addrBytes = firstAddress.ToBigEndianBytes();
+ var addrBytes = firstAddress.ToBigEndianBytes();
request[2] = addrBytes[0];
request[3] = addrBytes[1];
- byte[] countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
+ var countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
request[4] = countBytes[0];
request[5] = countBytes[1];
diff --git a/AMWD.Protocols.Modbus.Common/Protocols/TcpProtocol.cs b/AMWD.Protocols.Modbus.Common/Protocols/TcpProtocol.cs
index 4cd1d5c..21f142d 100644
--- a/AMWD.Protocols.Modbus.Common/Protocols/TcpProtocol.cs
+++ b/AMWD.Protocols.Modbus.Common/Protocols/TcpProtocol.cs
@@ -101,12 +101,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadCoils;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -159,12 +159,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadDiscreteInputs;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -217,12 +217,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -272,12 +272,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.ReadInputRegisters;
// Starting address
- byte[] addrBytes = startAddress.ToBigEndianBytes();
+ var addrBytes = startAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = count.ToBigEndianBytes();
+ var countBytes = count.ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -389,7 +389,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Function code
request[7] = (byte)ModbusFunctionCode.WriteSingleCoil;
- byte[] addrBytes = coil.Address.ToBigEndianBytes();
+ var addrBytes = coil.Address.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
@@ -431,7 +431,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Function code
request[7] = (byte)ModbusFunctionCode.WriteSingleRegister;
- byte[] addrBytes = register.Address.ToBigEndianBytes();
+ var addrBytes = register.Address.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
@@ -489,12 +489,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.WriteMultipleCoils;
// Starting address
- byte[] addrBytes = firstAddress.ToBigEndianBytes();
+ var addrBytes = firstAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
+ var countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -564,12 +564,12 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
request[7] = (byte)ModbusFunctionCode.WriteMultipleRegisters;
// Starting address
- byte[] addrBytes = firstAddress.ToBigEndianBytes();
+ var addrBytes = firstAddress.ToBigEndianBytes();
request[8] = addrBytes[0];
request[9] = addrBytes[1];
// Quantity
- byte[] countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
+ var countBytes = ((ushort)orderedList.Count).ToBigEndianBytes();
request[10] = countBytes[0];
request[11] = countBytes[1];
@@ -678,7 +678,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
// Transaction id
ushort txId = GetNextTransacitonId();
- byte[] txBytes = txId.ToBigEndianBytes();
+ var txBytes = txId.ToBigEndianBytes();
header[0] = txBytes[0];
header[1] = txBytes[1];
@@ -687,7 +687,7 @@ namespace AMWD.Protocols.Modbus.Common.Protocols
header[3] = 0x00;
// Number of following bytes
- byte[] countBytes = ((ushort)followingBytes).ToBigEndianBytes();
+ var countBytes = ((ushort)followingBytes).ToBigEndianBytes();
header[4] = countBytes[0];
header[5] = countBytes[1];
diff --git a/AMWD.Protocols.Modbus.Common/Protocols/VirtualProtocol.cs b/AMWD.Protocols.Modbus.Common/Protocols/VirtualProtocol.cs
new file mode 100644
index 0000000..3d0ef6d
--- /dev/null
+++ b/AMWD.Protocols.Modbus.Common/Protocols/VirtualProtocol.cs
@@ -0,0 +1,478 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Protocols.Modbus.Common.Contracts;
+using AMWD.Protocols.Modbus.Common.Events;
+using AMWD.Protocols.Modbus.Common.Models;
+
+namespace AMWD.Protocols.Modbus.Common.Protocols
+{
+ [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ internal class VirtualProtocol : IModbusProtocol, IDisposable
+ {
+ #region Fields
+
+ private bool _isDisposed;
+
+ private readonly ReaderWriterLockSlim _deviceListLock = new();
+ private readonly Dictionary _devices = [];
+
+ #endregion Fields
+
+ public void Dispose()
+ {
+ if (_isDisposed)
+ return;
+
+ _isDisposed = true;
+
+ _deviceListLock.Dispose();
+
+ foreach (var device in _devices.Values)
+ device.Dispose();
+
+ _devices.Clear();
+ }
+
+ #region Events
+
+ public event EventHandler CoilWritten;
+
+ public event EventHandler RegisterWritten;
+
+ #endregion Events
+
+ #region Properties
+
+ public string Name => nameof(VirtualProtocol);
+
+ #endregion Properties
+
+ #region Protocol
+
+ public bool CheckResponseComplete(IReadOnlyList responseBytes) => true;
+
+ public IReadOnlyList DeserializeReadCoils(IReadOnlyList response)
+ {
+ if (!_devices.TryGetValue(response[0], out var device))
+ throw new TimeoutException("Device not found.");
+
+ ushort start = response.GetBigEndianUInt16(1);
+ ushort count = response.GetBigEndianUInt16(3);
+
+ return Enumerable.Range(0, count)
+ .Select(i => device.GetCoil((ushort)(start + i)))
+ .ToList();
+ }
+
+ public DeviceIdentificationRaw DeserializeReadDeviceIdentification(IReadOnlyList response)
+ {
+ if (!_devices.TryGetValue(response[0], out var device))
+ throw new TimeoutException("Device not found.");
+
+ var result = new DeviceIdentificationRaw
+ {
+ AllowsIndividualAccess = false,
+ MoreRequestsNeeded = false,
+ Objects = []
+ };
+
+ if (response[1] >= 1)
+ {
+ string version = GetType().Assembly
+ .GetCustomAttribute()
+ .InformationalVersion;
+
+ result.Objects.Add(0, Encoding.UTF8.GetBytes("AM.WD"));
+ result.Objects.Add(1, Encoding.UTF8.GetBytes("AMWD.Protocols.Modbus"));
+ result.Objects.Add(2, Encoding.UTF8.GetBytes(version));
+ }
+
+ if (response[1] >= 2)
+ {
+ result.Objects.Add(3, Encoding.UTF8.GetBytes("https://github.com/AM-WD/AMWD.Protocols.Modbus"));
+ result.Objects.Add(4, Encoding.UTF8.GetBytes("Modbus Protocol for .NET"));
+ result.Objects.Add(5, Encoding.UTF8.GetBytes("Virtual Device"));
+ result.Objects.Add(6, Encoding.UTF8.GetBytes("Virtual Modbus Client"));
+ }
+
+ if (response[1] >= 3)
+ {
+ for (int i = 128; i < 256; i++)
+ result.Objects.Add((byte)i, []);
+ }
+
+ return result;
+ }
+
+ public IReadOnlyList DeserializeReadDiscreteInputs(IReadOnlyList response)
+ {
+ if (!_devices.TryGetValue(response[0], out var device))
+ throw new TimeoutException("Device not found.");
+
+ ushort start = response.GetBigEndianUInt16(1);
+ ushort count = response.GetBigEndianUInt16(3);
+
+ return Enumerable.Range(0, count)
+ .Select(i => device.GetDiscreteInput((ushort)(start + i)))
+ .ToList();
+ }
+
+ public IReadOnlyList DeserializeReadHoldingRegisters(IReadOnlyList response)
+ {
+ if (!_devices.TryGetValue(response[0], out var device))
+ throw new TimeoutException("Device not found.");
+
+ ushort start = response.GetBigEndianUInt16(1);
+ ushort count = response.GetBigEndianUInt16(3);
+
+ return Enumerable.Range(0, count)
+ .Select(i => device.GetHoldingRegister((ushort)(start + i)))
+ .ToList();
+ }
+
+ public IReadOnlyList DeserializeReadInputRegisters(IReadOnlyList response)
+ {
+ if (!_devices.TryGetValue(response[0], out var device))
+ throw new TimeoutException("Device not found.");
+
+ ushort start = response.GetBigEndianUInt16(1);
+ ushort count = response.GetBigEndianUInt16(3);
+
+ return Enumerable.Range(0, count)
+ .Select(i => device.GetInputRegister((ushort)(start + i)))
+ .ToList();
+ }
+
+ public (ushort FirstAddress, ushort NumberOfCoils) DeserializeWriteMultipleCoils(IReadOnlyList response)
+ {
+ if (!_devices.TryGetValue(response[0], out var device))
+ throw new TimeoutException("Device not found.");
+
+ ushort start = response.GetBigEndianUInt16(1);
+ ushort count = response.GetBigEndianUInt16(3);
+
+ for (int i = 0; i < count; i++)
+ {
+ var coil = new Coil
+ {
+ Address = (ushort)(start + i),
+ HighByte = response[5 + i]
+ };
+ device.SetCoil(coil);
+
+ Task.Run(() =>
+ {
+ try
+ {
+ CoilWritten?.Invoke(this, new CoilWrittenEventArgs(
+ unitId: response[0],
+ address: coil.Address,
+ value: coil.Value));
+ }
+ catch
+ {
+ // ignore
+ }
+ });
+ }
+
+ return (start, count);
+ }
+
+ public (ushort FirstAddress, ushort NumberOfRegisters) DeserializeWriteMultipleHoldingRegisters(IReadOnlyList response)
+ {
+ if (!_devices.TryGetValue(response[0], out var device))
+ throw new TimeoutException("Device not found.");
+
+ ushort start = response.GetBigEndianUInt16(1);
+ ushort count = response.GetBigEndianUInt16(3);
+
+ for (int i = 0; i < count; i++)
+ {
+ var register = new HoldingRegister
+ {
+ Address = (ushort)(start + i),
+ HighByte = response[5 + i * 2],
+ LowByte = response[5 + i * 2 + 1]
+ };
+ device.SetHoldingRegister(register);
+
+ Task.Run(() =>
+ {
+ try
+ {
+ RegisterWritten?.Invoke(this, new RegisterWrittenEventArgs(
+ unitId: response[0],
+ address: register.Address,
+ highByte: register.HighByte,
+ lowByte: register.LowByte));
+ }
+ catch
+ {
+ // ignore
+ }
+ });
+ }
+
+ return (start, count);
+ }
+
+ public Coil DeserializeWriteSingleCoil(IReadOnlyList response)
+ {
+ if (!_devices.TryGetValue(response[0], out var device))
+ throw new TimeoutException("Device not found.");
+
+ var coil = new Coil
+ {
+ Address = response.GetBigEndianUInt16(1),
+ HighByte = response[3]
+ };
+ device.SetCoil(coil);
+
+ Task.Run(() =>
+ {
+ try
+ {
+ CoilWritten?.Invoke(this, new CoilWrittenEventArgs(
+ unitId: response[0],
+ address: coil.Address,
+ value: coil.Value));
+ }
+ catch
+ {
+ // ignore
+ }
+ });
+
+ return coil;
+ }
+
+ public HoldingRegister DeserializeWriteSingleHoldingRegister(IReadOnlyList response)
+ {
+ if (!_devices.TryGetValue(response[0], out var device))
+ throw new TimeoutException("Device not found.");
+
+ var register = new HoldingRegister
+ {
+ Address = response.GetBigEndianUInt16(1),
+ HighByte = response[3],
+ LowByte = response[4]
+ };
+ device.SetHoldingRegister(register);
+
+ Task.Run(() =>
+ {
+ try
+ {
+ RegisterWritten?.Invoke(this, new RegisterWrittenEventArgs(
+ unitId: response[0],
+ address: register.Address,
+ highByte: register.HighByte,
+ lowByte: register.LowByte));
+ }
+ catch
+ {
+ // ignore
+ }
+ });
+
+ return register;
+ }
+
+ public IReadOnlyList SerializeReadCoils(byte unitId, ushort startAddress, ushort count)
+ {
+ return [unitId, .. startAddress.ToBigEndianBytes(), .. count.ToBigEndianBytes()];
+ }
+
+ public IReadOnlyList SerializeReadDeviceIdentification(byte unitId, ModbusDeviceIdentificationCategory category, ModbusDeviceIdentificationObject objectId)
+ {
+ if (!Enum.IsDefined(typeof(ModbusDeviceIdentificationCategory), category))
+ throw new ArgumentOutOfRangeException(nameof(category));
+
+ return [unitId, (byte)category];
+ }
+
+ public IReadOnlyList SerializeReadDiscreteInputs(byte unitId, ushort startAddress, ushort count)
+ {
+ return [unitId, .. startAddress.ToBigEndianBytes(), .. count.ToBigEndianBytes()];
+ }
+
+ public IReadOnlyList SerializeReadHoldingRegisters(byte unitId, ushort startAddress, ushort count)
+ {
+ return [unitId, .. startAddress.ToBigEndianBytes(), .. count.ToBigEndianBytes()];
+ }
+
+ public IReadOnlyList SerializeReadInputRegisters(byte unitId, ushort startAddress, ushort count)
+ {
+ return [unitId, .. startAddress.ToBigEndianBytes(), .. count.ToBigEndianBytes()];
+ }
+
+ public IReadOnlyList SerializeWriteMultipleCoils(byte unitId, IReadOnlyList coils)
+ {
+ ushort start = coils.OrderBy(c => c.Address).First().Address;
+ ushort count = (ushort)coils.Count;
+ byte[] values = coils.Select(c => c.HighByte).ToArray();
+
+ return [unitId, .. start.ToBigEndianBytes(), .. count.ToBigEndianBytes(), .. values];
+ }
+
+ public IReadOnlyList SerializeWriteMultipleHoldingRegisters(byte unitId, IReadOnlyList registers)
+ {
+ ushort start = registers.OrderBy(c => c.Address).First().Address;
+ ushort count = (ushort)registers.Count;
+ byte[] values = registers.SelectMany(r => new[] { r.HighByte, r.LowByte }).ToArray();
+
+ return [unitId, .. start.ToBigEndianBytes(), .. count.ToBigEndianBytes(), .. values];
+ }
+
+ public IReadOnlyList SerializeWriteSingleCoil(byte unitId, Coil coil)
+ {
+ return [unitId, .. coil.Address.ToBigEndianBytes(), coil.HighByte];
+ }
+
+ public IReadOnlyList SerializeWriteSingleHoldingRegister(byte unitId, HoldingRegister register)
+ {
+ return [unitId, .. register.Address.ToBigEndianBytes(), register.HighByte, register.LowByte];
+ }
+
+ public void ValidateResponse(IReadOnlyList request, IReadOnlyList response)
+ {
+ if (!request.SequenceEqual(response))
+ throw new InvalidOperationException("Request and response have to be the same on virtual protocol.");
+ }
+
+ #endregion Protocol
+
+ #region Device Handling
+
+ public bool AddDevice(byte unitId)
+ {
+ Assertions();
+ using (_deviceListLock.GetWriteLock())
+ {
+ if (_devices.ContainsKey(unitId))
+ return false;
+
+ _devices.Add(unitId, new ModbusDevice(unitId));
+ return true;
+ }
+ }
+
+ public bool RemoveDevice(byte unitId)
+ {
+ Assertions();
+ using (_deviceListLock.GetWriteLock())
+ {
+ if (_devices.ContainsKey(unitId))
+ return false;
+
+ return _devices.Remove(unitId);
+ }
+ }
+
+ #endregion Device Handling
+
+ #region Entity Handling
+
+ public Coil GetCoil(byte unitId, ushort address)
+ {
+ Assertions();
+ using (_deviceListLock.GetReadLock())
+ {
+ return _devices.TryGetValue(unitId, out var device)
+ ? device.GetCoil(address)
+ : null;
+ }
+ }
+
+ public void SetCoil(byte unitId, Coil coil)
+ {
+ Assertions();
+ using (_deviceListLock.GetWriteLock())
+ {
+ if (_devices.TryGetValue(unitId, out var device))
+ device.SetCoil(coil);
+ }
+ }
+
+ public DiscreteInput GetDiscreteInput(byte unitId, ushort address)
+ {
+ Assertions();
+ using (_deviceListLock.GetReadLock())
+ {
+ return _devices.TryGetValue(unitId, out var device)
+ ? device.GetDiscreteInput(address)
+ : null;
+ }
+ }
+
+ public void SetDiscreteInput(byte unitId, DiscreteInput discreteInput)
+ {
+ Assertions();
+ using (_deviceListLock.GetWriteLock())
+ {
+ if (_devices.TryGetValue(unitId, out var device))
+ device.SetDiscreteInput(discreteInput);
+ }
+ }
+
+ public HoldingRegister GetHoldingRegister(byte unitId, ushort address)
+ {
+ Assertions();
+ using (_deviceListLock.GetReadLock())
+ {
+ return _devices.TryGetValue(unitId, out var device)
+ ? device.GetHoldingRegister(address)
+ : null;
+ }
+ }
+
+ public void SetHoldingRegister(byte unitId, HoldingRegister holdingRegister)
+ {
+ Assertions();
+ using (_deviceListLock.GetWriteLock())
+ {
+ if (_devices.TryGetValue(unitId, out var device))
+ device.SetHoldingRegister(holdingRegister);
+ }
+ }
+
+ public InputRegister GetInputRegister(byte unitId, ushort address)
+ {
+ Assertions();
+ using (_deviceListLock.GetReadLock())
+ {
+ return _devices.TryGetValue(unitId, out var device)
+ ? device.GetInputRegister(address)
+ : null;
+ }
+ }
+
+ public void SetInputRegister(byte unitId, InputRegister inputRegister)
+ {
+ Assertions();
+ using (_deviceListLock.GetWriteLock())
+ {
+ if (_devices.TryGetValue(unitId, out var device))
+ device.SetInputRegister(inputRegister);
+ }
+ }
+
+ #endregion Entity Handling
+
+ private void Assertions()
+ {
+#if NET8_0_OR_GREATER
+ ObjectDisposedException.ThrowIf(_isDisposed, this);
+#else
+ if (_isDisposed)
+ throw new ObjectDisposedException(GetType().Name);
+#endif
+ }
+ }
+}
diff --git a/AMWD.Protocols.Modbus.Common/README.md b/AMWD.Protocols.Modbus.Common/README.md
index 43326f1..97c462b 100644
--- a/AMWD.Protocols.Modbus.Common/README.md
+++ b/AMWD.Protocols.Modbus.Common/README.md
@@ -50,7 +50,8 @@ The different types handled by the Modbus Protocol.
In addition, you'll find the `DeviceIdentification` there.
It is used for a "special" function called _Read Device Identification_ (0x2B / 43), not supported on all devices.
-The `ModbusDevice` is used for the server implementations in the derived packages.
+The `ModbusDevice` is used for the `VirtualModbusClient`.
+In combination with the *Proxy implementations (in the derived packages) it can be used as server.
### Protocols
diff --git a/AMWD.Protocols.Modbus.Common/Utils/VirtualModbusClient.cs b/AMWD.Protocols.Modbus.Common/Utils/VirtualModbusClient.cs
new file mode 100644
index 0000000..26dd52f
--- /dev/null
+++ b/AMWD.Protocols.Modbus.Common/Utils/VirtualModbusClient.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Threading;
+using AMWD.Protocols.Modbus.Common.Contracts;
+using AMWD.Protocols.Modbus.Common.Events;
+using AMWD.Protocols.Modbus.Common.Models;
+using AMWD.Protocols.Modbus.Common.Protocols;
+
+namespace AMWD.Protocols.Modbus.Common.Utils
+{
+ ///
+ /// Implements a virtual Modbus client.
+ ///
+ [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public class VirtualModbusClient : ModbusClientBase
+ {
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// DO NOT MODIFY connection or protocol.
+ public VirtualModbusClient()
+ : base(new VirtualConnection())
+ {
+ Protocol = new VirtualProtocol();
+
+ TypedProtocol.CoilWritten += (sender, e) => CoilWritten?.Invoke(this, e);
+ TypedProtocol.RegisterWritten += (sender, e) => RegisterWritten?.Invoke(this, e);
+ }
+
+ #endregion Constructor
+
+ #region Events
+
+ ///
+ /// Indicates that a -value received through a remote client has been written.
+ ///
+ public event EventHandler CoilWritten;
+
+ ///
+ /// Indicates that a -value received from a remote client has been written.
+ ///
+ public event EventHandler RegisterWritten;
+
+ #endregion Events
+
+ #region Properties
+
+ internal VirtualProtocol TypedProtocol => Protocol as VirtualProtocol;
+
+ #endregion Properties
+
+ #region Device Handling
+
+ ///
+ /// Adds a device to the virtual client.
+ ///
+ /// The unit id of the device.
+ /// if the device was added successfully, otherwise.
+ public bool AddDevice(byte unitId)
+ => TypedProtocol.AddDevice(unitId);
+
+ ///
+ /// Removes a device from the virtual client.
+ ///
+ /// The unit id of the device.
+ /// if the device was removed successfully, otherwise.
+ public bool RemoveDevice(byte unitId)
+ => TypedProtocol.RemoveDevice(unitId);
+
+ #endregion Device Handling
+
+ #region Entity Handling
+
+ ///
+ /// Gets a from the specified .
+ ///
+ /// The unit ID of the device.
+ /// The address of the .
+ public Coil GetCoil(byte unitId, ushort address)
+ => TypedProtocol.GetCoil(unitId, address);
+
+ ///
+ /// Sets a to the specified .
+ ///
+ /// The unit ID of the device.
+ /// The to set.
+ public void SetCoil(byte unitId, Coil coil)
+ => TypedProtocol.SetCoil(unitId, coil);
+
+ ///
+ /// Gets a from the specified .
+ ///
+ /// The unit ID of the device.
+ /// The address of the .
+ public DiscreteInput GetDiscreteInput(byte unitId, ushort address)
+ => TypedProtocol.GetDiscreteInput(unitId, address);
+
+ ///
+ /// Sets a to the specified .
+ ///
+ /// The unit ID of the device.
+ /// The to set.
+ public void SetDiscreteInput(byte unitId, DiscreteInput discreteInput)
+ => TypedProtocol.SetDiscreteInput(unitId, discreteInput);
+
+ ///
+ /// Gets a from the specified .
+ ///
+ /// The unit ID of the device.
+ /// The address of the .
+ public HoldingRegister GetHoldingRegister(byte unitId, ushort address)
+ => TypedProtocol.GetHoldingRegister(unitId, address);
+
+ ///
+ /// Sets a to the specified .
+ ///
+ /// The unit ID of the device.
+ /// The to set.
+ public void SetHoldingRegister(byte unitId, HoldingRegister holdingRegister)
+ => TypedProtocol.SetHoldingRegister(unitId, holdingRegister);
+
+ ///
+ /// Gets a from the specified .
+ ///
+ /// The unit ID of the device.
+ /// The address of the .
+ public InputRegister GetInputRegister(byte unitId, ushort address)
+ => TypedProtocol.GetInputRegister(unitId, address);
+
+ ///
+ /// Sets a to the specified .
+ ///
+ /// The unit ID of the device.
+ /// The to set.
+ public void SetInputRegister(byte unitId, InputRegister inputRegister)
+ => TypedProtocol.SetInputRegister(unitId, inputRegister);
+
+ #endregion Entity Handling
+
+ #region Methods
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ TypedProtocol.Dispose();
+ base.Dispose(disposing);
+ }
+
+ #endregion Methods
+
+ #region Connection
+
+ internal class VirtualConnection : IModbusConnection
+ {
+ public string Name => nameof(VirtualConnection);
+
+ public TimeSpan IdleTimeout { get; set; }
+
+ public TimeSpan ConnectTimeout { get; set; }
+
+ public TimeSpan ReadTimeout { get; set; }
+
+ public TimeSpan WriteTimeout { get; set; }
+
+ public void Dispose()
+ { /* nothing to do */ }
+
+ public Task> InvokeAsync(
+ IReadOnlyList request,
+ Func, bool> validateResponseComplete,
+ CancellationToken cancellationToken = default) => Task.FromResult(request);
+ }
+
+ #endregion Connection
+ }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6201a72..3ffa58f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Small CLI tool to test Modbus client communication.
+- `VirtualModbusClient` added to `AMWD.Protocols.Modbus.Common`.
### Changed