From ce3d873cd0db70c6a5978803df9f9aacfc8c1d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=BCller?= Date: Thu, 23 Jan 2025 21:59:04 +0100 Subject: [PATCH] Moving proxies to matching projects --- .../AMWD.Protocols.Modbus.Proxy.csproj | 41 ---- AMWD.Protocols.Modbus.Proxy/README.md | 14 -- .../ModbusRtuProxy.cs | 207 +++++++++--------- .../ModbusTcpProxy.cs | 4 +- AMWD.Protocols.Modbus.sln | 8 +- CHANGELOG.md | 6 + 6 files changed, 112 insertions(+), 168 deletions(-) delete mode 100644 AMWD.Protocols.Modbus.Proxy/AMWD.Protocols.Modbus.Proxy.csproj delete mode 100644 AMWD.Protocols.Modbus.Proxy/README.md rename {AMWD.Protocols.Modbus.Proxy => AMWD.Protocols.Modbus.Serial}/ModbusRtuProxy.cs (83%) rename {AMWD.Protocols.Modbus.Proxy => AMWD.Protocols.Modbus.Tcp}/ModbusTcpProxy.cs (96%) 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