How to auto-detect Arduino COM port?

2019-01-10 15:33发布

问题:

I'm using an Arduino with the Firmata library for communication to a C# application, and I want to eliminate a COM port configuration component since it can change from machine to machine...

Is it possible to:

  1. Enumerate list of COM ports in the system? (In my googling I've seen some fairly ugly Win32 API code, hoping there's maybe a cleaner version now)
  2. Auto-detect which COM port(s) are connected to an Arduino?

回答1:

This little bit of code has performed very well for this (returns the COM port string, i.e. "COM12" if Arduino is detected):

private string AutodetectArduinoPort()
        {
            ManagementScope connectionScope = new ManagementScope();
            SelectQuery serialQuery = new SelectQuery("SELECT * FROM Win32_SerialPort");
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(connectionScope, serialQuery);

            try
            {
                foreach (ManagementObject item in searcher.Get())
                {
                    string desc = item["Description"].ToString();
                    string deviceId = item["DeviceID"].ToString();

                    if (desc.Contains("Arduino"))
                    {
                        return deviceId;
                    }
                }
            }
            catch (ManagementException e)
            {
                /* Do Nothing */
            }

            return null;
        }


回答2:

  1. You can use SerialPort.GetPortNames() to return an array of string COM port names.
  2. I dont think you can auto detect the ports, youd have to ping the device in order to see if the device is connected.


回答3:

Taking the WMI Management route a bit further, I've come up with a wrapper class which hooks on to the Win32_SerialPorts events and dynamically populated a list of SerialPorts for Arduino and Digi International (X-Bee) devices, complete with PortNames and BaudRates.

For now, I've used the devices Description field in the Win32_SerialPorts entry as the Key for the Dictionary, but this can easily be changed.

It has been tested to a limited capacity with an Arduino UNO and it seems to be stable.

// -------------------------------------------------------------------------
//  <copyright file="ArduinoDeviceManager.cs" company="ApacheTech Consultancy">
//      Copyright (c) ApacheTech Consultancy. All rights reserved.
//  </copyright>
//  <license type="GNU General Public License" version="3">
//      This program is free software: you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation, either version 3 of the License, or
//      (at your option) any later version.
// 
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
// 
//      You should have received a copy of the GNU General Public License
//      along with this program. If not, see http://www.gnu.org/licenses
//  <license>
// -------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Management;
using System.Runtime.CompilerServices;

// Automatically imported by Jetbeans Resharper
using ArduinoLibrary.Annotations;

namespace ArduinoLibrary
{
    /// <summary>
    ///     Provides automated detection and initiation of Arduino devices. This class cannot be inherited.
    /// </summary>
    public sealed class ArduinoDeviceManager : IDisposable, INotifyPropertyChanged
    {
        /// <summary>
        ///     A System Watcher to hook events from the WMI tree.
        /// </summary>
        private readonly ManagementEventWatcher _deviceWatcher = new ManagementEventWatcher(new WqlEventQuery(
            "SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2 OR EventType = 3"));

        /// <summary>
        ///     A list of all dynamically found SerialPorts.
        /// </summary>
        private Dictionary<string, SerialPort> _serialPorts = new Dictionary<string, SerialPort>();

        /// <summary>
        ///     Initialises a new instance of the <see cref="ArduinoDeviceManager"/> class.
        /// </summary>
        public ArduinoDeviceManager()
        {
            // Attach an event listener to the device watcher.
            _deviceWatcher.EventArrived += _deviceWatcher_EventArrived;

            // Start monitoring the WMI tree for changes in SerialPort devices.
            _deviceWatcher.Start();

            // Initially populate the devices list.
            DiscoverArduinoDevices();
        }

        /// <summary>
        ///     Gets a list of all dynamically found SerialPorts.
        /// </summary>
        /// <value>A list of all dynamically found SerialPorts.</value>
        public Dictionary<string, SerialPort> SerialPorts
        {
            get { return _serialPorts; }
            private set
            {
                _serialPorts = value;
                OnPropertyChanged();
            }
        }

        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            // Stop the WMI monitors when this instance is disposed.
            _deviceWatcher.Stop();
        }

        /// <summary>
        ///     Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        ///     Handles the EventArrived event of the _deviceWatcher control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArrivedEventArgs"/> instance containing the event data.</param>
        private void _deviceWatcher_EventArrived(object sender, EventArrivedEventArgs e)
        {
            DiscoverArduinoDevices();
        }

        /// <summary>
        ///     Dynamically populates the SerialPorts property with relevant devices discovered from the WMI Win32_SerialPorts class.
        /// </summary>
        private void DiscoverArduinoDevices()
        {
            // Create a temporary dictionary to superimpose onto the SerialPorts property.
            var dict = new Dictionary<string, SerialPort>();

            try
            {
                // Scan through each SerialPort registered in the WMI.
                foreach (ManagementObject device in
                    new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_SerialPort").Get())
                {
                    // Ignore all devices that do not have a relevant VendorID.
                    if (!device["PNPDeviceID"].ToString().Contains("VID_2341") && // Arduino
                        !device["PNPDeviceID"].ToString().Contains("VID_04d0")) return; // Digi International (X-Bee)

                    // Create a SerialPort to add to the collection.
                    var port = new SerialPort();

                    // Gather related configuration details for the Arduino Device.
                    var config = device.GetRelated("Win32_SerialPortConfiguration")
                                       .Cast<ManagementObject>().ToList().FirstOrDefault();

                    // Set the SerialPort's PortName property.
                    port.PortName = device["DeviceID"].ToString();

                    // Set the SerialPort's BaudRate property. Use the devices maximum BaudRate as a fallback.
                    port.BaudRate = (config != null)
                                        ? int.Parse(config["BaudRate"].ToString())
                                        : int.Parse(device["MaxBaudRate"].ToString());

                    // Add the SerialPort to the dictionary. Key = Arduino device description.
                    dict.Add(device["Description"].ToString(), port);
                }

                // Return the dictionary.
                SerialPorts = dict;
            }
            catch (ManagementException mex)
            {
                // Send a message to debug.
                Debug.WriteLine(@"An error occurred while querying for WMI data: " + mex.Message);
            }
        }

        /// <summary>
        ///     Called when a property is set.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        [NotifyPropertyChangedInvocator]
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}


回答4:

Try this, I'm working on a very similar project, also anyone please feel free to edit this!

In the setup portion of the Arduino code, I have it call a setupComms() method, this method simply prints an "A" until it receives an "a". Once "a" is received it jumps to the main loop() function. So the C# portion checks each available port for "A" and if "A" is found we know that we have opened the port to the Arduino!

Again, this may not be very clean but it does work, I am open to any comments and suggestions!

 foreach (string s in SerialPort.GetPortNames())
        {
            com.Close(); // To handle the exception, in case the port isn't found and then they try again...

            bool portfound = false;
                com.PortName = s;
                com.BaudRate = 38400;
                try
                {
                    com.Open();
                    status.Clear();
                    status.Text += "Trying port: " + s+"\r";
                }
                catch (IOException c)
                {
                    status.Clear();
                    status.Text += "Invalid Port"+"\r";
                    return;
                }
                catch (InvalidOperationException c1)
                {

                    status.Clear();
                    status.Text += "Invalid Port" + "\r";
                    return;
                }
                catch (ArgumentNullException c2)
                {
                    // System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c2);
                    status.Clear();
                    status.Text += "Invalid Port" + "\r";
                    return;
                }
                catch (TimeoutException c3)
                {
                    //  System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c3);
                    status.Clear();
                    status.Text += "Invalid Port" + "\r";
                    return;
                }
                catch (UnauthorizedAccessException c4)
                {
                    //System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c);
                    status.Clear();
                    status.Text += "Invalid Port" + "\r";
                    return;
                }
                catch (ArgumentOutOfRangeException c5)
                {
                    //System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c5);
                    status.Clear();
                    status.Text += "Invalid Port" + "\r";
                    return;
                }
                catch (ArgumentException c2)
                {
                    //System.Windows.Forms.MessageBox.Show("Sorry, Exception Occured - " + c2);
                    status.Clear();
                    status.Text += "Invalid Port" + "\r";
                    return;
                }
                if (!portfound)
                {
                    if (com.IsOpen) // Port has been opened properly...
                    {
                        com.ReadTimeout = 500; // 500 millisecond timeout...
                        sent.Text += "Attemption to open port " + com.PortName + "\r";
                        try
                        {
                            sent.Text += "Waiting for a response from controller: " + com.PortName + "\r";
                            string comms = com.ReadLine();
                            sent.Text += "Reading From Port " + com.PortName+"\r";
                            if (comms.Substring(0,1) == "A") // We have found the arduino!
                            {
                                status.Clear();
                                status.Text += s + com.PortName+" Opened Successfully!" + "\r";
                                //com.Write("a"); // Sends 0x74 to the arduino letting it know that we are connected!
                                com.ReadTimeout = 200; 
                                com.Write("a");
                                sent.Text += "Port " + com.PortName + " Opened Successfully!"+"\r";
                                brbox.Text += com.BaudRate;
                                comboBox1.Text = com.PortName;

                            }
                            else
                            {
                                sent.Text += "Port Not Found! Please cycle controller power and try again" + "\r";
                                com.Close();       
                            }
                        }
                        catch (Exception e1)
                        {
                            status.Clear();
                            status.Text += "Incorrect Port! Trying again...";
                            com.Close();
                        }
                    }
              }
        }

All of the Try Catch statements are in there from when I was originally testing, this has worked for me so far. Good luck!



回答5:

This method does not help you find out which port your arduino is connected to your computer

SerialPort.GetPortNames Method ()

// Get a list of serial port names.
        string[] ports = SerialPort.GetPortNames();

        Console.WriteLine("The following serial ports were found:");
        Console.WriteLine("Aşşağıda Seri Bağlantı Noktaları Bulundu:");//For Turkish
        // Display each port name to the console.
        foreach(string port in ports)
        {
            Console.WriteLine(port);
        }

        Console.ReadLine();


回答6:

I've noticed that my Chinese clone of Arduino nano shows up the COM port correctly in Devices Manager, but it doesn't show on the C# app dorp down list when you try and get all ports using this command:

using (var searcher = new ManagementObjectSearcher("SELECT * FROM WIN32_SerialPort"));

However, it is shown correctly when executing:

foreach (string z in SerialPort.GetPortNames())

So I have 2 lists: one with output of 1st code, and one with output from 2nd code. When comparing both, it will find the correct COM port. Obviously, when using Original Andurino Uno both commands displays port correctly so this solution will only work for Chinese clones which, for some odd reason, are invisible to using (var searcher = new ManagementObjectSearcher("SELECT * FROM WIN32_SerialPort"));



回答7:

I just had a similar challenge with a Teensyduino communicating with a PC based processing language program. It may be useful for someone working with something Java or processing language rather than C#.

The basic idea of this solution was to send a handshake request ("!sh\n") to each Serial port and then listen for a response ("$h\n") from each device until the correct handshake response was received. Thereby showing which of the ports was the device I was seeking.

Also, I'm quite new to StackOverflow so please forgive and educate me if I'm breaking any StackOverflow etiquette in this answer.

Processing Language Code:

import processing.serial.*;

int ellipticalWalkerTeensyIndex; /* Represents Elliptical Walker Serial Port index in the Serial.list() String array. */
boolean latch;

String[] serialPortList = Serial.list();
int serialPortCount = serialPortList.length;
Serial[] ports = new Serial[serialPortCount];
int[] serialConnected = new int[serialPortCount];

void setup(){
    for (int z = 0; z < serialPortCount; ++z) { /* Initialise serialConnected array to 0; Anything not marked to 1 later will be ignored. */
        serialConnected[z] = 0;
    } 

    ellipticalWalkerTeensyIndex = -1; /* Initialise ellipticalWalkerTeensyIndex to -1, as the correct serial port is not yet known. */
    latch = false;

    for (int z = 0; z < serialPortCount; ++z) {
        try {
            ports[z] = new Serial(this, serialPortList[z], 9600);
            serialConnected[z] = 1; /* Mark this index as connected. */
            ports[z].write("!sh");  /* Send handshake request;  Expected response is "$h\n" */
        }catch (Exception e){
            println("Could not connect to "+Integer.toString(z)+" exception details: "+e);
        }
    }
}

void draw(){
    if (ellipticalWalkerTeensyIndex < 0) {
        for (int z = 0; z < serialPortCount; ++z) {
            if(serialConnected[z]>0){ /* Only attempt communication if we have marked this serial port as connected during the setup routine. */
                if (ports[z].available()>0) { /* Read from serial port 'z' if data is available. */
                    String lineOfData = ports[z].readStringUntil('\n');
                    if(lineOfData.charAt(0)=='$' && lineOfData.charAt(1)=='h'){ /* Check if received response matches expected handshake response */
                        ellipticalWalkerTeensyIndex = z; /* Note the correct serial port for the teensy. */
                    }
                } 
            }
        }    
    }else{
        if (!latch) {
            println("The teensyduino is on serial port: "+serialPortList[ellipticalWalkerTeensyIndex]);
            latch = true;
            exit();
        }
    }
}

Runtime results:

PS C:\repos\elliptical_walker> processing-java --sketch=c:\repos\elliptical_walker\EW0 --run
The teensyduino is on serial port: COM3
Finished.


标签: c# arduino