using System; using System.Diagnostics; using System.IO.Ports; using System.Net; using System.Threading; using System.Threading.Tasks; using AMWD.Common.Cli; using AMWD.Protocols.Modbus.Common.Contracts; using AMWD.Protocols.Modbus.Common.Protocols; using AMWD.Protocols.Modbus.Serial; using AMWD.Protocols.Modbus.Tcp; namespace AMWD.Protocols.Modbus.CliProxy { internal class Program { #region General options private static Option _helpOption; private static Option _debugOption; private static Option _serverOption; private static Option _clientOption; private static Option _clientProtocolOption; #endregion General options #region Server options private static Option _serverSerialBaudOption; private static Option _serverSerialDataBitsOption; private static Option _serverSerialStopBitsOption; private static Option _serverSerialParityOption; private static Option _serverSerialDeviceOption; private static Option _serverTcpHostOption; private static Option _serverTcpPortOption; #endregion Server options #region Client options private static Option _clientSerialBaudOption; private static Option _clientSerialDataBitsOption; private static Option _clientSerialStopBitsOption; private static Option _clientSerialParityOption; private static Option _clientSerialDeviceOption; private static Option _clientSerialSoftEnableOption; private static Option _clientTcpHostOption; private static Option _clientTcpPortOption; #endregion Client options private static async Task Main(string[] args) { if (!ParseArguments(args)) { Console.WriteLine("Could not parse arguments."); return 1; } if (_helpOption.IsSet) { PrintHelp(); return 0; } using var cts = new CancellationTokenSource(); Console.CancelKeyPress += (_, e) => { cts.Cancel(); e.Cancel = true; }; if (_debugOption.IsSet) { Console.Error.Write("Waiting for debugger "); while (!Debugger.IsAttached) { try { Console.Write("."); await Task.Delay(1000, cts.Token); } catch (OperationCanceledException) { return 0; } } Console.WriteLine(); } try { using var client = CreateClient(); if (_clientProtocolOption.IsSet) { switch (_clientProtocolOption.Value.ToLower()) { case "ascii": client.Protocol = new AsciiProtocol(); break; case "rtu": client.Protocol = new RtuProtocol(); break; case "tcp": client.Protocol = new TcpProtocol(); break; } } using var proxy = CreateProxy(client); await proxy.StartAsync(cts.Token); try { await Task.Delay(Timeout.Infinite, cts.Token); } finally { await proxy.StopAsync(); } return 0; } catch (OperationCanceledException) when (cts.IsCancellationRequested) { return 0; } catch (Exception ex) { Console.Error.WriteLine($"{ex.GetType().Name}: {ex.Message}"); return 1; } } private static bool ParseArguments(string[] args) { var cmdLine = new CommandLineParser(); #region General options _helpOption = cmdLine.RegisterOption("help").Alias("h"); _debugOption = cmdLine.RegisterOption("debug"); _serverOption = cmdLine.RegisterOption("server", 1); // TCP | RTU _clientOption = cmdLine.RegisterOption("client", 1); // TCP | RTU _clientProtocolOption = cmdLine.RegisterOption("client-protocol", 1); #endregion General options #region Server options _serverSerialBaudOption = cmdLine.RegisterOption("server-baud", 1); _serverSerialDataBitsOption = cmdLine.RegisterOption("server-databits", 1); _serverSerialDeviceOption = cmdLine.RegisterOption("server-device", 1); _serverSerialStopBitsOption = cmdLine.RegisterOption("server-stopbits", 1); _serverSerialParityOption = cmdLine.RegisterOption("server-parity", 1); _serverTcpHostOption = cmdLine.RegisterOption("server-host", 1); _serverTcpPortOption = cmdLine.RegisterOption("server-port", 1); #endregion Server options #region Client options _clientSerialBaudOption = cmdLine.RegisterOption("client-baud", 1); _clientSerialDataBitsOption = cmdLine.RegisterOption("client-databits", 1); _clientSerialDeviceOption = cmdLine.RegisterOption("client-device", 1); _clientSerialStopBitsOption = cmdLine.RegisterOption("client-stopbits", 1); _clientSerialParityOption = cmdLine.RegisterOption("client-parity", 1); _clientSerialSoftEnableOption = cmdLine.RegisterOption("client-enable-rs485"); _clientTcpHostOption = cmdLine.RegisterOption("client-host", 1); _clientTcpPortOption = cmdLine.RegisterOption("client-port", 1); #endregion Client options try { cmdLine.Parse(args); return true; } catch (Exception ex) { Console.Error.WriteLine(ex.Message); return false; } } private static void PrintHelp() { Console.WriteLine($"Usage: {typeof(Program).Assembly.GetName().Name} --server --client [options]"); Console.WriteLine(); Console.WriteLine("General options:"); Console.WriteLine(" --help, -h"); Console.WriteLine(" Shows this help message."); Console.WriteLine(); Console.WriteLine(" --debug"); Console.WriteLine(" Waits for a debugger to be attached before starting."); Console.WriteLine(); Console.WriteLine(); Console.WriteLine("Server options:"); Console.WriteLine(" --server "); Console.WriteLine(" Defines whether to use an RTU or an TCP proxy."); Console.WriteLine(); Console.WriteLine(" --server-baud #"); Console.WriteLine(" The baud rate (e.g. 9600) to use for the RTU proxy. Default: 19200."); Console.WriteLine(); Console.WriteLine(" --server-databits #"); Console.WriteLine(" The number of data bits. Default: 8."); Console.WriteLine(); Console.WriteLine(" --server-device "); Console.WriteLine(" The serial port to use (e.g. COM1, /dev/ttyS0)."); Console.WriteLine(); Console.WriteLine(" --server-parity "); Console.WriteLine(" The parity to use. Default: even."); Console.WriteLine(); Console.WriteLine(" --server-stopbits #"); Console.WriteLine(" The number of stop bits. Default: 1."); Console.WriteLine(); Console.WriteLine(" --server-host
"); Console.WriteLine(" The IP address to listen on. Default: 127.0.0.1."); Console.WriteLine(); Console.WriteLine(" --server-port #"); Console.WriteLine(" The port to listen on. Default: 502."); Console.WriteLine(); Console.WriteLine(); Console.WriteLine("Client options:"); Console.WriteLine(" --client "); Console.WriteLine(" Defines whether to use an RTU or an TCP client."); Console.WriteLine(); Console.WriteLine(" --client-protocol "); Console.WriteLine(" Select which Modbus protocol to use."); Console.WriteLine(); Console.WriteLine(" --client-baud #"); Console.WriteLine(" The baud rate (e.g. 9600) to use for the RTU client. Default: 19200."); Console.WriteLine(); Console.WriteLine(" --client-databits #"); Console.WriteLine(" The number of data bits. Default: 8."); Console.WriteLine(); Console.WriteLine(" --client-device "); Console.WriteLine(" The serial port to use (e.g. COM1, /dev/ttyS0)."); Console.WriteLine(); Console.WriteLine(" --client-parity "); Console.WriteLine(" The parity to use. Default: even."); Console.WriteLine(); Console.WriteLine(" --client-stopbits #"); Console.WriteLine(" The number of stop bits. Default: 1."); Console.WriteLine(); Console.WriteLine(" --client-enable-rs485"); Console.WriteLine(" Enables the RS485 software switch for serial adapters capable of RS232 and RS485."); Console.WriteLine(); Console.WriteLine(" --client-host "); Console.WriteLine(" The host to connect to."); Console.WriteLine(); Console.WriteLine(" --client-port #"); Console.WriteLine(" The port to connect to. Default: 502."); Console.WriteLine(); } private static ModbusClientBase CreateClient() { if (!_clientOption.IsSet) throw new ApplicationException("No client type specified."); BaudRate baudRate = BaudRate.Baud19200; if (_clientSerialBaudOption.IsSet && int.TryParse(_clientSerialBaudOption.Value, out int baudRateValue)) baudRate = (BaudRate)baudRateValue; int dataBits = 8; if (_clientSerialDataBitsOption.IsSet && int.TryParse(_clientSerialDataBitsOption.Value, out int dataBitsValue)) dataBits = dataBitsValue; StopBits stopBits = StopBits.One; if (_clientSerialStopBitsOption.IsSet && float.TryParse(_clientSerialStopBitsOption.Value, out float stopBitsValue)) { switch (stopBitsValue) { case 1.0f: stopBits = StopBits.One; break; case 1.5f: stopBits = StopBits.OnePointFive; break; case 2.0f: stopBits = StopBits.Two; break; } } Parity parity = Parity.Even; if (_clientSerialParityOption.IsSet) { switch (_clientSerialParityOption.Value.ToLower()) { case "none": parity = Parity.None; break; case "odd": parity = Parity.Odd; break; case "even": parity = Parity.Even; break; } } bool enableRs485 = _clientSerialSoftEnableOption.IsSet; int port = 502; if (_clientTcpPortOption.IsSet && ushort.TryParse(_clientTcpPortOption.Value, out ushort portValue)) port = portValue; return _clientOption.Value.ToLower() switch { "rtu" => new ModbusSerialClient(_clientSerialDeviceOption.Value) { BaudRate = baudRate, DataBits = dataBits, StopBits = stopBits, Parity = parity, DriverEnabledRS485 = enableRs485 }, "tcp" => new ModbusTcpClient(_clientTcpHostOption.Value) { Port = port }, _ => throw new ApplicationException($"Unknown client type: '{_clientOption.Value}'"), }; } private static IModbusProxy CreateProxy(ModbusClientBase client) { if (!_serverOption.IsSet) throw new ApplicationException("No proxy type specified."); BaudRate baudRate = BaudRate.Baud19200; if (_serverSerialBaudOption.IsSet && int.TryParse(_serverSerialBaudOption.Value, out int baudRateValue)) baudRate = (BaudRate)baudRateValue; int dataBits = 8; if (_serverSerialDataBitsOption.IsSet && int.TryParse(_serverSerialDataBitsOption.Value, out int dataBitsValue)) dataBits = dataBitsValue; StopBits stopBits = StopBits.One; if (_serverSerialStopBitsOption.IsSet && float.TryParse(_serverSerialStopBitsOption.Value, out float stopBitsValue)) { switch (stopBitsValue) { case 1.0f: stopBits = StopBits.One; break; case 1.5f: stopBits = StopBits.OnePointFive; break; case 2.0f: stopBits = StopBits.Two; break; } } Parity parity = Parity.Even; if (_serverSerialParityOption.IsSet) { switch (_serverSerialParityOption.Value.ToLower()) { case "none": parity = Parity.None; break; case "odd": parity = Parity.Odd; break; case "even": parity = Parity.Even; break; } } int port = 502; if (_serverTcpPortOption.IsSet && ushort.TryParse(_serverTcpPortOption.Value, out ushort portValue)) port = portValue; return _serverOption.Value.ToLower() switch { "rtu" => new ModbusRtuProxy(client, _serverSerialDeviceOption.Value) { BaudRate = baudRate, DataBits = dataBits, StopBits = stopBits, Parity = parity }, "tcp" => new ModbusTcpProxy(client, IPAddress.Parse(_serverTcpHostOption.Value)) { ListenPort = port }, _ => throw new ApplicationException($"Unknown client type: '{_serverOption.Value}'"), }; } } }