Author Topic: How to display com# when USB is plugged in?  (Read 8442 times)

Arne

  • Member
  • ***
  • Posts: 3
How to display com# when USB is plugged in?
« on: December 29, 2013, 11:57:00 pm »
Hi all,

I didn't find any posts to this issue, but if there is any please point me to them.

I am trying to piece together a script that will display the com# of an Arduino board when it gets plugged in.

I've been on the AutoIt forum, but my knowledge of the windows environment is very limited as well as AutoIt. So my questions to their forum may not be that clear.

Here's what I am trying to do. When each board is first connected, a com# is assigned to it. So when I switch between boards I have to go to Device Manager>Ports to find the port#. So, I thought that it should be possible to have a script running (on Win7) that will detect when any of my Arduino's are plugged in and show a small popup with the port#. AutoIt has a ToolTip that does this nicely. It can follow the mouse pointer as well which is kind of neat. The problem is getting the number of the port.

If anyone has AutoIt installed they can try this little script to see what I'm after:
Code: [Select]
$j = Random(1,6,1)

For $i = 1 To 200
   ToolTip("COM" & $j)
   Sleep(10)
Next

I have also spent time looking around at PowerShell, but get lost in the huge amount of info there :-\ I don't know what area to look at. I see stuff like WM_DEVICECHANGE, RegisterDeviceNotification, handles, events, etc.
 
When I'm using BrayTerminal the ports that aren't available are grayed out, so with that idea there must be something in windows that would have that info, I just don't know what that is.

First I need to know if it's possible? - I think it should be.

I don't know if I need a dll to accomplish it? - Hopefully not because I know nothing about dll's.

And please don't tell me to put stickers on each board ;)  that's not the point.

I am hoping to find a small example script that can output (anything at this point) when a device is plugged in and output that to a MsgBox, console or something.

Any assistance would be appreciated.
« Last Edit: December 30, 2013, 10:59:58 am by Arne »

Arne

  • Member
  • ***
  • Posts: 3
Re: How to display com# when USB is plugged in?
« Reply #1 on: December 30, 2013, 10:09:00 am »
On LVR's site I found this statement "Use WMI to detect device arrival and removal." So I googled it and stumbled upon one thing that led to another.  Back in PowerShell I tried the following code that returns what I want:

Code: [Select]
PS C:\MyScripts> Get-WmiObject win32_serialport | Select-Object name

or if using alias:
PS C:\MyScripts> gwmi win32_serialport | Select name

Output is this:
name
----
Communications Port (COM1)
Arduino Uno (COM11)

At this point I should be able to look for "Uno" and get pull out 'COM11' to use for $j in the above post.

Now I have more questions:
How can I use PS in AutoIt?   Post that to AutoIt forum.
How to use WMI to detect device arrival in a script?

Jan Axelson

  • Administrator
  • Frequent Contributor
  • *****
  • Posts: 3033
    • Lakeview Research
Re: How to display com# when USB is plugged in?
« Reply #2 on: December 30, 2013, 10:36:27 am »
As you found, my example code shows how to detect device arrival using WMI and the .NET System.Management class:

http://www.janaxelson.com/hidpage.htm

http://www.janaxelson.com/winusb.htm

Whether you can do the same in a PowerShell script, I don't know.
« Last Edit: July 13, 2014, 09:57:31 pm by Jan Axelson »

bpaddock

  • Frequent Contributor
  • ****
  • Posts: 66
Re: How to display com# when USB is plugged in?
« Reply #3 on: January 02, 2014, 10:55:21 am »
Hi all,

I didn't find any posts to this issue, but if there is any please point me to them.

I am trying to piece together a script that will display the com# of an Arduino board when it gets plugged in.

When I'm using BrayTerminal the ports that aren't available are grayed out, so with that idea there must be something in windows that would have that info, I just don't know what that is.


Windows code below does what BrayTerminal does, via using the Windows SetupAPI.

 I got tired of people calling me on the phone and asking me "What COM port do I have?".
"How should I know! It is *your* computer!", is what I always felt like shouting back at them, but didn't, wrote this code instead.

Compile it with this: http://nuwen.net/mingw.html to make a DOS demo program.
I have not compiled this code with the current version there that is now using C11 by default.
If you have a problem me know and I'll get it fixed to compile with current compiler, last I touched this code was a few years ago.

You can make this a DLL if you like, or just copy the code into your own program.

Code: [Select]
/** @(#)ports_get.c        <19-Nov-2011 10:59:49 Bob>
 *  \date Last Time-stamp: <29-Nov-2011 14:37:51 Bob>
 *
 *  \file ports_get.c
 *  \brief  'ports_get' shows what Microsoft Windows [TM] COM ports present.
 *
 * 'ports_get' is an example of how to enumerate the COM Ports on
 * Windows to tell which ports are currently available on a system.
 * By including this code in to your project you will never again have
 * to answer the support phone call "What COM ports do I have on my
 * computer?".
 *
 * **@@##@@** BEGIN LICENSE BLOCK **@@##@@**
 *
 * Copyright Robert L. Paddock 2011.  All Rights Reserved.
 *
 * http://blog.softwaresafety.net
 * http://www.designer-iii.com
 *
 * License: The I hate Lawyers License; "The first thing we do, let's
 *          kill all the lawyers." - Dick the Butcher in
 *          "Henry VI, Part II" by Shakespeare.
 *
 *   1) Do whatever you want with this code, just don't claim you
 *   wrote it.
 *
 *   2) This License Block must remain intact in your code if you use
 *   said code.

 *   3) There are no guarantees with this code whatsoever.  Anything
 *   that happens is not my fault, as I don't know how you applied the
 *   code to your project.

 *   4) There are no guarantees that Microsoft Windows [TM] will
 *   return the expected information in all conditions, on all
 *   possible relevant operating systems, if it returns anything
 *   useful at all, ever.  In my testing, Windows [TM] has always
 *   returned the correct results on my systems.  I use variants of
 *   this code in my own systems.
 *
 *   5) The 'trim.c' function is from the Snippets Collection, by Bob
 *   Stout.  It is covered by its own Public Domain license.  In a
 *   real application this function would be a separate compilation
 *   unit, if you chose to use it in your own code.
 *
 *   6) The example .EXE file was compiled with the version 7.2 Nuwen MinGW
 *   Distro, from http://nuwen.net/mingw.html , based on GCC 4.6.1.
 *
 *   7) The included *.lnt files and lintall.bat are the configuration
 *   files I used with Gimpel Software's Lint program.
 *
 * **@@##@@** END LICENSE BLOCK **@@##@@**
 */

/*lint -save */

/*lint -save -e537 Repeated include file, due to windows.h including some of these: */
#include <windows.h>  /* Standard Windows sizes */
#include <setupapi.h> /* Microsoft Windows Setup API */

#include <ctype.h>    /* Used by trim() */
#include <stdlib.h>   /* atoi() */
#include <string.h>   /* memset() */
/*lint -restore */

#include "ports_get.h"

#ifndef TEST_CODE
#define TEST_CODE (1) /* Set to NonZero to build main() to test */
#endif

#define NUM_DEVICES (256U)
static port_info port_table[ NUM_DEVICES ];

/* ************************************************************ */
/*
**  trim.c - Remove leading, trailing, and excess embedded spaces.
**  Public Domain by Bob Stout, from the Snippets collection.
*/
#define NUL '\0'

static char *trim(char *str)
{
      char *ibuf = str, *obuf = str;
      int i = 0, cnt = 0;

      /*
      **  Trap NULL
      */

      if (str)
      {
            /*
            **  Remove leading spaces (from rmlead.c)
            */

            for (ibuf = str; *ibuf && isspace(*ibuf); ++ibuf)
              {
                  ;
              }
            if (str != ibuf)
              {
                memmove(str, ibuf, (size_t) (ibuf - str) );
              }

            /*
            **  Collapse embedded spaces (from lv1ws.c)
            */

            while (*ibuf)
            {
                  if (isspace(*ibuf) && cnt)
                        ibuf++;
                  else
                  {
                        if (!isspace(*ibuf))
                              cnt = 0;
                        else
                        {
                              *ibuf = ' ';
                              cnt = 1;
                        }
                        obuf[i++] = *ibuf++;
                  }
            }
            obuf[i] = NUL;

            /*
            **  Remove trailing spaces (from rmtrail.c)
            */

            while (--i >= 0)
            {
                  if (!isspace(obuf[i]))
                        break;
            }
            obuf[++i] = NUL;
      }
      return str;
}
/* End Public Domain trim.c section */
/* ************************************************************ */

static unsigned int com_string_to_port_number( char const *com_start_chr_ptr, char const *com_end_chr_ptr )
{
  unsigned int buf_idx_ui = 0;
  char         buf[ PORTS_GET_BUFFER_SIZE ];
  char const   *chr_ptr = (com_start_chr_ptr + 4U); /* Skip over the '(COM' */

  if( NULL == strstr( com_start_chr_ptr, "(COM" ) )
    {
      return( 0 ); /* This is not COM port */
    }

  while( chr_ptr != com_end_chr_ptr )
    {
      buf[ buf_idx_ui++ ] = *chr_ptr++;
    }
  buf[ buf_idx_ui ] = 0;

  return( (unsigned int) atoi( buf ) );
}

int ports_get( void )
{
  HDEVINFO device_handle;
  DWORD RequiredSize=0;
  char guid_buf[ sizeof(GUID) ];

  /* Clear any previous port data: */
  memset( port_table, 0, (size_t) (NUM_DEVICES * sizeof( port_info )) );

  /*
   * http://msdn.microsoft.com/en-us/library/ff550937.aspx :
   *
   *   "The SetupDiClassGuidsFromName function retrieves the GUID(s)
   *    associated with the specified class name. This list is built
   *    based on the classes currently installed on the system."
   *
   */
  if( !SetupDiClassGuidsFromNameA( "Ports", (LPGUID) guid_buf, sizeof( guid_buf ), (PDWORD) &RequiredSize ) )
    {
      return( (int) PORTS_API_UNABLE_TO_OPEN );
    }

    /*
     * http://msdn.microsoft.com/en-us/library/ff550937(v=VS.85).aspx
     *
     *     "The SetupDiClassGuidsFromName function retrieves the
     *     GUID(s) associated with the specified class name. This list
     *     is built based on the classes currently installed on the
     *     system."
     *
     * We are only interested in devices that are currently present in a system.
     *
     */

    device_handle=SetupDiGetClassDevs( (GUID *)guid_buf, NULL, NULL, DIGCF_PRESENT );
if( INVALID_HANDLE_VALUE == device_handle )
      {
        return( (int) PORTS_API_INVALID_HANDEL_VALUE_DURING_OPEN );
      }

BYTE  Buffer[PORTS_GET_BUFFER_SIZE];
DWORD BufferSize = 0;
DWORD DataType   = 0;
    DWORD dwIndex    = 0;
SP_DEVINFO_DATA devInfo;

    memset(&devInfo, 0, sizeof(devInfo));
    devInfo.cbSize   =  sizeof(devInfo);

    /*
     * http://msdn.microsoft.com/en-us/library/ff551010(v=VS.85).aspx
     *
     *    "The SetupDiEnumDeviceInfo function returns a
     *    SP_DEVINFO_DATA structure that specifies a device
     *    information element in a device information set."
     */
    while( SetupDiEnumDeviceInfo( device_handle, dwIndex++, &devInfo ) )
      {

        /*
         * http://msdn.microsoft.com/en-us/library/ff551967(v=VS.85).aspx
         *
         *    "The SetupDiGetDeviceRegistryProperty function retrieves
         *    a specified Plug and Play device property."
         */
        if( 0 != SetupDiGetDeviceRegistryProperty(
                                             device_handle,        /* Handel to device information set */
                                             &devInfo,             /* Pointer to SP_DEVINFO_DATA structure */
                                             SPDRP_DEVICEDESC,     /* Request device description string */
                                             (PDWORD) &DataType,   /* Where to store the data type property */
                                             Buffer,               /* Where to store the requested property */
                                             sizeof(Buffer),       /* How large of text the above Buffer can hold */
                                             (PDWORD) &BufferSize) /* Length of text put into Buffer */
            )
{
              strncpy( port_table[ dwIndex ].DeviceDescription_chr_ary, trim( (char *) Buffer ), (size_t) BufferSize );
}

        if( 0 != SetupDiGetDeviceRegistryProperty(
                                             device_handle,
                                             &devInfo,
                                             SPDRP_FRIENDLYNAME, /* Request FriendlyName of a device */
                                             (PDWORD) &DataType,
                                             Buffer,
                                             sizeof(Buffer),
                                             (PDWORD) &BufferSize)
            )
{
              strncpy( port_table[ dwIndex ].FriendlyName_chr_ary, trim( (char *) Buffer ), (size_t) BufferSize );

              /* Convert the string, if it is present, '(COMxxx)' to unsigned integer: */
              char *num_start_str_ptr = strpbrk( (char *) Buffer, "(" );
              if( NULL != num_start_str_ptr )
                {
                  char *num_end_str_ptr = strpbrk( (char *) Buffer, ")" );
                  if( NULL != num_end_str_ptr )
                    {
                      port_table[ dwIndex].port_number_ui = com_string_to_port_number( num_start_str_ptr, num_end_str_ptr );
                    }
                }
}

        if( 0 != SetupDiGetDeviceRegistryProperty(
                                             device_handle,
                                             &devInfo,
                                             SPDRP_HARDWAREID,  /* Request list of hardware IDs for a device */
                                             (PDWORD) &DataType,
                                             Buffer,
                                             sizeof(Buffer),
                                             (PDWORD) &BufferSize)
            )
{
              strncpy( port_table[ dwIndex ].HardwareID_chr_ary, trim( (char *) Buffer ), (size_t) BufferSize );
}

        if( 0 != SetupDiGetDeviceRegistryProperty(
                                             device_handle,
                                             &devInfo,
                                             SPDRP_MFG,         /* Request name of the device manufacturer */
                                             (PDWORD) &DataType,
                                             Buffer,
                                             sizeof(Buffer),
                                             (PDWORD) &BufferSize)
            )
{
              strncpy( port_table[ dwIndex ].Mfg_chr_ary, trim( (char *) Buffer ), (size_t) BufferSize );
}

        /*
         * http://msdn.microsoft.com/en-us/library/ff551106(v=VS.85).aspx :
         *
         *    "The SetupDiGetDeviceInstanceId function retrieves the
         *    device instance ID that is associated with a device
         *    information element."
         */
        if(
           SetupDiGetDeviceInstanceId(
                                      device_handle,
                                      &devInfo,
                                      (PSTR )Buffer,
                                      sizeof(Buffer),
                                      (PDWORD) &BufferSize
                                     )
          )
          {
            strncpy( port_table[ dwIndex ].InstatnceID_chr_ary, trim( (char *) Buffer ), (size_t) BufferSize );
          }
      }/* while( SetupDiEnumDeviceInfo( device_handle, dwIndex++, &devInfo ) ) */

    /*
     * http://msdn.microsoft.com/en-us/library/ff550996(v=VS.85).aspx :
     *
     *    "The SetupDiDestroyDeviceInfoList function deletes a device
     *    information set and frees all associated memory."
     */
(void) SetupDiDestroyDeviceInfoList(device_handle);

    return( (dwIndex - 1) ); /* Number of Ports currently available [While() always does at least one loop] */
}

port_info const *port_get( unsigned int const idx_ui )
{
  return( &port_table[ idx_ui ] );
}

#if( TEST_CODE > 0 ) /* This is example code of how to apply the above source code in your own project. */

#include <stdio.h>    /* printf() for testing */
int main( void )
{
  int number_of_ports_i = ports_get(); /* How many ports are currently present? */

  if( number_of_ports_i > 0 )
    {
      if( 1 == number_of_ports_i ) /* Deal with proper plurals */
        {
          printf( "%d COM or LPT port found (A LPT port will not be listed below):\n", number_of_ports_i );
        }
      else
        {
          printf( "%d total LPT and COM ports found, COM ports listed below:\n", number_of_ports_i );
        }

      /* Display all COM Ports that have been found: */
      for( unsigned int idx_ui = 1; idx_ui < (size_t) (number_of_ports_i + 1); ++idx_ui )
        {
          /* We are only interested in Serial COM Ports. port_number_ui will be zero for LPT ports: */
          if( 0 != port_table[ idx_ui ].port_number_ui )
            {
              port_info const *port_info_ptr = port_get( idx_ui ); /* Get pointer to this COM Port information, then display it */

              printf( "COM%u:\n", port_info_ptr->port_number_ui ); /* COMxxx: as would be displayed by Windows Device Manager */
              printf( "\tDevice Description: %s\n", port_info_ptr->DeviceDescription_chr_ary );
              printf( "\tFriendly Name:      %s\n", port_info_ptr->FriendlyName_chr_ary );
              printf( "\tManufacture:        %s\n", port_info_ptr->Mfg_chr_ary );
              printf( "\tHardware ID:        %s\n", port_info_ptr->HardwareID_chr_ary );
              printf( "\tInstance ID:        %s\n", port_info_ptr->InstatnceID_chr_ary );
            }
        }
    }
  else
    {
      printf( "No ports found.\n" ); /* Maybe this is a Laptop with no USB devices connected. */
    }
}
#endif

/*lint -restore */

Code: [Select]
/** @(#)ports_get.h  <19-Nov-2011 11:02:10 Bob>
 *  Last Time-stamp: <20-Nov-2011 15:45:30 Bob>
 *
 *  \file ports_get.h
 *  \brief  'ports_get' shows what Microsoft Windows [TM] COM ports available present.
 *
 * 'ports_get' is an example of how to enumerate the COM Ports on
 * Windows to tell which ports are currently available on a system.
 * By including this code in to your project you will never again
 * have to answer the support phone call "What COM ports do I have on
 * my computer?".
 *
 * **@@##@@** BEGIN LICENSE BLOCK **@@##@@**
 *
 * Copyright Robert L. Paddock 2011.  All Rights Reserved.
 *
 * License: The I hate Lawyers License; "The first thing we do, let's
 *          kill all the lawyers." - Dick the Butcher in
 *          "Henry VI, Part II" by Shakespeare.
 *
 *   1) Do whatever you want with this code, just don't claim you
 *   wrote it.
 *
 *   2) There are no guarantees with this code whatsoever.  Anything
 *   that happens is not my fault, as I don't know how you applied the
 *   code to your project.

 *   3) There are no guarantees that Microsoft Windows [TM] will
 *   return the expected information in all conditions, on all
 *   possible relevant operating systems, if it returns anything
 *   useful at all, ever.
 *
 *  4) The 'trim.c' function is from the Snippets Collection, by Bob
 *  Stout.  It is covered by its own Public Domain license.  In a real
 *  application this function would be a separate compilation unit.
 *
 * **@@##@@** END LICENSE BLOCK **@@##@@**
 */

/*lint -save -e755 -e756 Disable warning(s), this file only, global macro/typedef 'Symbol' (Location) not referenced */

#ifndef _PORTS_GET_H_
#define _PORTS_GET_H_ (1)

#if defined(__cplusplus) && __cplusplus
 extern "C" {
#endif

enum PORTS_GET_RESULTS{
  PORTS_API_UNABLE_TO_OPEN                    = -1,
  PORTS_API_INVALID_HANDEL_VALUE_DURING_OPEN  = -2
};

#define PORTS_GET_BUFFER_SIZE (512U)

typedef struct /* define the struct 'port_info' */
{
  unsigned int port_number_ui;
  char         DeviceDescription_chr_ary[ PORTS_GET_BUFFER_SIZE ];
  char         FriendlyName_chr_ary[      PORTS_GET_BUFFER_SIZE ];
  char         HardwareID_chr_ary[        PORTS_GET_BUFFER_SIZE ];
  char         InstatnceID_chr_ary[       PORTS_GET_BUFFER_SIZE ];
  char         Mfg_chr_ary[               PORTS_GET_BUFFER_SIZE ];
}port_info;

int ports_get( void );

port_info const *port_get( unsigned int const idx_ui );

#if defined(__cplusplus) && __cplusplus
 }
#endif

#endif /* _PORTS_GET_H_ */

/*lint -restore */

Code: [Select]
# Hey Emacs, this is a -*- makefile -*-
# @(#)Makefile        <19-Nov-2011 09:43:14 Bob>
#  \date Last Time-stamp: <20-Nov-2011 10:12:47 Bob>
#  \file Makefile
#----------------------------------------------------------------------------
#
#  \brief  Simple Makefile for ports_get demo.

CC = gcc

CFLAGS  = -Wall
CFLAGS += -Werror
CFLAGS += -Wextra
CFLAGS += -Winline
CFLAGS += -Wsign-compare
CFLAGS += -Wstrict-prototypes
CFLAGS += -Wundef
CFLAGS += -Wunreachable-code
CFLAGS += -pedantic
CFLAGS += -Wc++-compat
CFLAGS += -std=c99

ports_get.exe: ports_get.o
$(CC) ports_get.o -lsetupapi -o ports_get.exe

ports_get.o: ports_get.c
$(CC) $(CFLAGS) -c ports_get.c

Arne

  • Member
  • ***
  • Posts: 3
Re: How to display com# when USB is plugged in?
« Reply #4 on: January 05, 2014, 10:46:41 am »
Quote
You can make this a DLL if you like, or just ....
???

Thanks Jan & bpaddock,

Unfortunately my level is not at that point. My programming is mostly BASIC, way back in the PET commodore era and IBM punch cards (back in high school). The PET was locked up in it own room back then.  Over the years I have picked up many boards starting with the 8051 and tried to learn C. Then got the next issue of Popular Elec. and find that a new board is in town with a new IDE and all that stuff.

Oh well, I'll keep your references in mind for later...

I have been over at the other forums trying to get help. I was hopping that someone here might have had a small snippet of code that would get me going in the right direction.

Cheers