/// <summary> /// Validates and processes any command-line arguments that were passed in. Invalid arguments will halt program execution. /// </summary> /// <param name="args">Command-line parameters</param> static void ProcessArguments(string[] args) { port.name = String.Empty; // switch to lower case and remove '/', '--' and '-' from beginning of arguments - we can process correctly without them for (int i = 0; i < args.Count(); i++) { args[i] = (args[i].TrimStart('/', '-')).ToLower(); } // sort the parameters so that they get processed in order of priority (i.e. 'quiet' option gets processed before something that would normally result in console output, etc.) Array.Sort(args, new ArgumentSorter()); // iterate through command-line arguments foreach (string arg in args) { // split argument into components based on 'key:value' formatting string[] argument = arg.Split(':'); // help if (argument[0].StartsWith("h") || argument[0].StartsWith("?")) { ShowHelp(); ExitProgram(silent: true); } // list available ports else if (argument[0].StartsWith("l")) { // get a list of all available ports availablePorts = (SimplySerial.GetSerialPorts()).OrderBy(p => p.num).ToList(); if (availablePorts.Count >= 1) { Console.WriteLine("\nPORT\tVID\tPID\tDESCRIPTION"); Console.WriteLine("------------------------------------------------------------"); foreach (ComPort p in availablePorts) { Console.WriteLine("{0}\t{1}\t{2}\t{3}", p.name, p.vid, p.pid, (p.board.isCircuitPython) ? (p.board.make + " " + p.board.model) : p.description ); } Console.WriteLine(""); } else { Console.WriteLine("\nNo COM ports detected.\n"); } ExitProgram(silent: true); } // quiet (no output to console other than comes in via serial) else if (argument[0].StartsWith("q")) { SimplySerial.Quiet = true; } // the remainder of possible command-line arguments require two parameters, so let's enforce that now else if (argument.Count() < 2) { ExitProgram(("Invalid or incomplete argument <" + arg + ">\nTry 'ss.exe help' to see a list of valid arguments"), exitCode: -1); } // preliminary validate on com port, final validation occurs towards the end of this method else if (argument[0].StartsWith("c")) { string newPort = argument[1].ToUpper(); if (!argument[1].StartsWith("COM")) { newPort = "COM" + argument[1]; } port.name = newPort; autoConnect = AutoConnect.ONE; } // validate baud rate, terminate on error else if (argument[0].StartsWith("b")) { // these are the baud rates we're supporting for now string[] availableBaudRates = new string[] { "1200", "2400", "4800", "7200", "9600", "14400", "19200", "38400", "57600", "115200" }; if (availableBaudRates.Contains(argument[1])) { baud = Convert.ToInt32(argument[1]); } else { ExitProgram(("Invalid baud rate specified <" + argument[1] + ">"), exitCode: -1); } } // validate parity, terminate on error else if (argument[0].StartsWith("p")) { if (argument[1].StartsWith("e")) { parity = Parity.Even; } else if (argument[1].StartsWith("m")) { parity = Parity.Mark; } else if (argument[1].StartsWith("n")) { parity = Parity.None; } else if (argument[1].StartsWith("o")) { parity = Parity.Odd; } else if (argument[1].StartsWith("s")) { parity = Parity.Space; } else { ExitProgram(("Invalid parity specified <" + argument[1] + ">"), exitCode: -1); } } // validate databits, terminate on error else if (argument[0].StartsWith("d")) { int newDataBits = Convert.ToInt32(argument[1]); if ((newDataBits > 3) && (newDataBits < 9)) { dataBits = newDataBits; } else { ExitProgram(("Invalid data bits specified <" + argument[1] + ">"), exitCode: -1); } } // validate stopbits, terminate on error else if (argument[0].StartsWith("s")) { if (argument[1] == "0") { stopBits = StopBits.None; } else if (argument[1] == "1") { stopBits = StopBits.One; } else if (argument[1] == "1.5") { stopBits = StopBits.OnePointFive; } else if (argument[1] == "2") { stopBits = StopBits.Two; } else { ExitProgram(("Invalid stop bits specified <" + argument[1] + ">"), exitCode: -1); } } // validate auto connect, terminate on error else if (argument[0].StartsWith("a")) { if (argument[1].StartsWith("n")) { autoConnect = AutoConnect.NONE; } else if (argument[1].StartsWith("o")) { autoConnect = AutoConnect.ONE; } else if (argument[1].StartsWith("a")) { autoConnect = AutoConnect.ANY; } else { ExitProgram(("Invalid auto connect setting specified <" + argument[1] + ">"), exitCode: -1); } } // an invalid/incomplete argument was passed else { ExitProgram(("Invalid or incomplete argument <" + arg + ">\nTry 'ss.exe -help' to see a list of valid arguments"), exitCode: -1); } } Console.Clear(); if (autoConnect == AutoConnect.ANY) { Output("<<< Attemping to connect to any avaiable COM port. Use CTRL-X to cancel >>>"); } else if (autoConnect == AutoConnect.ONE) { Console.Clear(); if (port.name == String.Empty) { Output("<<< Attempting to connect to first available COM port. Use CTRL-X to cancel >>>"); } else { Output("<<< Attempting to connect to " + port.name + ". Use CTRL-X to concel >>>"); } } // if we made it this far, everything has been processed and we're ready to proceed! }
static void Main(string[] args) { // process all command-line arguments ProcessArguments(args); // set up keyboard input for program control / relay to serial port ConsoleKeyInfo keyInfo = new ConsoleKeyInfo(); Console.TreatControlCAsInput = true; // we need to use CTRL-C to activate the REPL in CircuitPython, so it can't be used to exit the application // this is where data read from the serial port will be temporarily stored string received = string.Empty; //main loop - keep this up until user presses CTRL-X or an exception takes us down do { // first things first, check for (and respect) a request to exit the program via CTRL-X if (Console.KeyAvailable) { keyInfo = Console.ReadKey(intercept: true); if ((keyInfo.Key == ConsoleKey.X) && (keyInfo.Modifiers == ConsoleModifiers.Control)) { Output("\n<<< SimplySerial session terminated via CTRL-X >>>"); ExitProgram(silent: true); } } // get a list of available ports availablePorts = (SimplySerial.GetSerialPorts()).OrderBy(p => p.num).ToList(); // if no port was specified/selected, pick one automatically if (port.name == String.Empty) { // if there are com ports available, pick one if (availablePorts.Count() >= 1) { // first, try to default to something that we assume is running CircuitPython SimplySerial.port = availablePorts.Find(p => p.board.isCircuitPython == true); // if that doesn't work out, just default to the first available COM port if (SimplySerial.port.name == null) { SimplySerial.port = availablePorts[0]; } } // if there are no com ports available, exit or try again depending on autoconnect setting else { if (autoConnect == AutoConnect.NONE) { ExitProgram("No COM ports detected.", exitCode: -1); } else { continue; } } } // if a specific port has been selected, try to match it with one that actually exists else { bool portMatched = false; foreach (ComPort p in availablePorts) { if (p.name == port.name) { portMatched = true; port = p; break; } } // if the specified port is not available, exit or try again depending on autoconnect setting if (!portMatched) { if (autoConnect == AutoConnect.NONE) { ExitProgram(("Invalid port specified <" + port.name + ">"), exitCode: -1); } else { continue; } } } // if we get this far, it should be safe to set up the specified/selected serial port serialPort = new SerialPort(port.name, baud, parity, dataBits, stopBits) { Handshake = Handshake.None, // we don't need to support any handshaking at this point ReadTimeout = 1, // minimal timeout - we don't want to wait forever for data that may not be coming! WriteTimeout = 250, // small delay - if we go too small on this it causes System.IO semaphore timeout exceptions DtrEnable = true, // without this we don't ever receive any data RtsEnable = true // without this we don't ever receive any data }; // attempt to open the serial port, deal with failures try { serialPort.Open(); } catch (Exception e) { // if auto-connect is disabled than any exception should result in program termination if (autoConnect == AutoConnect.NONE) { if (e is UnauthorizedAccessException) { ExitProgram((e.GetType() + " occurred while attempting to open " + port.name + ". Is this port already in use in another application?"), exitCode: -1); } else { ExitProgram((e.GetType() + " occurred while attempting to open " + port.name + "."), exitCode: -1); } } // if auto-connect is enabled, prepare to try again serialPort.Dispose(); Thread.Sleep(1000); // putting a delay here to avoid gobbling tons of resources thruogh constant high-speed re-connect attempts continue; } // if we get this far, clear the screen and send the connection message if not in 'quiet' mode Console.Clear(); if (!SimplySerial.Quiet) { Console.WriteLine(("<<< SimplySerial v{0} connected via {1} >>>\n" + "Settings : {2} baud, {3} parity, {4} data bits, {5} stop bit{6}, auto-connect {7}.\n" + "Device : {8}{9} {10}{11}{12}\n" + "---\n\nUse CTRL-X to exit.\n"), version, port.name, baud, (parity == Parity.None) ? "no" : (parity.ToString()).ToLower(), dataBits, (stopBits == StopBits.None) ? "0" : (stopBits == StopBits.One) ? "1" : (stopBits == StopBits.OnePointFive) ? "1.5" : "2", (stopBits == StopBits.One) ? "" : "s", (autoConnect == AutoConnect.ONE) ? "on" : (autoConnect == AutoConnect.ANY) ? "any" : "off", port.board.make, (port.board.make == "VID") ? ":" + port.vid : "", port.board.model, (port.board.model == "PID") ? ":" + port.pid : "", (port.board.isCircuitPython) ? " (CircuitPython-capable)" : "" ); } // this is the core functionality - loop while the serial port is open while (serialPort.IsOpen) { try { // process keypresses for transmission through the serial port if (Console.KeyAvailable) { // determine what key is pressed (including modifiers) keyInfo = Console.ReadKey(intercept: true); // exit the program if CTRL-X was pressed if ((keyInfo.Key == ConsoleKey.X) && (keyInfo.Modifiers == ConsoleModifiers.Control)) { Output("\n<<< SimplySerial session terminated via CTRL-X >>>"); ExitProgram(silent: true); } // properly process the backspace character else if (keyInfo.Key == ConsoleKey.Backspace) { serialPort.Write("\b"); Thread.Sleep(150); // sort of cheating here - by adding this delay we ensure that when we process the receive buffer it will contain the correct backspace control sequence } // everything else just gets sent right on through else { serialPort.Write(Convert.ToString(keyInfo.KeyChar)); } } // process data coming in from the serial port received = serialPort.ReadExisting(); // if anything was received, process it if (received.Length > 0) { // properly process backspace if (received == ("\b\x1B[K")) { received = "\b \b"; } // write what was received to console Console.Write(received); } } catch (Exception e) { if (autoConnect == AutoConnect.NONE) { ExitProgram((e.GetType() + " occurred while attempting to read/write to/from " + port.name + "."), exitCode: -1); } else { Output("\n<<< Communications Interrupted >>>\n"); } serialPort.Dispose(); Thread.Sleep(2000); // sort-of arbitrary delay - should be long enough to read the "interrupted" message Console.Clear(); if (autoConnect == AutoConnect.ANY) { port.name = String.Empty; Output("<<< Attemping to connect to any avaiable COM port. Use CTRL-X to cancel >>>"); } else if (autoConnect == AutoConnect.ONE) { Console.Clear(); Output("<<< Attempting to re-connect to " + port.name + ". Use CTRL-X to cancel >>>"); } break; } } } while (autoConnect > AutoConnect.NONE); // if we get to this point, we should be exiting gracefully ExitProgram("<<< SimplySerial session terminated >>>", exitCode: 0); }
static void Main(string[] args) { // attempt to enable virtual terminal escape sequence processing try { var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleMode(iStdOut, out uint outConsoleMode); outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; SetConsoleMode(iStdOut, outConsoleMode); } catch { // if the above fails, it doesn't really matter - it just means escape sequences won't process nicely } // load and parse data in boards.json LoadBoards(); // process all command-line arguments ProcessArguments(args); // verify log-related settings if (logging) { try { FileStream stream = new FileStream(logFile, logMode, FileAccess.Write); using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8)) { writer.WriteLine($"\n----- LOGGING STARTED ({DateTime.Now}) ------------------------------------"); } } catch (Exception e) { logging = false; ExitProgram($"* Error accessing log file '{logFile}'\n > {e.GetType()}: {e.Message}", exitCode: -1); } } // set up keyboard input for program control / relay to serial port ConsoleKeyInfo keyInfo = new ConsoleKeyInfo(); Console.TreatControlCAsInput = true; // we need to use CTRL-C to activate the REPL in CircuitPython, so it can't be used to exit the application // this is where data read from the serial port will be temporarily stored string received = string.Empty; //main loop - keep this up until user presses CTRL-X or an exception takes us down do { // first things first, check for (and respect) a request to exit the program via CTRL-X if (Console.KeyAvailable) { keyInfo = Console.ReadKey(intercept: true); if ((keyInfo.Key == ConsoleKey.X) && (keyInfo.Modifiers == ConsoleModifiers.Control)) { Output("\n<<< SimplySerial session terminated via CTRL-X >>>"); ExitProgram(silent: true); } } // get a list of available ports availablePorts = (SimplySerial.GetSerialPorts()).OrderBy(p => p.num).ToList(); // if no port was specified/selected, pick one automatically if (port.name == String.Empty) { // if there are com ports available, pick one if (availablePorts.Count() >= 1) { // first, try to default to something that we assume is running CircuitPython SimplySerial.port = availablePorts.Find(p => p.isCircuitPython == true); // if that doesn't work out, just default to the first available COM port if (SimplySerial.port == null) { SimplySerial.port = availablePorts[0]; } } // if there are no com ports available, exit or try again depending on autoconnect setting else { if (autoConnect == AutoConnect.NONE) { ExitProgram("No COM ports detected.", exitCode: -1); } else { continue; } } } // if a specific port has been selected, try to match it with one that actually exists else { bool portMatched = false; foreach (ComPort p in availablePorts) { if (p.name == port.name) { portMatched = true; port = p; break; } } // if the specified port is not available, exit or try again depending on autoconnect setting if (!portMatched) { if (autoConnect == AutoConnect.NONE) { ExitProgram(("Invalid port specified <" + port.name + ">"), exitCode: -1); } else { continue; } } } // if we get this far, it should be safe to set up the specified/selected serial port serialPort = new SerialPort(port.name) { Handshake = Handshake.None, // we don't need to support any handshaking at this point ReadTimeout = 1, // minimal timeout - we don't want to wait forever for data that may not be coming! WriteTimeout = 250, // small delay - if we go too small on this it causes System.IO semaphore timeout exceptions DtrEnable = true, // without this we don't ever receive any data RtsEnable = true // without this we don't ever receive any data }; // attempt to set the baud rate, fail if the specified value is not supported by the hardware try { if (baud < 0) { if (port.isCircuitPython) { baud = 115200; } else { baud = 9600; } } serialPort.BaudRate = baud; } catch (ArgumentOutOfRangeException) { ExitProgram(("The specified baud rate (" + baud + ") is not supported."), exitCode: -2); } // set other port parameters (which have already been validated) serialPort.Parity = parity; serialPort.DataBits = dataBits; serialPort.StopBits = stopBits; // attempt to open the serial port, deal with failures try { serialPort.Open(); } catch (Exception e) { // if auto-connect is disabled than any exception should result in program termination if (autoConnect == AutoConnect.NONE) { if (e is UnauthorizedAccessException) { ExitProgram((e.GetType() + " occurred while attempting to open " + port.name + ". Is this port already in use in another application?"), exitCode: -1); } else { ExitProgram((e.GetType() + " occurred while attempting to open " + port.name + "."), exitCode: -1); } } // if auto-connect is enabled, prepare to try again serialPort.Dispose(); Thread.Sleep(1000); // putting a delay here to avoid gobbling tons of resources thruogh constant high-speed re-connect attempts continue; } // if we get this far, clear the screen and send the connection message if not in 'quiet' mode Console.Clear(); Output(String.Format("<<< SimplySerial v{0} connected via {1} >>>\n" + "Settings : {2} baud, {3} parity, {4} data bits, {5} stop bit{6}, auto-connect {7}.\n" + "Device : {8} {9}{10}\n{11}" + "---\n\nUse CTRL-X to exit.\n", version, port.name, baud, (parity == Parity.None) ? "no" : (parity.ToString()).ToLower(), dataBits, (stopBits == StopBits.None) ? "0" : (stopBits == StopBits.One) ? "1" : (stopBits == StopBits.OnePointFive) ? "1.5" : "2", (stopBits == StopBits.One) ? "" : "s", (autoConnect == AutoConnect.ONE) ? "on" : (autoConnect == AutoConnect.ANY) ? "any" : "off", port.board.make, port.board.model, (port.isCircuitPython) ? " (CircuitPython-capable)" : "", (logging == true) ? ($"Logfile : {logFile} (Mode = " + ((logMode == FileMode.Create) ? "OVERWRITE" : "APPEND") + ")\n") : "" ), flush: true); lastFlush = DateTime.Now; DateTime start = DateTime.Now; TimeSpan timeSinceRX = new TimeSpan(); TimeSpan timeSinceFlush = new TimeSpan(); // this is the core functionality - loop while the serial port is open while (serialPort.IsOpen) { try { // process keypresses for transmission through the serial port if (Console.KeyAvailable) { // determine what key is pressed (including modifiers) keyInfo = Console.ReadKey(intercept: true); // exit the program if CTRL-X was pressed if ((keyInfo.Key == ConsoleKey.X) && (keyInfo.Modifiers == ConsoleModifiers.Control)) { Output("\n<<< SimplySerial session terminated via CTRL-X >>>"); ExitProgram(silent: true); } // check for keys that require special processing (cursor keys, etc.) else if (specialKeys.ContainsKey(keyInfo.Key)) { serialPort.Write(specialKeys[keyInfo.Key]); } // everything else just gets sent right on through else { serialPort.Write(Convert.ToString(keyInfo.KeyChar)); } } // process data coming in from the serial port received = serialPort.ReadExisting(); // if anything was received, process it if (received.Length > 0) { if (forceNewline) { received = received.Replace("\r", "\n"); } // write what was received to console Output(received, force: true, newline: false); start = DateTime.Now; } else { Thread.Sleep(1); } if (logging) { timeSinceRX = DateTime.Now - start; timeSinceFlush = DateTime.Now - lastFlush; if ((timeSinceRX.TotalSeconds >= 2) || (timeSinceFlush.TotalSeconds >= 10)) { if (logData.Length > 0) { Output("", force: true, newline: false, flush: true); } start = DateTime.Now; lastFlush = DateTime.Now; } } // if the serial port is unexpectedly closed, throw an exception if (!serialPort.IsOpen) { throw new IOException(); } } catch (Exception e) { if (autoConnect == AutoConnect.NONE) { ExitProgram((e.GetType() + " occurred while attempting to read/write to/from " + port.name + "."), exitCode: -1); } else { Output("\n<<< Communications Interrupted >>>\n"); } try { serialPort.Dispose(); } catch { //nothing to do here, other than prevent execution from stopping if dispose() throws an exception } Thread.Sleep(2000); // sort-of arbitrary delay - should be long enough to read the "interrupted" message if (autoConnect == AutoConnect.ANY) { port.name = String.Empty; Output("<<< Attemping to connect to any available COM port. Use CTRL-X to cancel >>>"); } else if (autoConnect == AutoConnect.ONE) { Output("<<< Attempting to re-connect to " + port.name + ". Use CTRL-X to cancel >>>"); } break; } } } while (autoConnect > AutoConnect.NONE); // if we get to this point, we should be exiting gracefully ExitProgram("<<< SimplySerial session terminated >>>", exitCode: 0); }