Page 1 of 1

usb-to-serial: how to get associated display name

Posted: Wed May 22, 2024 3:02 pm
by morosh
Hello:
I'm using a usb to serial frequently, each time I use Device Manager to know the name of the corresponding serial port "comx". I like to know the com port number programmatically by doing a loop searching all serial ports available and getting the com number of the one described as "Prolific USB-to-Serial Comm Port". Going to the corresponding properties in Device Manager I noticed that the number is included in the "Display Name" property like "Prolific USB-to-Serial Comm Port (COM9)", is it possible to get this "Display Name" property by a call to some Win-api or other.

Thank you

Re: usb-to-serial: how to get associated display name

Posted: Wed May 22, 2024 9:51 pm
by infratec
It is easy with an object orientated language:
https://stackoverflow.com/questions/283 ... nformation

In PB I would run wmic.exe:

Code: Select all

WMIC.exe path win32_pnpentity where "PNPClass='Ports'" get Caption,Service,Manufacturer

Code: Select all

EnableExplicit

Define WMIC.i, Parameter$, Output$

Parameter$ = "path win32_pnpentity where " + #DQUOTE$ + "PNPClass='Ports'" + #DQUOTE$ + " get Caption,Manufacturer"

WMIC = RunProgram("wmic.exe", Parameter$, "", #PB_Program_Open|#PB_Program_Read|#PB_Program_Hide)
If WMIC
  While ProgramRunning(WMIC)
    If AvailableProgramOutput(WMIC)
      Output$ + ReadProgramString(WMIC) + #LF$
    EndIf
  Wend
  CloseProgram(WMIC)
  Debug Output$
EndIf

Re: usb-to-serial: how to get associated display name

Posted: Thu May 23, 2024 6:49 am
by morosh
Amazing, Thank you very much Infratec!!!
Exactly what I'm searching for

Re: usb-to-serial: how to get associated display name

Posted: Thu May 23, 2024 9:14 am
by infratec
As a side note:

As hobby project I'm writing OBD software for motorcycles.
The only really reliable adapters are using chips from FTDI.
With Prolific I (or better: the users) came very often in trouble.

Re: usb-to-serial: how to get associated display name

Posted: Thu May 23, 2024 11:51 am
by PeDe
I have translated this C# code to PureBasic:
https://nakov.com/blog/2009/05/10/enume ... tion-in-c/

It's just a first try, but the output works here with Windows 7 and PB v6.11 x64.

Peter

Code: Select all

; https://nakov.com/blog/2009/05/10/enumerate-all-com-ports-and-find-their-name-and-description-in-c/

EnableExplicit

#DICS_FLAG_GLOBAL = 1
#DIREG_DEV = 1
#SPDRP_DEVICEDESC = 0

Structure uDeviceInfo
    sName.s
    sDescription.s
EndStructure

Structure uSpDevInfoData Align #PB_Structure_AlignC
    cbSize.l
    ClassGuid.GUID
    DevInst.l
    *Reserved
EndStructure

Prototype.i ptSetupDiGetClassDevs(*ClassGuid, *pctstrEnumerator, hwndParent.i, dwFlags.l)
Prototype.i ptSetupDiEnumDeviceInfo(hDevInfoSet.i, dwMemberIndex.l, *DevInfoData)
Prototype.i ptSetupDiOpenDevRegKey(hDevInfoSet.i, *DeviceInfoData, dwScope.l, dwHwProfile.l, dwKeyType.l, dwSamDesired.l)
Prototype.i ptSetupDiGetDeviceRegistryProperty(hDevInfoSet.i, *DeviceInfoData, dwProperty.l, *dwPropertyRegDataType,
    *bytePropertyBuffer, dwPropertyBufferSize.l, *dwRequiredSize)
Prototype.i ptSetupDiDestroyDeviceInfoList(hDevInfoSet.i)

Global SetupDiGetClassDevs.ptSetupDiGetClassDevs
Global SetupDiEnumDeviceInfo.ptSetupDiEnumDeviceInfo
Global SetupDiOpenDevRegKey.ptSetupDiOpenDevRegKey
Global SetupDiGetDeviceRegistryProperty.ptSetupDiGetDeviceRegistryProperty
Global SetupDiDestroyDeviceInfoList.ptSetupDiDestroyDeviceInfoList


Procedure.i LibraryInit()
    Protected iReturn.i, fResult.i, iSetupApiLibNumber.i

    iSetupApiLibNumber = OpenLibrary(#PB_Any, "setupapi.dll")
    iReturn = iSetupApiLibNumber
    fResult = Bool(iSetupApiLibNumber)
    If Not fResult
        Debug "Error LibraryInit()->OpenLibrary() setupapi.dll"
    EndIf

    If fResult
        SetupDiGetClassDevs = GetFunction(iSetupApiLibNumber, "SetupDiGetClassDevsW")
        SetupDiEnumDeviceInfo = GetFunction(iSetupApiLibNumber, "SetupDiEnumDeviceInfo")
        SetupDiOpenDevRegKey = GetFunction(iSetupApiLibNumber, "SetupDiOpenDevRegKey")
        SetupDiGetDeviceRegistryProperty = GetFunction(iSetupApiLibNumber, "SetupDiGetDeviceRegistryPropertyW")
        SetupDiDestroyDeviceInfoList = GetFunction(iSetupApiLibNumber, "SetupDiDestroyDeviceInfoList")

        If Not SetupDiGetClassDevs
            fResult = #False
            Debug "Error LibraryInit()->GetFunction() SetupDiGetClassDevs"
        EndIf
        If Not SetupDiEnumDeviceInfo
            fResult = #False
            Debug "Error LibraryInit()->GetFunction() SetupDiEnumDeviceInfo"
        EndIf
        If Not SetupDiOpenDevRegKey
            fResult = #False
            Debug "Error LibraryInit()->GetFunction() SetupDiOpenDevRegKey"
        EndIf
        If Not SetupDiGetDeviceRegistryProperty
            fResult = #False
            Debug "Error LibraryInit()->GetFunction() SetupDiGetDeviceRegistryProperty"
        EndIf
        If Not SetupDiDestroyDeviceInfoList
            fResult = #False
            Debug "Error LibraryInit()->GetFunction() SetupDiDestroyDeviceInfoList"
        EndIf
    EndIf

    If Not fResult
        If iSetupApiLibNumber
            CloseLibrary(iSetupApiLibNumber)
        EndIf
        iReturn = #Null
    EndIf

    ProcedureReturn iReturn
EndProcedure


Procedure LibraryExit(iLibNumber.i)

    If iLibNumber
        CloseLibrary(iLibNumber)
    EndIf

EndProcedure


Procedure.s GetSerialName(hDeviceInfoSet.i, *uSpDevInfoData.uSpDevInfoData)
    Protected sReturn.s, iResult.i, hDeviceRegistryKey.i
    Protected iRegKeyType.l, sDeviceNameBuffer.s, iLength.l

    hDeviceRegistryKey = SetupDiOpenDevRegKey(hDeviceInfoSet, *uSpDevInfoData,
        #DICS_FLAG_GLOBAL, 0, #DIREG_DEV, #KEY_QUERY_VALUE)
    If Not hDeviceRegistryKey
        Debug "Error GetSerialName()->SetupDiOpenDevRegKey() Failed To open a registry key For device-specific configuration information"
    Else
        iLength = 256
        sDeviceNameBuffer = Space(iLength)

        iResult = RegQueryValueEx_(hDeviceRegistryKey, @"PortName", #Null, @iRegKeyType, @sDeviceNameBuffer, @iLength)
        If (iResult <> #ERROR_SUCCESS)
            Debug "Error " + iResult + " GetSerialName()->RegQueryValueEx_() Can Not Read registry value PortName For device"
        Else
            RegCloseKey_(hDeviceRegistryKey)
            sReturn = sDeviceNameBuffer
        EndIf
    EndIf

    ProcedureReturn sReturn
EndProcedure


Procedure.s GetSerialDescription(hDeviceInfoSet.i, *uSpDevInfoData.uSpDevInfoData)
    Protected sReturn.s, iResult.i
    Protected sDescriptionBuffer.s, iLength.l, iPropRegDataType.l

    iLength = 256
    sDescriptionBuffer = Space(iLength)

    iResult = SetupDiGetDeviceRegistryProperty(hDeviceInfoSet, *uSpDevInfoData, #SPDRP_DEVICEDESC, @iPropRegDataType,
         @sDescriptionBuffer, iLength, @iLength)
    If ((iResult = #ERROR_INVALID_DATA) Or (iResult = 0))
        Debug "Error " + iResult +
            " GetSerialDescription()->SetupDiGetDeviceRegistryProperty() Can not read registry value PortName for device"
    Else
        sReturn = sDescriptionBuffer
    EndIf

    ProcedureReturn sReturn
EndProcedure


Procedure.i GetSerialPortsList(List DeviceInfoList.uDeviceInfo())
    Protected iReturn.i, fResult.i
    Protected hDeviceInfoSet.i, uSpDevInfoData.uSpDevInfoData, iMemberIndex.l
    Protected uGuidDevInterfaceComport.GUID

    ; DataSection
    ;   GuidDevInterfaceComport:
    ;   Data.l $86E0D1E0
    ;   Data.w $8089, $11D0
    ;   Data.b $9C, $E4, $08, $00, $3E, $30, $1F, $73
    ; EndDataSection
    With uGuidDevInterfaceComport
        \Data1 = $86E0D1E0
        \Data2 = $8089
        \Data3 = $11D0
        \Data4[0] = $9C
        \Data4[1] = $E4
        \Data4[2] = $08
        \Data4[3] = $00
        \Data4[4] = $3E
        \Data4[5] = $30
        \Data4[6] = $1F
        \Data4[7] = $73
    EndWith

    hDeviceInfoSet = SetupDiGetClassDevs(@uGuidDevInterfaceComport, #Null, #Null, (#DIGCF_PRESENT | #DIGCF_DEVICEINTERFACE))
    fResult = Bool(hDeviceInfoSet <> #INVALID_HANDLE_VALUE)
    If Not fResult
        hDeviceInfoSet = #Null
        Debug "Error GetSerialPortsList()->SetupDiGetClassDevs() Failed to get device information set for the COM ports"
    EndIf

    If fResult
        iMemberIndex = 0
        Repeat
            ClearStructure(@uSpDevInfoData, uSpDevInfoData)
            uSpDevInfoData\cbSize = SizeOf(uSpDevInfoData)
            fResult = SetupDiEnumDeviceInfo(hDeviceInfoSet, iMemberIndex, @uSpDevInfoData)
            If Not fResult
                Break   ; No more devices in the device information set.
            Else
                AddElement(DeviceInfoList())
                With DeviceInfoList()
                    \sName = GetSerialName(hDeviceInfoSet, @uSpDevInfoData)
                    \sDescription = GetSerialDescription(hDeviceInfoSet, @uSpDevInfoData)
                EndWith
            EndIf
            iMemberIndex + 1
        ForEver
        fResult = #True
    EndIf

    If hDeviceInfoSet
        SetupDiDestroyDeviceInfoList(hDeviceInfoSet)
    EndIf

    iReturn = ListSize(DeviceInfoList())

    ProcedureReturn iReturn
EndProcedure


; #################


Define iLibNumber.i
NewList DeviceInfoList.uDeviceInfo()

iLibNumber = LibraryInit()
If iLibNumber
    Debug "Serial Ports: " + GetSerialPortsList(DeviceInfoList())

    ForEach DeviceInfoList()
        With DeviceInfoList()
            Debug \sName + " - " + \sDescription
        EndWith
    Next

    LibraryExit(iLibNumber)
EndIf


; [12:42:08] Waiting for executable to start...
; [12:42:08] Executable type: Windows - x64  (64bit, Unicode, Thread, Purifier)
; [12:42:08] Executable started.
; [12:42:08] [Debug] Serial Ports: 4
; [12:42:08] [Debug] COM1 - Kommunikationsanschluss
; [12:42:08] [Debug] COM7 - USB Serial Port
; [12:42:08] [Debug] COM8 - USB Serial Port
; [12:42:08] [Debug] COM5 - Prolific USB-to-Serial Comm Port
; [12:42:08] The Program execution has finished.

Re: usb-to-serial: how to get associated display name

Posted: Thu May 23, 2024 4:12 pm
by morosh
As a side note:

As hobby project I'm writing OBD software for motorcycles.
The only really reliable adapters are using chips from FTDI.
With Prolific I (or better: the users) came very often in trouble.
The devices are already bought by the school where I teach, till now they work correctly.

Thanks for the info

Re: usb-to-serial: how to get associated display name

Posted: Thu May 23, 2024 5:55 pm
by infratec
The main problem with OBD is ... timing.

USB is not really a serial protocol. Normally it waits until a buffer is full, then the buffer is send.

But if you have to send single bytes in a specific time, you can get into trouble.
To avoid this the FTDI driver allows to set a maximum delay value, befor the buffer is send.
You can set it to 1ms, then the device behaves (more or less) like a real serial device.

Re: usb-to-serial: how to get associated display name

Posted: Thu May 23, 2024 8:48 pm
by HeX0R
I'm using the registry for that.
btw. nothing more unconvinient, than tools, which scan the COM ports at start and keep those fixed.
When you connect your COM adapter afterwards you have to restart the tool to find the new COM Port.
Windows has a nice feature, where you get signaled when a new COM port is added/removed, this is what all my (windows) COM port tools are using:

Code: Select all

;use SetWindowCallback(@CallBack_NewDevice()) for your main window
Procedure CallBack_NewDevice(WindowID, Message, wParam, lParam)              ;add/delete live connecting USB devices (windows only)
	Protected Result, a$, F
	Protected *db.DEV_BROADCAST_HDR
	
	Result = #PB_ProcessPureBasicEvents
	Select Message
			
		Case #WM_DEVICECHANGE
			If wParam = #DBT_DEVICEARRIVAL
				*db = lParam
				If *db\dbch_devicetype = #DBT_DEVTYP_PORT
					a$ = PeekS(*db + 12)
					Debug a$ + " arrived!"
					F  = #True
					PushListPosition(AvailableComs())
					ForEach AvailableComs()
						If AvailableComs() = a$
							F = #False
							Break
						EndIf
					Next
					If F
						AddElement(AvailableComs())
						AvailableComs() = a$
					EndIf
					PopListPosition(AvailableComs())
				EndIf
			ElseIf wParam = #DBT_DEVICEREMOVECOMPLETE
				*db = lParam
				If *db\dbch_devicetype = #DBT_DEVTYP_PORT
					a$ = PeekS(*db + 12)
					Debug a$ + " removed!"
					If ListIndex(AvailableComs()) <> - 1 And AvailableComs() = a$
							;This was our active Loggerport
						CloseSerialPorts()
						DeleteElement(AvailableComs())
						If ListSize(AvailableComs()) > 0
							FirstElement(AvailableComs())
						Else
							AddElement(AvailableComs())
							AvailableComs() = "COM1"
						EndIf
					Else
							;This was something Else...
						PushListPosition(AvailableComs())
						ForEach AvailableComs()
							If AvailableComs() = a$
								DeleteElement(AvailableComs())
								Break
							EndIf
						Next
						PopListPosition(AvailableComs())
					EndIf
				EndIf
			EndIf
			
	EndSelect
	
	ProcedureReturn Result
EndProcedure