Example #1
0
        /// <summary>
        /// Returns a list of available serial ports with their associated PID, VID and descriptions
        /// Modified from the example written by Kamil Górski (freakone) available at
        /// http://blog.gorski.pm/serial-port-details-in-c-sharp
        /// https://github.com/freakone/serial-reader
        /// Some modifications were based on this stackoverflow thread:
        /// https://stackoverflow.com/questions/11458835/finding-information-about-all-serial-devices-connected-through-usb-in-c-sharp
        /// </summary>
        /// <returns>List of available serial ports</returns>
        private static List <ComPort> GetSerialPorts()
        {
            const string vidPattern  = @"VID_([0-9A-F]{4})";
            const string pidPattern  = @"PID_([0-9A-F]{4})";
            const string namePattern = @"(?<=\()COM[0-9]{1,3}(?=\)$)";

            using (var searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"{4d36e978-e325-11ce-bfc1-08002be10318}\""))
            {
                var            ports         = searcher.Get().Cast <ManagementBaseObject>().ToList();
                List <ComPort> detectedPorts = ports.Select(p =>
                {
                    ComPort c     = new ComPort();
                    c.name        = p.GetPropertyValue("Name").ToString();
                    c.vid         = p.GetPropertyValue("PNPDeviceID").ToString();
                    c.description = p.GetPropertyValue("Caption").ToString();

                    Match mVID = Regex.Match(c.vid, vidPattern, RegexOptions.IgnoreCase);
                    Match mPID = Regex.Match(c.vid, pidPattern, RegexOptions.IgnoreCase);

                    if (mVID.Success)
                    {
                        c.vid = mVID.Groups[1].Value;
                        c.vid = c.vid.Substring(0, Math.Min(4, c.vid.Length));
                    }
                    else
                    {
                        c.vid = "----";
                    }

                    if (mPID.Success)
                    {
                        c.pid = mPID.Groups[1].Value;
                        c.pid = c.pid.Substring(0, Math.Min(4, c.vid.Length));
                    }
                    else
                    {
                        c.pid = "----";
                    }

                    Match mName = Regex.Match(c.name, namePattern);
                    if (mName.Success)
                    {
                        c.name = mName.Value;
                        c.num  = int.Parse(c.name.Substring(3));
                        if (c.num == 0)
                        {
                            c.name = "?";
                        }
                    }
                    else
                    {
                        c.name = "?";
                        c.num  = 0;
                    }

                    c.board = MatchBoard(c.vid, c.pid);

                    return(c);
                }).ToList();

                // remove all unusable ports from the list
                detectedPorts.RemoveAll(p => p.name == "?");

                return(detectedPorts);
            }
        }
Example #2
0
        /// <summary>
        /// Returns a list of available serial ports with their associated PID, VID and descriptions
        /// Modified from the example written by Kamil Górski (freakone) available at
        /// http://blog.gorski.pm/serial-port-details-in-c-sharp
        /// https://github.com/freakone/serial-reader
        /// Some modifications were based on this stackoverflow thread:
        /// https://stackoverflow.com/questions/11458835/finding-information-about-all-serial-devices-connected-through-usb-in-c-sharp
        /// Hardware Bus Description through WMI is based on Simon Mourier's answer on this stackoverflow thread:
        /// https://stackoverflow.com/questions/69362886/get-devpkey-device-busreporteddevicedesc-from-win32-pnpentity-in-c-sharp
        /// </summary>
        /// <returns>List of available serial ports</returns>
        private static List <ComPort> GetSerialPorts()
        {
            const string vidPattern  = @"VID_([0-9A-F]{4})";
            const string pidPattern  = @"PID_([0-9A-F]{4})";
            const string namePattern = @"(?<=\()COM[0-9]{1,3}(?=\)$)";
            const string query       = "SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"{4d36e978-e325-11ce-bfc1-08002be10318}\"";

            // as per INTERFACE_PREFIXES in adafruit_board_toolkit
            // (see https://github.com/adafruit/Adafruit_Board_Toolkit/blob/main/adafruit_board_toolkit)
            string[] cpb_descriptions = new string[] { "CircuitPython CDC ", "Sol CDC ", "StringCarM0Ex CDC " };

            List <ComPort> detectedPorts = new List <ComPort>();

            foreach (var p in new ManagementObjectSearcher("root\\CIMV2", query).Get().OfType <ManagementObject>())
            {
                ComPort c = new ComPort();

                // extract and clean up port name and number
                c.name = p.GetPropertyValue("Name").ToString();
                Match mName = Regex.Match(c.name, namePattern);
                if (mName.Success)
                {
                    c.name = mName.Value;
                    c.num  = int.Parse(c.name.Substring(3));
                }

                // if the port name or number cannot be determined, skip this port and move on
                if (c.num < 1)
                {
                    continue;
                }

                // get the device's VID and PID
                string pidvid = p.GetPropertyValue("PNPDeviceID").ToString();

                // extract and clean up device's VID
                Match mVID = Regex.Match(pidvid, vidPattern, RegexOptions.IgnoreCase);
                if (mVID.Success)
                {
                    c.vid = mVID.Groups[1].Value.Substring(0, Math.Min(4, c.vid.Length));
                }

                // extract and clean up device's PID
                Match mPID = Regex.Match(pidvid, pidPattern, RegexOptions.IgnoreCase);
                if (mPID.Success)
                {
                    c.pid = mPID.Groups[1].Value.Substring(0, Math.Min(4, c.pid.Length));
                }

                // extract the device's friendly description (caption)
                c.description = p.GetPropertyValue("Caption").ToString();

                // attempt to match this device with a known board
                c.board = MatchBoard(c.vid, c.pid);

                // extract the device's hardware bus description
                c.busDescription = "";
                var inParams = new object[] { new string[] { "DEVPKEY_Device_BusReportedDeviceDesc" }, null };
                p.InvokeMethod("GetDeviceProperties", inParams);
                var outParams = (ManagementBaseObject[])inParams[1];
                if (outParams.Length > 0)
                {
                    var data = outParams[0].Properties.OfType <PropertyData>().FirstOrDefault(d => d.Name == "Data");
                    if (data != null)
                    {
                        c.busDescription = data.Value.ToString();
                    }
                }

                // we can determine if this is a CircuitPython board by its bus description
                foreach (string prefix in cpb_descriptions)
                {
                    if (c.busDescription.StartsWith(prefix))
                    {
                        c.isCircuitPython = true;
                    }
                }

                // add this port to our list of detected ports
                detectedPorts.Add(c);
            }

            return(detectedPorts);
        }
Example #3
0
        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);
        }
Example #4
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);
        }