diff --git a/AMWD.Protocols.Modbus.Proxy/AMWD.Protocols.Modbus.Proxy.csproj b/AMWD.Protocols.Modbus.Proxy/AMWD.Protocols.Modbus.Proxy.csproj
deleted file mode 100644
index 83e40d4..0000000
--- a/AMWD.Protocols.Modbus.Proxy/AMWD.Protocols.Modbus.Proxy.csproj
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
- netstandard2.0;net6.0;net8.0
-
- AMWD.Protocols.Modbus.Proxy
- amwd-modbus-proxy
- AMWD.Protocols.Modbus.Proxy
-
- Modbus Proxy Clients
- Plugging Modbus Servers and Clients together to create Modbus Proxies.
- Modbus Protocol Proxy
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/AMWD.Protocols.Modbus.Proxy/README.md b/AMWD.Protocols.Modbus.Proxy/README.md
deleted file mode 100644
index d58c5f6..0000000
--- a/AMWD.Protocols.Modbus.Proxy/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Modbus Protocol for .NET | Proxy
-
-With this package the server and client implementations will be combined as proxy.
-
-You can use any `ModbusBaseClient` implementation as target client and plug it into the implemented `ModbusTcpProxy` or `ModbusRtuProxy`, which implement the server side.
-
-
----
-
-Published under MIT License (see [choose a license])
-
-
-
-[choose a license]: https://choosealicense.com/licenses/mit/
diff --git a/AMWD.Protocols.Modbus.Proxy/ModbusRtuProxy.cs b/AMWD.Protocols.Modbus.Serial/ModbusRtuProxy.cs
similarity index 83%
rename from AMWD.Protocols.Modbus.Proxy/ModbusRtuProxy.cs
rename to AMWD.Protocols.Modbus.Serial/ModbusRtuProxy.cs
index e6dee55..7c62700 100644
--- a/AMWD.Protocols.Modbus.Proxy/ModbusRtuProxy.cs
+++ b/AMWD.Protocols.Modbus.Serial/ModbusRtuProxy.cs
@@ -8,9 +8,9 @@ using System.Threading.Tasks;
using AMWD.Protocols.Modbus.Common;
using AMWD.Protocols.Modbus.Common.Contracts;
using AMWD.Protocols.Modbus.Common.Protocols;
-using AMWD.Protocols.Modbus.Serial;
+using AMWD.Protocols.Modbus.Serial.Utils;
-namespace AMWD.Protocols.Modbus.Proxy
+namespace AMWD.Protocols.Modbus.Serial
{
///
/// Implements a Modbus serial line RTU server proxying all requests to a Modbus client of choice.
@@ -21,7 +21,7 @@ namespace AMWD.Protocols.Modbus.Proxy
private bool _isDisposed;
- private readonly SerialPort _serialPort;
+ private readonly SerialPortWrapper _serialPort;
private CancellationTokenSource _stopCts;
#endregion Fields
@@ -33,31 +33,25 @@ namespace AMWD.Protocols.Modbus.Proxy
///
/// The used to request the remote device, that should be proxied.
/// The name of the serial port to use.
- /// The baud rate of the serial port (Default: 19.200).
- public ModbusRtuProxy(ModbusClientBase client, string portName, BaudRate baudRate = BaudRate.Baud19200)
+ public ModbusRtuProxy(ModbusClientBase client, string portName)
{
Client = client ?? throw new ArgumentNullException(nameof(client));
if (string.IsNullOrWhiteSpace(portName))
throw new ArgumentNullException(nameof(portName));
- if (!Enum.IsDefined(typeof(BaudRate), baudRate))
- throw new ArgumentOutOfRangeException(nameof(baudRate));
-
- if (!ModbusSerialClient.AvailablePortNames.Contains(portName))
- throw new ArgumentException($"The serial port ({portName}) is not available.", nameof(portName));
-
- _serialPort = new SerialPort
+ _serialPort = new SerialPortWrapper
{
PortName = portName,
- BaudRate = (int)baudRate,
- Handshake = Handshake.None,
+
+ BaudRate = (int)BaudRate.Baud19200,
DataBits = 8,
- ReadTimeout = 1000,
- RtsEnable = false,
StopBits = StopBits.One,
+ Parity = Parity.Even,
+ Handshake = Handshake.None,
+ ReadTimeout = 1000,
WriteTimeout = 1000,
- Parity = Parity.Even
+ RtsEnable = false,
};
}
@@ -70,73 +64,101 @@ namespace AMWD.Protocols.Modbus.Proxy
///
public ModbusClientBase Client { get; }
- ///
- public string PortName => _serialPort.PortName;
+ #region SerialPort Properties
- ///
- /// Gets or sets the baud rate of the serial port.
- ///
- public BaudRate BaudRate
+ ///
+ public virtual string PortName
+ {
+ get => _serialPort.PortName;
+ set => _serialPort.PortName = value;
+ }
+
+ ///
+ public virtual BaudRate BaudRate
{
get => (BaudRate)_serialPort.BaudRate;
set => _serialPort.BaudRate = (int)value;
}
- ///
- public Handshake Handshake
- {
- get => _serialPort.Handshake;
- set => _serialPort.Handshake = value;
- }
-
- ///
- public int DataBits
+ ///
+ ///
+ /// From the Specs:
+ ///
+ /// On it can be 7 or 8.
+ ///
+ /// On it has to be 8.
+ ///
+ public virtual int DataBits
{
get => _serialPort.DataBits;
set => _serialPort.DataBits = value;
}
- ///
- public bool IsOpen => _serialPort.IsOpen;
-
- ///
- /// Gets or sets the before a time-out occurs when a read operation does not finish.
- ///
- public TimeSpan ReadTimeout
+ ///
+ public virtual Handshake Handshake
{
- get => TimeSpan.FromMilliseconds(_serialPort.ReadTimeout);
- set => _serialPort.ReadTimeout = (int)value.TotalMilliseconds;
+ get => _serialPort.Handshake;
+ set => _serialPort.Handshake = value;
}
- ///
- public bool RtsEnable
+ ///
+ ///
+ /// From the Specs:
+ ///
+ /// is recommended and therefore the default value.
+ ///
+ /// If you use , is required,
+ /// otherwise should work fine.
+ ///
+ public virtual Parity Parity
+ {
+ get => _serialPort.Parity;
+ set => _serialPort.Parity = value;
+ }
+
+ ///
+ public virtual bool RtsEnable
{
get => _serialPort.RtsEnable;
set => _serialPort.RtsEnable = value;
}
- ///
- public StopBits StopBits
+ ///
+ ///
+ /// From the Specs:
+ ///
+ /// Should be for or .
+ ///
+ /// Should be for .
+ ///
+ public virtual StopBits StopBits
{
get => _serialPort.StopBits;
set => _serialPort.StopBits = value;
}
+ ///
+ public bool IsOpen => _serialPort.IsOpen;
+
///
- /// Gets or sets the before a time-out occurs when a write operation does not finish.
+ /// Gets or sets the before a time-out occurs when a read/receive operation does not finish.
///
- public TimeSpan WriteTimeout
+ public virtual TimeSpan ReadTimeout
+ {
+ get => TimeSpan.FromMilliseconds(_serialPort.ReadTimeout);
+ set => _serialPort.ReadTimeout = (int)value.TotalMilliseconds;
+ }
+
+ ///
+ /// Gets or sets the before a time-out occurs when a write/send operation does not finish.
+ ///
+ public virtual TimeSpan WriteTimeout
{
get => TimeSpan.FromMilliseconds(_serialPort.WriteTimeout);
set => _serialPort.WriteTimeout = (int)value.TotalMilliseconds;
}
- ///
- public Parity Parity
- {
- get => _serialPort.Parity;
- set => _serialPort.Parity = value;
- }
+ #endregion SerialPort Properties
#endregion Properties
@@ -175,7 +197,7 @@ namespace AMWD.Protocols.Modbus.Proxy
private Task StopAsyncInternal(CancellationToken cancellationToken)
{
- _stopCts.Cancel();
+ _stopCts?.Cancel();
_serialPort.Close();
_serialPort.DataReceived -= OnDataReceived;
@@ -207,13 +229,16 @@ namespace AMWD.Protocols.Modbus.Proxy
if (_isDisposed)
throw new ObjectDisposedException(GetType().FullName);
#endif
+
+ if (string.IsNullOrWhiteSpace(PortName))
+ throw new ArgumentNullException(nameof(PortName), "The serial port name cannot be empty.");
}
#endregion Control Methods
#region Client Handling
- private void OnDataReceived(object _, SerialDataReceivedEventArgs evArgs)
+ private void OnDataReceived(object _, SerialDataReceivedEventArgs __)
{
try
{
@@ -282,16 +307,14 @@ namespace AMWD.Protocols.Modbus.Proxy
default: // unknown function
{
- byte[] responseBytes = new byte[5];
- Array.Copy(requestBytes, 0, responseBytes, 0, 2);
+ var responseBytes = new List();
+ responseBytes.AddRange(requestBytes.Take(2));
+ responseBytes.Add((byte)ModbusErrorCode.IllegalFunction);
// Mark as error
responseBytes[1] |= 0x80;
- responseBytes[2] = (byte)ModbusErrorCode.IllegalFunction;
-
- SetCrc(responseBytes);
- return responseBytes;
+ return ReturnResponse(responseBytes);
}
}
}
@@ -332,8 +355,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
}
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
private async Task HandleReadDiscreteInputsAsync(byte[] requestBytes, CancellationToken cancellationToken)
@@ -372,8 +394,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
}
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
private async Task HandleReadHoldingRegistersAsync(byte[] requestBytes, CancellationToken cancellationToken)
@@ -407,8 +428,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
}
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
private async Task HandleReadInputRegistersAsync(byte[] requestBytes, CancellationToken cancellationToken)
@@ -442,8 +462,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
}
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
private async Task HandleWriteSingleCoilAsync(byte[] requestBytes, CancellationToken cancellationToken)
@@ -461,8 +480,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes[1] |= 0x80;
responseBytes.Add((byte)ModbusErrorCode.IllegalDataValue);
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
try
@@ -492,8 +510,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
}
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
private async Task HandleWriteSingleRegisterAsync(byte[] requestBytes, CancellationToken cancellationToken)
@@ -532,8 +549,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
}
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
private async Task HandleWriteMultipleCoilsAsync(byte[] requestBytes, CancellationToken cancellationToken)
@@ -553,8 +569,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes[1] |= 0x80;
responseBytes.Add((byte)ModbusErrorCode.IllegalDataValue);
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
try
@@ -594,8 +609,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
}
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
private async Task HandleWriteMultipleRegistersAsync(byte[] requestBytes, CancellationToken cancellationToken)
@@ -615,8 +629,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes[1] |= 0x80;
responseBytes.Add((byte)ModbusErrorCode.IllegalDataValue);
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
try
@@ -653,8 +666,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
}
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
private async Task HandleEncapsulatedInterfaceAsync(byte[] requestBytes, CancellationToken cancellationToken)
@@ -667,8 +679,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes[1] |= 0x80;
responseBytes.Add((byte)ModbusErrorCode.IllegalFunction);
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
var firstObject = (ModbusDeviceIdentificationObject)requestBytes[4];
@@ -677,8 +688,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes[1] |= 0x80;
responseBytes.Add((byte)ModbusErrorCode.IllegalDataAddress);
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
var category = (ModbusDeviceIdentificationCategory)requestBytes[3];
@@ -687,8 +697,7 @@ namespace AMWD.Protocols.Modbus.Proxy
responseBytes[1] |= 0x80;
responseBytes.Add((byte)ModbusErrorCode.IllegalDataValue);
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
try
@@ -755,16 +764,14 @@ namespace AMWD.Protocols.Modbus.Proxy
bodyBytes[5] = numberOfObjects;
responseBytes.AddRange(bodyBytes);
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
catch
{
responseBytes[1] |= 0x80;
responseBytes.Add((byte)ModbusErrorCode.SlaveDeviceFailure);
- AddCrc(responseBytes);
- return [.. responseBytes];
+ return ReturnResponse(responseBytes);
}
}
@@ -848,18 +855,10 @@ namespace AMWD.Protocols.Modbus.Proxy
return [.. result];
}
- private static void SetCrc(byte[] bytes)
+ private static byte[] ReturnResponse(List response)
{
- byte[] crc = RtuProtocol.CRC16(bytes, 0, bytes.Length - 2);
- bytes[bytes.Length - 2] = crc[0];
- bytes[bytes.Length - 1] = crc[1];
- }
-
- private static void AddCrc(List bytes)
- {
- byte[] crc = RtuProtocol.CRC16(bytes);
- bytes.Add(crc[0]);
- bytes.Add(crc[1]);
+ response.AddRange(RtuProtocol.CRC16(response));
+ return [.. response];
}
#endregion Request Handling
diff --git a/AMWD.Protocols.Modbus.Proxy/ModbusTcpProxy.cs b/AMWD.Protocols.Modbus.Tcp/ModbusTcpProxy.cs
similarity index 96%
rename from AMWD.Protocols.Modbus.Proxy/ModbusTcpProxy.cs
rename to AMWD.Protocols.Modbus.Tcp/ModbusTcpProxy.cs
index ce1ef23..510bc31 100644
--- a/AMWD.Protocols.Modbus.Proxy/ModbusTcpProxy.cs
+++ b/AMWD.Protocols.Modbus.Tcp/ModbusTcpProxy.cs
@@ -11,7 +11,7 @@ using AMWD.Protocols.Modbus.Common;
using AMWD.Protocols.Modbus.Common.Contracts;
using AMWD.Protocols.Modbus.Common.Protocols;
-namespace AMWD.Protocols.Modbus.Proxy
+namespace AMWD.Protocols.Modbus.Tcp
{
///
/// Implements a Modbus TCP server proxying all requests to a Modbus client of choice.
@@ -875,7 +875,7 @@ namespace AMWD.Protocols.Modbus.Proxy
private static byte[] ReturnResponse(List response)
{
ushort followingBytes = (ushort)(response.Count - 6);
- byte[] bytes = followingBytes.ToBigEndianBytes();
+ var bytes = followingBytes.ToBigEndianBytes();
response[4] = bytes[0];
response[5] = bytes[1];
diff --git a/AMWD.Protocols.Modbus.sln b/AMWD.Protocols.Modbus.sln
index 640e790..f21ce82 100644
--- a/AMWD.Protocols.Modbus.sln
+++ b/AMWD.Protocols.Modbus.sln
@@ -35,9 +35,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Protocols.Modbus.Tcp",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Protocols.Modbus.Serial", "AMWD.Protocols.Modbus.Serial\AMWD.Protocols.Modbus.Serial.csproj", "{D966826F-EE6C-4BC0-9185-C2A9A50FD586}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Protocols.Modbus.Proxy", "AMWD.Protocols.Modbus.Proxy\AMWD.Protocols.Modbus.Proxy.csproj", "{C30EBE45-E3B8-4997-95DF-8F94B31C8E1A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLI Client", "CliClient\CliClient.csproj", "{B0E53462-B0ED-4685-8AA5-948DC160EE27}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CliClient", "CliClient\CliClient.csproj", "{B0E53462-B0ED-4685-8AA5-948DC160EE27}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -61,10 +59,6 @@ Global
{D966826F-EE6C-4BC0-9185-C2A9A50FD586}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D966826F-EE6C-4BC0-9185-C2A9A50FD586}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D966826F-EE6C-4BC0-9185-C2A9A50FD586}.Release|Any CPU.Build.0 = Release|Any CPU
- {C30EBE45-E3B8-4997-95DF-8F94B31C8E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C30EBE45-E3B8-4997-95DF-8F94B31C8E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C30EBE45-E3B8-4997-95DF-8F94B31C8E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C30EBE45-E3B8-4997-95DF-8F94B31C8E1A}.Release|Any CPU.Build.0 = Release|Any CPU
{B0E53462-B0ED-4685-8AA5-948DC160EE27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0E53462-B0ED-4685-8AA5-948DC160EE27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0E53462-B0ED-4685-8AA5-948DC160EE27}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16ec5c8..6201a72 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- The `ModbusTcpProxy.ReadWriteTimeout` has a default value of 100 seconds (same default as a `HttpClient` has).
+- The `ModbusRtuProxy` moved from `AMWD.Protocols.Modbus.Proxy` to `AMWD.Protocols.Modbus.Serial`.
+- The `ModbusTcpProxy` moved from `AMWD.Protocols.Modbus.Proxy` to `AMWD.Protocols.Modbus.Tcp`.
+
+### Removed
+
+- The `AMWD.Protocols.Modbus.Proxy` (introduced in [v0.3.0]) has been removed.
### Fixed