From 39863880d5adc7519e1622aca4eae0cd04528c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=BCller?= Date: Tue, 21 Jan 2025 19:27:29 +0100 Subject: [PATCH] Added new cli tool for client connections --- .../Models/DeviceIdentification.cs | 19 + .../ModbusSerialClient.cs | 18 + AMWD.Protocols.Modbus.Tcp/ModbusTcpClient.cs | 12 + AMWD.Protocols.Modbus.sln | 6 + CHANGELOG.md | 4 + CliClient/Cli/Argument.cs | 35 + CliClient/Cli/CommandLineParser.cs | 366 ++++++++++ CliClient/Cli/EnumerableWalker.cs | 53 ++ CliClient/Cli/Option.cs | 112 ++++ CliClient/CliClient.csproj | 31 + CliClient/Program.cs | 628 ++++++++++++++++++ CliClient/Properties/launchSettings.json | 9 + 12 files changed, 1293 insertions(+) create mode 100644 CliClient/Cli/Argument.cs create mode 100644 CliClient/Cli/CommandLineParser.cs create mode 100644 CliClient/Cli/EnumerableWalker.cs create mode 100644 CliClient/Cli/Option.cs create mode 100644 CliClient/CliClient.csproj create mode 100644 CliClient/Program.cs create mode 100644 CliClient/Properties/launchSettings.json diff --git a/AMWD.Protocols.Modbus.Common/Models/DeviceIdentification.cs b/AMWD.Protocols.Modbus.Common/Models/DeviceIdentification.cs index 64ee898..30387ec 100644 --- a/AMWD.Protocols.Modbus.Common/Models/DeviceIdentification.cs +++ b/AMWD.Protocols.Modbus.Common/Models/DeviceIdentification.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Text; namespace AMWD.Protocols.Modbus.Common { @@ -91,5 +92,23 @@ namespace AMWD.Protocols.Modbus.Common /// Gets or sets a value indicating whether individual access () is allowed. /// public bool IsIndividualAccessAllowed { get; set; } + + /// + public override string ToString() + { + var sb = new StringBuilder(); + + sb.AppendLine(nameof(DeviceIdentification)); + sb.AppendLine($" {nameof(VendorName)}: {VendorName}"); + sb.AppendLine($" {nameof(ProductCode)}: {ProductCode}"); + sb.AppendLine($" {nameof(MajorMinorRevision)}: {MajorMinorRevision}"); + sb.AppendLine($" {nameof(VendorUrl)}: {VendorUrl}"); + sb.AppendLine($" {nameof(ProductName)}: {ProductName}"); + sb.AppendLine($" {nameof(ModelName)}: {ModelName}"); + sb.AppendLine($" {nameof(UserApplicationName)}: {UserApplicationName}"); + sb.AppendLine($" {nameof(IsIndividualAccessAllowed)}: {IsIndividualAccessAllowed}"); + + return sb.ToString(); + } } } diff --git a/AMWD.Protocols.Modbus.Serial/ModbusSerialClient.cs b/AMWD.Protocols.Modbus.Serial/ModbusSerialClient.cs index 506e3ad..d0251de 100644 --- a/AMWD.Protocols.Modbus.Serial/ModbusSerialClient.cs +++ b/AMWD.Protocols.Modbus.Serial/ModbusSerialClient.cs @@ -1,5 +1,6 @@ using System; using System.IO.Ports; +using System.Text; using AMWD.Protocols.Modbus.Common.Contracts; using AMWD.Protocols.Modbus.Common.Protocols; @@ -223,5 +224,22 @@ namespace AMWD.Protocols.Modbus.Serial serialConnection.StopBits = value; } } + + /// + public override string ToString() + { + var sb = new StringBuilder(); + + sb.AppendLine($"Serial Client {PortName}"); + sb.AppendLine($" {nameof(BaudRate)}: {(int)BaudRate}"); + sb.AppendLine($" {nameof(DataBits)}: {DataBits}"); + sb.AppendLine($" {nameof(StopBits)}: {(StopBits == StopBits.OnePointFive ? "1.5" : ((int)StopBits).ToString())}"); + sb.AppendLine($" {nameof(Parity)}: {Parity.ToString().ToLower()}"); + sb.AppendLine($" {nameof(Handshake)}: {Handshake.ToString().ToLower()}"); + sb.AppendLine($" {nameof(RtsEnable)}: {RtsEnable.ToString().ToLower()}"); + sb.AppendLine($" {nameof(DriverEnabledRS485)}: {DriverEnabledRS485.ToString().ToLower()}"); + + return sb.ToString(); + } } } diff --git a/AMWD.Protocols.Modbus.Tcp/ModbusTcpClient.cs b/AMWD.Protocols.Modbus.Tcp/ModbusTcpClient.cs index a73ad6f..7f2c322 100644 --- a/AMWD.Protocols.Modbus.Tcp/ModbusTcpClient.cs +++ b/AMWD.Protocols.Modbus.Tcp/ModbusTcpClient.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using AMWD.Protocols.Modbus.Common.Contracts; using AMWD.Protocols.Modbus.Common.Protocols; @@ -101,5 +102,16 @@ namespace AMWD.Protocols.Modbus.Tcp tcpConnection.Port = value; } } + + /// + public override string ToString() + { + var sb = new StringBuilder(); + + sb.AppendLine($"TCP Client {Hostname}"); + sb.AppendLine($" {nameof(Port)}: {Port}"); + + return sb.ToString(); + } } } diff --git a/AMWD.Protocols.Modbus.sln b/AMWD.Protocols.Modbus.sln index f1cf8e3..640e790 100644 --- a/AMWD.Protocols.Modbus.sln +++ b/AMWD.Protocols.Modbus.sln @@ -37,6 +37,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Protocols.Modbus.Seria 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}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +65,10 @@ Global {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 + {B0E53462-B0ED-4685-8AA5-948DC160EE27}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CHANGELOG.md b/CHANGELOG.md index ede9233..16ec5c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Small CLI tool to test Modbus client communication. + ### Changed - The `ModbusTcpProxy.ReadWriteTimeout` has a default value of 100 seconds (same default as a `HttpClient` has). diff --git a/CliClient/Cli/Argument.cs b/CliClient/Cli/Argument.cs new file mode 100644 index 0000000..5e4a76a --- /dev/null +++ b/CliClient/Cli/Argument.cs @@ -0,0 +1,35 @@ +namespace AMWD.Common.Cli +{ + /// + /// Represents a logical argument in the command line. Options with their additional + /// parameters are combined in one argument. + /// + internal class Argument + { + /// + /// Initialises a new instance of the class. + /// + /// The that is set in this argument; or null. + /// The additional parameter values for the option; or the argument value. + internal Argument(Option option, string[] values) + { + Option = option; + Values = values; + } + + /// + /// Gets the that is set in this argument; or null. + /// + public Option Option { get; private set; } + + /// + /// Gets the additional parameter values for the option; or the argument value. + /// + public string[] Values { get; private set; } + + /// + /// Gets the first item of ; or null. + /// + public string Value => Values.Length > 0 ? Values[0] : null; + } +} diff --git a/CliClient/Cli/CommandLineParser.cs b/CliClient/Cli/CommandLineParser.cs new file mode 100644 index 0000000..0577971 --- /dev/null +++ b/CliClient/Cli/CommandLineParser.cs @@ -0,0 +1,366 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AMWD.Common.Cli +{ + /// + /// Provides options and arguments parsing from command line arguments or a single string. + /// + internal class CommandLineParser + { + #region Private data + + private string[] _args; + private List _parsedArguments; + private readonly List