Related
This is probably a pretty basic question, but it is giving me a problem. I have an FTDI USB3 development board that I want to drive by pressing buttons on a Qt form. The FTDI interface examples are all static functions, so that the generated values can be used elsewhere. Here is what I hoped would work, but pressing the button on the Qt form does not seem to call the FTDI static function (no visible response in the Qt debug). Any suggestions are welcome!
//Click button on Qt form
void StarLight::on_pushBtnFind_clicked()
{
FT_STATUS displayDevicesMethod2(void);
QApplication::processEvents();
}
// Method 2 for displaying the list of devices connected.
static FT_STATUS displayDevicesMethod2(void)
{
FT_STATUS ftStatus;
FT_HANDLE ftHandle = NULL;
// Get and display the list of devices connected
// First call FT_CreateDeviceInfoList to get the number of connected devices.
// Then call FT_GetDeviceInfoList or FT_GetDeviceInfoDetail to display device info.
// Device info: Flags (usb speed), device type (600 e.g.), device ID (vendor, product),
handle for subsequent data access.
DWORD numDevs = 0;
ftStatus = FT_CreateDeviceInfoList(&numDevs); // Build a list and return number
connected.
if (FT_FAILED(ftStatus))
{
printf("Failed to create a device list, status = %lu\n", ftStatus);
}
printf("Successfully created a device list.\n\tNumber of connected devices: %lu\n",
numDevs);
// Method 2: using FT_GetDeviceInfoDetail
if (!FT_FAILED(ftStatus) && numDevs > 0)
{
ftHandle = NULL;
DWORD Flags = 0;
DWORD Type = 0;
DWORD ID = 0;
char SerialNumber[16] = { 0 };
char Description[32] = { 0 };
for(DWORD i = 0; i <numDevs; i++)
{
ftStatus = FT_GetDeviceInfoDetail(i, &Flags, &Type, &ID, NULL, SerialNumber,
Description, &ftHandle);
if (!FT_FAILED(ftStatus))
{
printf("Device[%lu] (using FT_GetDeviceInfoDetail)\n", i);
printf("\tFlags: 0x%lx %s | Type: %lu | ID: 0x%08lX | ftHandle=0x%p\n",
Flags,
Flags & FT_FLAGS_SUPERSPEED? "[USB 3]":
Flags & FT_FLAGS_HISPEED? "[USB 2]":
Flags & FT_FLAGS_OPENED? "[OPENED]": "",
Type,
ID,
ftHandle);
printf("\tSerialNumber=%s\n", SerialNumber);
printf("\tDescription=%s\n", Description);
}
}
}
return ftStatus;
}
I'm working on extending a receipt printing Serial Port (COM) interface for a thermal receipt printer to use a USB interface without needing a Virtual Serial Port. I have a working prototype that will enumerate over the attached USB devices, locate the USB path for a device with a specific vendor id and product id, and open a connection to the device using CreateFile().
The existing Serial Port code uses the Windows API wrapped in a set of functions. The approach I'm taking is to add additional code using the same set of functions but that depend on a USB connection rather than a Serial Port connection. I have previously used the same approach to allow the use of a kitchen printer over either a Serial Port or over a WiFi/LAN connection with minimal changes to existing code successfully.
Unfortunately the existing code that uses the function library depends on the functions to use ReadFile() with a time out specified so that if the thermal printer does not respond to a status request within a reasonable time, the application can mark it as down and allow operations to continue or to use a backup or secondary printer.
How do I specify a time out for a ReadFile() on a file handle from CreateFile() that opens a connection to a communications devices using a USB pathname?
A consideration is this is multi-threaded code used for more than one serial communications device (receipt printer, kitchen printer, scale, etc.) however a thread will have exclusive access to a particular device (kitchen printing functionality opens serial port to kitchen printer only, scale reading functionality opens serial port to scale only, etc.).
In the existing Serial Port code, the function used to set timeouts, SetCommTimeouts(), for a Serial Port connection opened with CreateFile() does not work for a USB connection opened with CreateFile() (see SetupComm, SetCommState, SetCommTimeouts fail with USB device). This means some other mechanism is needed to provide a way to allow for an I/O failure due to a time out when using a USB device pathname.
We are using the following code segment to open a Serial Port, whether to a hardware COM port or a Virtual Serial Port emulating a hardware COM port:
// see Microsoft document HOWTO: Specify Serial Ports Larger than COM9.
// https://support.microsoft.com/en-us/kb/115831
// CreateFile() can be used to get a handle to a serial port. The "Win32 Programmer's Reference" entry for "CreateFile()"
// mentions that the share mode must be 0, the create parameter must be OPEN_EXISTING, and the template must be NULL.
//
// CreateFile() is successful when you use "COM1" through "COM9" for the name of the file;
// however, the value INVALID_HANDLE_VALUE is returned if you use "COM10" or greater.
//
// If the name of the port is \\.\COM10, the correct way to specify the serial port in a call to
// CreateFile() is "\\\\.\\COM10".
//
// NOTES: This syntax also works for ports COM1 through COM9. Certain boards will let you choose
// the port names yourself. This syntax works for those names as well.
wsprintf(wszPortName, TEXT("\\\\.\\COM%d"), usPortId);
/* Open the serial port. */
/* avoid to failuer of CreateFile */
for (i = 0; i < 10; i++) {
hHandle = CreateFile (wszPortName, /* Pointer to the name of the port, PifOpenCom() */
GENERIC_READ | GENERIC_WRITE, /* Access (read-write) mode */
0, /* Share mode */
NULL, /* Pointer to the security attribute */
OPEN_EXISTING,/* How to open the serial port */
0, /* Port attributes */
NULL); /* Handle to port with attribute */
/* to copy */
/* If it fails to open the port, return FALSE. */
if ( hHandle == INVALID_HANDLE_VALUE ) { /* Could not open the port. */
dwError = GetLastError ();
if (dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_INVALID_NAME || dwError == ERROR_ACCESS_DENIED) {
LeaveCriticalSection(&g_SioCriticalSection);
// the COM port does not exist. probably a Virtual Serial Communications Port
// from a USB device which was either unplugged or turned off.
// or the COM port or Virtual Serial Communications port is in use by some other application.
return PIF_ERROR_COM_ACCESS_DENIED;
}
PifLog (MODULE_PIF_OPENCOM, LOG_ERROR_PIFSIO_CODE_01);
PifLog (MODULE_ERROR_NO(MODULE_PIF_OPENCOM), (USHORT)dwError);
PifLog(MODULE_DATA_VALUE(FAULT_AT_PIFOPENCOM), usPortId);
PifSleep(500);
} else {
break;
}
}
if ( hHandle == INVALID_HANDLE_VALUE ) { /* Could not open the port. */
wsprintf(wszDisplay, TEXT("CreateFile, COM%d, Last Error =%d\n"), usPortId, dwError);
OutputDebugString(wszDisplay);
LeaveCriticalSection(&g_SioCriticalSection);
return PIF_ERROR_COM_ERRORS;
}
/* clear the error and purge the receive buffer */
dwError = (DWORD)(~0); // set all error code bits on
ClearCommError(hHandle, &dwError, NULL);
PurgeComm( hHandle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;
The ReadFile() is wrapped within a function and looks like:
fResult = ReadFile(hHandle, pBuffer, (DWORD)usBytes, &dwBytesRead, NULL);
if (PifSioCheckPowerDown(usPort, aPifSioTable) == TRUE) {
return PIF_ERROR_COM_POWER_FAILURE;
}
if (fResult) {
if (!dwBytesRead) return PIF_ERROR_COM_TIMEOUT;
return (SHORT)dwBytesRead;
} else {
SHORT sErrorCode = 0; // error code from PifSubGetErrorCode(). must call after GetLastError().
dwError = GetLastError();
PifLog (MODULE_PIF_READCOM, LOG_ERROR_PIFSIO_CODE_06);
PifLog (MODULE_ERROR_NO(MODULE_PIF_READCOM), (USHORT)dwError);
sErrorCode = PifSubGetErrorCode(hHandle);
PifLog (MODULE_ERROR_NO(MODULE_PIF_READCOM), (USHORT)abs(sErrorCode));
PifLog (MODULE_DATA_VALUE(MODULE_PIF_READCOM), usPort);
return (sErrorCode);
}
I found a number of similar posted questions which involved pipes however the same approach of using overlapped I/O applies.
Breaking ReadFile() blocking - Named Pipe (Windows API)
Win32 API: ReadFile timeout
Device driver: Windows ReadFile function timeout
I also found the following article on the web Peter's blog: Getting a handle on usbprint.sys which provides code and a description of how to find a USB pathname for a USB connected device. I have used some of that code sample in the class below.
I also found an article on codeproject.com, Enumerating windows device by Chuan-Liang Teng which contained an example of enumerating over the connected USB devices and interrogating various settings and details about the devices. The code from that article, though old, was helpful though not necessary for this particular application.
I have a prototype C++ class using overlapped I/O which seems to be replicating the behavior seen with a Serial Port connection using a USB connection to a thermal printer. The full source and Visual Studio 2017 solution and project files are in my GitHub repository https://github.com/RichardChambers/utilities_tools/tree/main/UsbWindows as this snip has the most pertinent parts.
I have done a simple test with modified code in the point of sale application and am now in the process of integrating this into the existing thermal receipt printer source code which already works with a Serial Port.
#include <windows.h>
#include <setupapi.h>
#include <initguid.h>
#include <iostream>
// This is the GUID for the USB device class.
// It is defined in the include file Usbiodef.h of the Microsoft Windows Driver Kit.
// See also https://msdn.microsoft.com/en-us/library/windows/hardware/ff545972(v=vs.85).aspx which
// provides basic documentation on this GUID.
DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);
class UsbSerialDevice
{
public:
// See https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-server-using-overlapped-i-o?redirectedfrom=MSDN
// to implement time outs for Write and for Read.
UsbSerialDevice(const wchar_t* wszVendorIdIn = nullptr);
~UsbSerialDevice();
int CreateEndPoint(const wchar_t* wszVendorId = nullptr, DWORD dwDesiredAccess = (GENERIC_READ | GENERIC_WRITE));
void CloseEndPoint(void);
int ListEndPoint(const wchar_t* wszVendorIdIn);
int ReadStream(void* bString, size_t nBytes);
int WriteStream(void* bString, size_t nBytes);
DWORD SetWriteTimeOut(DWORD msTimeout);
DWORD SetReadTimeOut(DWORD msTimeout);
DWORD m_dwError; // GetLastError() for last action
DWORD m_dwErrorWrite; // GetLastError() for last write
DWORD m_dwErrorRead; // GetLastError() for last read
DWORD m_dwBytesWritten; // number of bytes last write
DWORD m_dwBytesRead; // number of bytes last read
DWORD m_dwWait; // WaitForSingleObject() return value
private:
HANDLE m_hFile;
OVERLAPPED m_oOverlap;
COMMTIMEOUTS m_timeOut;
const unsigned short m_idLen = 255;
wchar_t m_wszVendorId[255 + 1] = { 0 };
};
UsbSerialDevice::UsbSerialDevice(const wchar_t* wszVendorIdIn) :
m_dwError(0),
m_dwErrorWrite(0),
m_dwErrorRead(0),
m_dwBytesWritten(0),
m_dwBytesRead(0),
m_dwWait(0),
m_hFile(INVALID_HANDLE_VALUE)
{
memset(&m_oOverlap, 0, sizeof(m_oOverlap));
m_oOverlap.hEvent = INVALID_HANDLE_VALUE;
if (wszVendorIdIn != nullptr) ListEndPoint(wszVendorIdIn);
}
void UsbSerialDevice::CloseEndPoint(void )
{
if (m_hFile && m_hFile != INVALID_HANDLE_VALUE) CloseHandle(m_hFile);
if (m_oOverlap.hEvent && m_oOverlap.hEvent != INVALID_HANDLE_VALUE) CloseHandle(m_oOverlap.hEvent);
}
UsbSerialDevice::~UsbSerialDevice()
{
CloseEndPoint();
}
/*
* Returns: -1 - file handle is invalid
* 0 - write failed. See m_dwErrorWrite for GetLastError() value
* 1 - write succedded.
*/
int UsbSerialDevice::WriteStream(void* bString, size_t nBytes)
{
SetLastError(0);
m_dwError = m_dwErrorWrite = 0;
m_dwBytesWritten = 0;
m_dwWait = WAIT_FAILED;
if (m_hFile && m_hFile != INVALID_HANDLE_VALUE) {
BOOL bWrite = WriteFile(m_hFile, bString, nBytes, 0, &m_oOverlap);
m_dwError = m_dwErrorWrite = GetLastError();
if (!bWrite && m_dwError == ERROR_IO_PENDING) {
SetLastError(0);
m_dwError = m_dwErrorWrite = 0;
m_dwWait = WaitForSingleObject(m_oOverlap.hEvent, m_timeOut.WriteTotalTimeoutConstant);
BOOL bCancel = FALSE;
switch (m_dwWait) {
case WAIT_OBJECT_0: // The state of the specified object is signaled.
break;
case WAIT_FAILED: // The function has failed. To get extended error information, call GetLastError.
m_dwError = m_dwErrorWrite = GetLastError();
bCancel = CancelIo(m_hFile);
break;
case WAIT_TIMEOUT: // The time-out interval elapsed, and the object's state is nonsignaled.
case WAIT_ABANDONED: // thread owning mutex terminated before releasing or signaling object.
bCancel = CancelIo(m_hFile);
m_dwError = m_dwErrorRead = ERROR_COUNTER_TIMEOUT;
break;
}
bWrite = GetOverlappedResult(m_hFile, &m_oOverlap, &m_dwBytesRead, FALSE);
}
return bWrite; // 0 or FALSE if failed, 1 or TRUE if succeeded.
}
return -1;
}
/*
* Returns: -1 - file handle is invalid
* 0 - read failed. See m_dwErrorRead for GetLastError() value
* 1 - read succedded.
*/
int UsbSerialDevice::ReadStream(void* bString, size_t nBytes)
{
SetLastError(0);
m_dwError = m_dwErrorRead = 0;
m_dwBytesRead = 0;
m_dwWait = WAIT_FAILED;
if (m_hFile && m_hFile != INVALID_HANDLE_VALUE) {
BOOL bRead = ReadFile(m_hFile, bString, nBytes, &m_dwBytesRead, &m_oOverlap);
m_dwError = m_dwErrorRead = GetLastError();
if (!bRead && m_dwError == ERROR_IO_PENDING) {
SetLastError(0);
m_dwError = m_dwErrorRead = 0;
m_dwWait = WaitForSingleObject(m_oOverlap.hEvent, m_timeOut.ReadTotalTimeoutConstant);
BOOL bCancel = FALSE;
switch (m_dwWait) {
case WAIT_OBJECT_0: // The state of the specified object is signaled.
break;
case WAIT_FAILED: // The function has failed. To get extended error information, call GetLastError.
m_dwError = m_dwErrorWrite = GetLastError();
bCancel = CancelIo(m_hFile);
break;
case WAIT_TIMEOUT: // The time-out interval elapsed, and the object's state is nonsignaled.
case WAIT_ABANDONED: // thread owning mutex terminated before releasing or signaling object.
bCancel = CancelIo(m_hFile);
m_dwError = m_dwErrorRead = ERROR_COUNTER_TIMEOUT;
break;
}
bRead = GetOverlappedResult(m_hFile, &m_oOverlap, &m_dwBytesRead, FALSE);
}
return bRead; // 0 or FALSE if failed, 1 or TRUE if succeeded.
}
return -1;
}
int UsbSerialDevice::ListEndPoint(const wchar_t* wszVendorIdIn)
{
m_dwError = ERROR_INVALID_HANDLE;
if (wszVendorIdIn == nullptr) return 0;
HDEVINFO hDevInfo;
// we need to make sure the vendor and product id codes are in lower case
// as this is needed for the CreateFile() function to open the connection
// to the USB device correctly. this lower case conversion applies to
// any alphabetic characters in the identifier.
//
// for example "VID_0FE6&PID_811E" must be converted to "vid_0fe6&pid_811e"
wchar_t wszVendorId[256] = { 0 };
for (unsigned short i = 0; i < 255 && (wszVendorId[i] = towlower(wszVendorIdIn[i])); i++);
// We will try to get device information set for all USB devices that have a
// device interface and are currently present on the system (plugged in).
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if (hDevInfo != INVALID_HANDLE_VALUE)
{
DWORD dwMemberIdx;
BOOL bContinue = TRUE;
SP_DEVICE_INTERFACE_DATA DevIntfData;
// Prepare to enumerate all device interfaces for the device information
// set that we retrieved with SetupDiGetClassDevs(..)
DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
dwMemberIdx = 0;
// Next, we will keep calling this SetupDiEnumDeviceInterfaces(..) until this
// function causes GetLastError() to return ERROR_NO_MORE_ITEMS. With each
// call the dwMemberIdx value needs to be incremented to retrieve the next
// device interface information.
for (BOOL bContinue = TRUE; bContinue; ) {
PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData;
SP_DEVINFO_DATA DevData;
DWORD dwSize;
dwMemberIdx++;
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, dwMemberIdx, &DevIntfData);
if (GetLastError() == ERROR_NO_MORE_ITEMS) break;
// As a last step we will need to get some more details for each
// of device interface information we are able to retrieve. This
// device interface detail gives us the information we need to identify
// the device (VID/PID), and decide if it's useful to us. It will also
// provide a DEVINFO_DATA structure which we can use to know the serial
// port name for a virtual com port.
DevData.cbSize = sizeof(DevData);
// Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
// a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
// of zero, and a valid RequiredSize variable. In response to such a call,
// this function returns the required buffer size at dwSize.
SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);
// Allocate memory for the DeviceInterfaceDetail struct. Don't forget to
// deallocate it later!
DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, DevIntfDetailData, dwSize, &dwSize, &DevData))
{
if (wcsstr(DevIntfDetailData->DevicePath, wszVendorId)) {
wcscpy_s(m_wszVendorId, DevIntfDetailData->DevicePath);
}
}
HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
}
SetupDiDestroyDeviceInfoList(hDevInfo);
}
return 0;
}
int UsbSerialDevice::CreateEndPoint(const wchar_t* wszVendorIdIn, DWORD dwDesiredAccess)
{
if (wszVendorIdIn) {
ListEndPoint(wszVendorIdIn);
}
m_dwError = ERROR_INVALID_HANDLE;
// Finally we can start checking if we've found a useable device,
// by inspecting the DevIntfDetailData->DevicePath variable.
//
// The DevicePath looks something like this for a Brecknell 67xx Series Serial Scale
// \\?\usb#vid_1a86&pid_7523#6&28eaabda&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
//
// The VID for a particular vendor will be the same for a particular vendor's equipment.
// The PID is variable for each device of the vendor.
//
// As you can see it contains the VID/PID for the device, so we can check
// for the right VID/PID with string handling routines.
// See https://github.com/Microsoft/Windows-driver-samples/blob/master/usb/usbview/vndrlist.h
// See https://blog.peter.skarpetis.com/archives/2005/04/07/getting-a-handle-on-usbprintsys/
// which describes a sample USB thermal receipt printer test application.
SetLastError(0);
m_hFile = CreateFile(m_wszVendorId, dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, 0);
if (m_hFile == INVALID_HANDLE_VALUE) {
m_dwError = GetLastError();
// wprintf(_T(" CreateFile() failed. GetLastError() = %d\n"), m_dwError);
}
else {
m_oOverlap.hEvent = CreateEvent(
NULL, // default security attribute
TRUE, // manual-reset event
TRUE, // initial state = signaled
NULL); // unnamed event object
m_timeOut.ReadIntervalTimeout = 0;
m_timeOut.ReadTotalTimeoutMultiplier = 0;
m_timeOut.ReadTotalTimeoutConstant = 5000;
m_timeOut.WriteTotalTimeoutMultiplier = 0;
m_timeOut.WriteTotalTimeoutConstant = 5000;
m_dwError = 0; // GetLastError();
return 1;
}
return 0;
}
I'm working with an HP Reverb G2 headset. If it's not plugged into a USB 3.0 port, it does not work. The device shows up in device manager, but right clicking it and selecting properties shows the following device error:
Reverb G2 properties
In that picture, the "Device status" text box on the "General" tab of the device's properties reads:
This device is working properly.
Device Error : 0x80040203 : E_DEVICE_USB_SPEED_TOO_SLOW
I need to be able to detect this specific state in code (C++), but I haven't figured out a way yet. Using the functions in "Setupapi.h", I'm able to find the right device. I can then query for different info using SetupDiGetDevicePropertyW. But I haven't found anything that gives me the info I need.
I was hoping querying DEVPKEY_Device_ProblemCode would do what I needed, but it's not returning any problem. I suppose because the above quote does start with "This device is working properly."
Is there any way for me to obtain that device error code, or the entire contents of that text box?
Here's sample code querying for the Problem Code:
long GetReverbProblemCode()
{
long returnValue = -1;
HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_ALLCLASSES);
if (hDevInfo != INVALID_HANDLE_VALUE)
{
SP_DEVINFO_DATA deviceInfoData;
ZeroMemory(&deviceInfoData, sizeof(SP_DEVINFO_DATA));
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
int devindex = 0;
while (SetupDiEnumDeviceInfo(hDevInfo, devindex, &deviceInfoData))
{
devindex++;
DWORD requiredSize = 0;
DEVPROPTYPE ulPropertyType;
ZeroMemory(&gVRNative_szBuffer[0], sizeof(gVRNative_szBuffer));
if (SetupDiGetDevicePropertyW(hDevInfo, &deviceInfoData, &DEVPKEY_Device_FriendlyName, &ulPropertyType, (BYTE*)gVRNative_szBuffer, sizeof(gVRNative_szBuffer), &requiredSize, 0))
{
if (_wcsicmp(gVRNative_szBuffer, L"HP Reverb Virtual Reality Headset G2") == 0)
{
ZeroMemory(&propertyBuffer[0], sizeof(propertyBuffer));
if (SetupDiGetDevicePropertyW(hDevInfo, &deviceInfoData, &DEVPKEY_Device_ProblemCode, &ulPropertyType, (BYTE*)propertyBuffer, sizeof(propertyBuffer), &requiredSize, 0))
{
unsigned long deviceProblemCode = *((unsigned long*)propertyBuffer);
returnValue = deviceProblemCode;
}
break;
}
}
}
SetupDiDestroyDeviceInfoList(hDevInfo);
}
return returnValue;
}
I should have kept looking a little longer. Just needed to query for DEVPKEY_Device_DriverProblemDesc. That gives me a string containing the error text from the text box in the device's properties window:
Device Error : 0x80040203 : E_DEVICE_USB_SPEED_TOO_SLOW
Adjusted function call:
SetupDiGetDevicePropertyW(hDevInfo, &deviceInfoData, &DEVPKEY_Device_DriverProblemDesc, &ulPropertyType, (BYTE*)propertyBuffer, sizeof(propertyBuffer), &requiredSize, 0);
I am using Qt on windows platform.
i want to get and display vendor id and product id of a plugged usb device from my local system.
Below is my full source code to get the vendor id and product id from the usb device.
when i run the my qt application it does not throw me any errors .
so i plug the usb device into the system.
but my print statement displays the result as below
qDebug ()<<pDetData->DevicePath;
i get the result as 0x4
Whether i have any implementation mistakes in my source code ?
if so please guide me what i am doing wrong..
Have i missed out any other functions ?
Is it possible to get the vendor id and product id from the usb device based on my source code .( my implementation of the code ) ?
kindly find my source code below
static GUID GUID_DEVINTERFACE_USB_DEVICE = { 0xA5DCBF10L, 0x6530, 0x11D2,
{ 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } };
HANDLE hInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE,NULL,NULL,
DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if ( hInfo == INVALID_HANDLE_VALUE )
{
qDebug ()<<"invalid";
}
else
{
qDebug ()<<"valid handle";
SP_DEVINFO_DATA DeviceInfoData;
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
SP_INTERFACE_DEVICE_DATA Interface_Info;
Interface_Info.cbSize = sizeof(Interface_Info);
BYTE Buf[1024];
DWORD i;
DWORD InterfaceNumber= 0;
PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd =
(PSP_DEVICE_INTERFACE_DETAIL_DATA) Buf;
for (i=0;SetupDiEnumDeviceInfo(hInfo,i,&DeviceInfoData);i++)
{
DWORD DataT;
LPTSTR buffer = NULL;
DWORD buffersize = 0;
while (!SetupDiGetDeviceRegistryProperty( hInfo,
&DeviceInfoData,
SPDRP_DEVICEDESC,
&DataT,
(PBYTE)buffer,
buffersize,
&buffersize))
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// Change the buffer size.
if (buffer) LocalFree(buffer);
buffer = (LPTSTR)LocalAlloc(LPTR,buffersize);
}
else
{
// Insert error handling here.
break;
}
qDebug ()<<(TEXT("Device Number %i is: %s\n"),i, buffer);
if (buffer) LocalFree(buffer);
if ( GetLastError() != NO_ERROR
&& GetLastError() != ERROR_NO_MORE_ITEMS )
{
// Insert error handling here.
qDebug ()<<"return false";
}
InterfaceNumber = 0; // this just returns the first one, you can iterate on this
if (SetupDiEnumDeviceInterfaces(hInfo,
NULL,
&GUID_DEVINTERFACE_USB_DEVICE,
InterfaceNumber,
&Interface_Info))
{
printf("Got interface");
DWORD needed;
pspdidd->cbSize = sizeof(*pspdidd);
SP_DEVICE_INTERFACE_DETAIL_DATA *pDetData = NULL;
DWORD dwDetDataSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA)
+ 256;
SetupDiGetDeviceInterfaceDetail(hInfo,
&Interface_Info, pDetData,dwDetDataSize, NULL,
&DeviceInfoData);
qDebug ()<<pDetData->DevicePath;
//qDebug ()<<QString::fromWCharArray(pDetData->DevicePath);
}
else
{
printf("\nNo interface");
//ErrorExit((LPTSTR) "SetupDiEnumDeviceInterfaces");
if ( GetLastError() == ERROR_NO_MORE_ITEMS)
printf(", since there are no more items found.");
else
printf(", unknown reason.");
}
// Cleanup
SetupDiDestroyDeviceInfoList(hInfo);
qDebug ()<<"return true";
}
}
}
--------------- Edited to add: -----------------
Hi... the application comes and prints this
\?\usb#vid_04f2&pid_0111#5&1ba5a77f&0&2#{a5dcbf1 0-6530-11d2-901f-00c04fb951ed}
again it goes to while loop .... here it gets breaked in the else statement...
Qt Code:
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
// Change the buffer size.
if (buffer) LocalFree(buffer);
buffer = (LPTSTR)LocalAlloc(LPTR,buffersize);
} else {
qDebug ()<<"Here it quits the application";
// Insert error handling here. break;
}
Any ideas in this....
After this line:
SP_DEVICE_INTERFACE_DETAIL_DATA *pDetData = NULL;
Add this:
DWORD dwDetDataSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA) + 256;
pDetData = (_SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc (dwDetDataSize);
pDetData->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA);
After this line:
qDebug ()<<pDetData->DevicePath;
Add this:
free(pDetData);
But eventually you're going to have to read the docs for SetupDiGetDeviceInterfaceDetail(). Do it, there are lots of functions that work like this, with pointers to variable-size structs.
-------- Edited to add: --------
You're really going about this the wrong way. I see you're following the advice you got here, and it's taken you down the wrong path. idVendor and idProduct can only be found in the USB_DEVICE_DESCRIPTOR (MSDN).
It looks like you already know how to get the device handle (using CreateFile()). After that, you call WinUsb_Initialize() (MSDN). That gets you a WINUSB_INTERFACE_HANDLE.
Once you have that handle, you want to call WinUsb_GetDescriptor() (MSDN), with the DescriptorType set to URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE. I can't test code now, but it will look something like this:
USB_DEVICE_DESCRIPTOR udd;
memset(&udd, 0, sizeof(udd));
ULONG LengthTransferred = 0;
WinUsb_GetDescriptor(
winusb_interface_handle, // returned by WinUsbInitialize
URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE,
0, // not sure if we need this
0x409, // not sure if we need this
&udd,
sizeof(udd),
&LengthTransferred);
After that, udd->idVendor and udd->idProduct should have what you want.
Microsoft used to supply sample code for all this in the DDK, and probably still does, but I don't have access to one.
---------- Edited to add: ----------
Daniel K writes that the code should really be:
USB_DEVICE_DESCRIPTOR udd;
memset(&udd, 0, sizeof(udd));
ULONG LengthTransferred = 0;
WinUsb_GetDescriptor(
winusb_interface_handle, // returned by WinUsbInitialize
USB_DEVICE_DESCRIPTOR_TYPE, // Daniel K's suggestion
0,
0x409, // asks for English
&udd,
sizeof(udd),
&LengthTransferred);
See the comments for further details.
An alternative is to obtain the hardwareID which includes the VID and PID.
Call SetupDiGetDeviceRegistryProperty with SPDRP_HARDWAREID like so:
wchar_t *hardwareID;
// First get requiredLength
SetupDiGetDeviceRegistryProperty(deviceInfoList, &deviceInfoData, SPDRP_HARDWAREID, NULL, NULL, 0, &requiredLength);
hardwareID = (wchar_t*)(new char[requiredLength]());
// Second call to populate hardwareID
SetupDiGetDeviceRegistryProperty(deviceInfoList, &deviceInfoData, SPDRP_HARDWAREID, NULL, (PBYTE)hardwareID, requiredLength, NULL);
// Display the string
qDebug() << "hardwareID =" << QString::fromWCharArray(hardwareID);
This will give you a string like USB\ROOT_HUB20&VID1002&PID4396&REV0000 which you can parse.
*Note: not all devices will have a VID and PID, such as non-USB devices.
You are enumerating the device "interface". Interfaces do not have a VID or PID - device instances do. I am not sure whether you are enumerating the interfaces to narrow down the devices you are interested in, for because it's an error.
If you just enumerate the device instances, then you can call SetupDiGetDeviceProperty with DEVPKEY_Device_HardwareIds and then grep the resulting hardware id for the VID and PID.
If you are using the device interfaces on purpose, then you need to call SetupDiGetDeviceInterfaceDetail once with a NULL PSP_DEVICE_INTERFACE_DETAIL parameter and a valid requiredSize pointer to get the required size of memory to allocate, allocate that memory and then call the function again. In that call, the last parameter is a SP_DEVINFO_DATA structure, which once retrieved, you can use in the call to SetupDiGetDeviceProperty as I mentioned above.
I am maintaining an application that uses SetupDiGetDeviceInterfaceDetail() to find out information on the installed serial ports on the computer. I have noticed while testing this that there are some devices, such as my Lucent WinModem, that do not show up in that enumeration. It turns out that I am having a similar issue with a set of devices manufactured by my company that implement the serial port interface. My assumption is that there is something that is missing from the INF file for the device. Does anyone know what kinds of conditions can result in this kind of omission?
Edit: Here is a sample of the code that I am using to enumerate the serial ports. I have tried various combinations of flags but have not seen any significant difference in behaviour.
DEFINE_GUID(GUID_CLASS_COMPORT, 0x4d36e978, 0xe325, 0x11ce, 0xbf, 0xc1, \
0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18);
GUID *serial_port_guid = const_cast<GUID *>(&GUID_CLASS_COMPORT);
HDEVINFO device_info = INVALID_HANDLE_VALUE;
SP_DEVICE_INTERFACE_DETAIL_DATA *detail_data = 0;
device_info = SetupDiGetClassDevs(
serial_port_guid, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if(device_info != INVALID_HANDLE_VALUE)
{
uint4 const detail_data_size = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + 256;
detail_data = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA *>(new char[detail_data_size]);
SP_DEVICE_INTERFACE_DATA ifc_data;
bool more_interfaces = true;
int rcd;
memset(&ifc_data, 0, sizeof(ifc_data));
memset(detail_data, 0, detail_data_size);
ifc_data.cbSize = sizeof(ifc_data);
detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
for(uint4 index = 0; more_interfaces; ++index)
{
rcd = SetupDiEnumDeviceInterfaces(device_info, 0, serial_port_guid, index, &ifc_data);
if(rcd)
{
// we need to get the details of this device
SP_DEVINFO_DATA device_data = { sizeof(SP_DEVINFO_DATA) };
rcd = SetupDiGetDeviceInterfaceDetail(
device_info, &ifc_data, detail_data, detail_data_size, 0, &device_data);
if(rcd)
{
StrAsc device_path(detail_data->DevicePath);
byte friendly_name[256];
rcd = SetupDiGetDeviceRegistryProperty(
device_info, &device_data, SPDRP_FRIENDLYNAME, 0, friendly_name, sizeof(friendly_name), 0);
if(rcd)
{
std::for_each(
port_names.begin(),
port_names.end(),
update_friendly_name(
reinterpret_cast<char const *>(friendly_name)));
}
}
else
more_interfaces = false;
}
}
}
This is more of a question about the issue. When you call the function the first arg you pass in should be DeviceInfoSet which you likely got from the SetupDiGetClassDevs function. When you called the SetupDiGetClassDevs function what did you specify for the flags (Last Argument) Quoting Microsoft's Page on the function:
DIGCF_ALLCLASSES
Return a list of installed devices for all device setup classes or all
device interface classes.
DIGCF_DEVICEINTERFACE
Return devices that support device interfaces for the specified device
interface classes. This flag must be
set in the Flags parameter if the
Enumerator parameter specifies a
device instance ID.
DIGCF_DEFAULT
Return only the device that is associated with the system default
device interface, if one is set, for
the specified device interface
classes.
DIGCF_PRESENT
Return only devices that are currently present in a system.
DIGCF_PROFILE
Return only devices that are a part of the current hardware profile.
Depending on your choice the list of devices changes. For example The Present flag will only show devices plugged in actively.
UPDATE: Thanks for the sample code.
My question now, is if you want to know the friendly name of the modem why not use the same call but specify the Modem Guid instead of the COM Port? I have the Modem GUID being 4D36E96D-E325-11CE-BFC1-08002BE10318
In the registry I can see a value called 'AttachedTo' which specifies a COM Port. I'll have to research which property thats is tied to in the API. The registry key is at
HKLM\SYSTEM\CurrentControlSet\Control\Class{4D36E96D-E325-11CE-BFC1-08002BE10318}\
ANOTHER UPDATE:
Looking closer at the sample code. Based on this, if you are trying to get the device interface class that should return a SP_DEVICE_INTERFACE_DETAIL_DATA Structure. That wouldn't provide a way of getting the friendly name of the device. I believe instead you would want the device instance.
From what I've read, the Device Interface is used to as a way to get the device path which can be used to write to it.
One thing I did to test your code was try it again the Disk Device Interface. I made a few changes to get it to work on my system and it still isn't quite done. I think the one problem (probably more) is that I need to resize the DevicePath variable inbetween the SetupDiGetDeviceInterfaceDetail calls.
void Test()
{
GUID *serial_port_guid = const_cast<GUID *>(&GUID_DEVINTERFACE_DISK);
HDEVINFO device_info = INVALID_HANDLE_VALUE;
SP_DEVICE_INTERFACE_DETAIL_DATA detail_data;
device_info = SetupDiGetClassDevs(
serial_port_guid, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if(device_info != INVALID_HANDLE_VALUE)
{
//uint4 const detail_data_size = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);// + 256;
//detail_data = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA *>(new char[detail_data_size]);
SP_DEVICE_INTERFACE_DATA ifc_data;
bool more_interfaces = true;
int rcd;
memset(&ifc_data, 0, sizeof(ifc_data));
//memset(detail_data, 0, detail_data_size);
ifc_data.cbSize = sizeof(ifc_data);
detail_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
for(uint4 index = 0; more_interfaces; ++index)
{
rcd = SetupDiEnumDeviceInterfaces(device_info, 0, serial_port_guid, index, &ifc_data);
if(rcd)
{
// we need to get the details of this device
SP_DEVINFO_DATA device_data;
device_data.cbSize = sizeof(SP_DEVINFO_DATA);
DWORD intReqSize;
rcd = SetupDiGetDeviceInterfaceDetail(device_info, &ifc_data, 0, 0, &intReqSize, &device_data);
rcd = SetupDiGetDeviceInterfaceDetail(device_info, &ifc_data, &detail_data,intReqSize,&intReqSize,&device_data);
if(rcd)
{
//StrAsc device_path(detail_data->DevicePath);
byte friendly_name[256];
rcd = SetupDiGetDeviceRegistryProperty(
device_info, &device_data, SPDRP_FRIENDLYNAME, 0, friendly_name, sizeof(friendly_name), reinterpret_cast<DWORD *>(sizeof(friendly_name)));
if(rcd)
{
cout<<reinterpret_cast<char const *>(friendly_name);
}
else
{ int num = GetLastError();
}
}
else
{
int num = GetLastError();
}
}
else
more_interfaces = false;
}
}
SetupDiDestroyDeviceInfoList(device_info);
}
Also, in the INF, you may have to add the AddInterface directive to associate your driver with the correct interface.
I am not sure whether following hotfix will solve your problem as mentioned in
http://support.microsoft.com/kb/327868
One more intersting point: GUID_CLASS_COMPORT is obsolete from Win2000 onwards..
http://msdn.microsoft.com/en-us/library/bb663140.aspx
http://msdn.microsoft.com/en-us/library/bb663174.aspx
Another site I find having 9 different ways of enumeration. Best of luck.
http://www.naughter.com/enumser.html
You say your device is present and accessible, but are you accessing your device directly or are you accessing a port by name and number COMn:
I have a WinModem that is connected to my audio driver. I have no serial port, not even a simulated one.
I decided to punt on this and to do away with the dependency on the SetupDi() functions. Instead, I have written code that traverses the subkeys in HKEY_LOCAL_MACHINE\System\CurrentControlSet\Enum to find any drivers that support the serial port GUID. I have the feeling that this is what the device manager does. In case anyone is interested, my code fragment can be seen below:
typedef std::string StrAsc;
typedef std::pair<StrAsc, StrAsc> port_name_type;
typedef std::list<port_name_type> friendly_names_type;
void SerialPortBase::list_ports_friendly(friendly_names_type &port_names)
{
// we will first get the list of names. This will ensure that, at the very least, we get
// the same list of names as we would have otherwise obtained.
port_names_type simple_list;
list_ports(simple_list);
port_names.clear();
for(port_names_type::iterator pi = simple_list.begin(); pi != simple_list.end(); ++pi)
port_names.push_back(friendly_name_type(*pi, *pi));
// we will now need to enumerate the subkeys of the Enum registry key. We will need to
// consider many levels of the registry key structure in doing this so we will use a list
// of key handles as a stack.
HKEY enum_key ;
char const enum_key_name[] = "SYSTEM\\CurrentControlSet\\Enum";
StrAsc const com_port_guid("{4d36e978-e325-11ce-bfc1-08002be10318}");
char const class_guid_name[] = "ClassGUID";
char const friendly_name_name[] = "FriendlyName";
char const device_parameters_name[] = "Device Parameters";
char const port_name_name[] = "PortName";
long rcd = ::RegOpenKeyEx(
HKEY_LOCAL_MACHINE, enum_key_name, 0, KEY_READ, &enum_key);
char value_buff[MAX_PATH];
StrAsc port_name, friendly_name;
if(!port_names.empty() && rcd == ERROR_SUCCESS)
{
std::list<HKEY> key_stack;
key_stack.push_back(enum_key);
while(!key_stack.empty())
{
// we need to determine whether this key has a "ClassGUID" value
HKEY current = key_stack.front();
uint4 value_buff_len = sizeof(value_buff);
key_stack.pop_front();
rcd = ::RegQueryValueEx(
current,
class_guid_name,
0,
0,
reinterpret_cast<byte *>(value_buff),
&value_buff_len);
if(rcd == ERROR_SUCCESS)
{
// we will only consider devices that match the com port GUID
if(com_port_guid == value_buff)
{
// this key appears to identify a com port. We will need to get the friendly name
// and try to get the 'PortName' from the 'Device Parameters' subkey. Once we
// have those things, we can update the friendly name in our original list
value_buff_len = sizeof(value_buff);
rcd = ::RegQueryValueEx(
current,
friendly_name_name,
0,
0,
reinterpret_cast<byte *>(value_buff),
&value_buff_len);
if(rcd == ERROR_SUCCESS)
{
HKEY device_parameters_key;
rcd = ::RegOpenKeyEx(
current,
device_parameters_name,
0,
KEY_READ,
&device_parameters_key);
if(rcd == ERROR_SUCCESS)
{
friendly_name = value_buff;
value_buff_len = sizeof(value_buff);
rcd = ::RegQueryValueEx(
device_parameters_key,
port_name_name,
0,
0,
reinterpret_cast<byte *>(value_buff),
&value_buff_len);
if(rcd == ERROR_SUCCESS)
{
friendly_names_type::iterator fi;
port_name = value_buff;
fi = std::find_if(
port_names.begin(), port_names.end(), port_has_name(port_name));
if(fi != port_names.end())
fi->second = friendly_name;
}
::RegCloseKey(device_parameters_key);
}
}
}
}
else
{
// since this key did not have what we expected, we will need to check its
// children
uint4 index = 0;
rcd = ERROR_SUCCESS;
while(rcd == ERROR_SUCCESS)
{
value_buff_len = sizeof(value_buff);
rcd = ::RegEnumKeyEx(
current, index, value_buff, &value_buff_len, 0, 0, 0, 0);
if(rcd == ERROR_SUCCESS)
{
HKEY child;
rcd = ::RegOpenKeyEx(current, value_buff, 0, KEY_READ, &child);
if(rcd == ERROR_SUCCESS)
key_stack.push_back(child);
}
++index;
}
}
::RegCloseKey(current);
}
}
} // list_ports_friendly