Posted inInformation Technology / Raspberry Pi

Qt and USB – Pt. 5

My actual USB class changed a bit.

#ifndef LOGIKALUSB_H
#define LOGIKALUSB_H

/*
 *      Copyright (c) 2018 Roland Hughes and Logical Solutions  ALL RIGHTS RESERVED
 *
 *      This code is "as-is" without any warranty expressed or implied. You may not modify or distribute it
 *      without having purchased the right to do so from the copyright holder. This code is provided for
 *      demonstration purposes only. Usage of it implies your express agreement to hold both the author and
 *      copyright holder harmless from damages both real and perceived.
 */

#include 
#include 

class LogikalUSB : public QObject
{
    Q_OBJECT
public:
    explicit LogikalUSB(QObject *parent = nullptr);
    ~LogikalUSB();

    size_t deviceCount() { return dev_count;}
    void getDeviceReport(QString *txt);
    void getDeviceDBReport(QString *txt);
    libusb_device *getDevice(unsigned int sub);
    libusb_device_descriptor *getDeviceDescriptor(int sub);

public slots:

private:
    libusb_device **devs = nullptr; // pointer to pointer of device used to retrieve list
    libusb_context *ctx = nullptr;  // libusb session
    size_t dev_count = 0;           // number of entries in list
};

#endif // LOGIKALUSB_H

Most notably we now have a second report and the new copyright notice. Yes, I finally got tired of people taking my code without giving credit. The only time they do give me credit is when they’ve modified and broken it.

/*
 *      Copyright (c) 2018 Roland Hughes and Logical Solutions  ALL RIGHTS RESERVED
 *
 *      This code is "as-is" without any warranty expressed or implied. You may not modify or distribute it
 *      without having purchased the right to do so from the copyright holder. This code is provided for
 *      demonstration purposes only. Usage of it implies your express agreement to hold both the author and
 *      copyright holder harmless from damages both real and perceived.
 */


#include "logikalusb.h"
#include "logikalusbinfo.h"

#include 
#include 

LogikalUSB::LogikalUSB(QObject *parent) : QObject(parent)
{
    int r0_status;              // return values


    r0_status = libusb_init(&ctx);
    if (r0_status < 0)
    {
        // TODO:: need an error log util to call
        std::cout << "Init Error " << r0_status << std::endl;
        return;
    }

    libusb_set_debug(ctx, 3);   // verbosity level

    int x = libusb_get_device_list(ctx, &devs);
    if (x < 0)
    {
        std::cout << "Get Device Error " << x << std::endl;
        dev_count = 0;
    }
    else
    {
        dev_count = (unsigned int) x;
    }

}

LogikalUSB::~LogikalUSB()
{
    if (devs)
    {
        libusb_free_device_list(devs, 1);   // free the list, unref the devices
    }

    if (ctx)
    {
        libusb_exit(ctx);
    }
}

libusb_device *LogikalUSB::getDevice(unsigned int sub)
{
    libusb_device *retVal = nullptr;

    if (sub < dev_count)
    {
        retVal = devs[sub];
    }

    return retVal;
}

void LogikalUSB::getDeviceReport(QString *txt)
{
    QTextStream out(txt);

    for (size_t y=0; y < dev_count; y++)
    {
        libusb_device *dev = devs[y];
        libusb_device_descriptor desc;
        libusb_device_handle *hHandle = nullptr;

        int r0_status = libusb_get_device_descriptor(dev, &desc);
        if (r0_status < 0)
        {
            std::cout << "Get Device Descriptor Error " << r0_status << std::endl;
            continue;
        }

        out << "Number of possible configurations: " << (int)desc.bNumConfigurations << "   ";
        out << "Device Class: " << (int)desc.bDeviceClass<<"  ";
        out << "VendorID: " << desc.idVendor << "   ";
        out << "ProductID: " << desc.idProduct << endl;

        r0_status = libusb_open(dev, &hHandle);
        if (r0_status != LIBUSB_SUCCESS)
        {
            out << "Error " << r0_status << " opening USB device. Error text: " << libusb_error_name(r0_status) << endl;
        }
        else
        {
            unsigned char serialNumber[255] = { };
            unsigned char vendorName[1024] = { };
            unsigned char productName[1024] = { };

            (void) libusb_get_string_descriptor_ascii(hHandle, desc.iSerialNumber, serialNumber, sizeof(serialNumber));
            (void) libusb_get_string_descriptor_ascii(hHandle, desc.idVendor, vendorName, sizeof(vendorName));
            (void) libusb_get_string_descriptor_ascii(hHandle, desc.idProduct, productName, sizeof(productName));

            out << "Vendor: " << (char *)vendorName << "  Product: " << (char *)productName << "  Serial Number: " << (char *)serialNumber << endl;
        }

        libusb_config_descriptor *config;
        libusb_get_config_descriptor(dev, 0, &config);

        out << "Interfaces: " << (int)config->bNumInterfaces << "   ";

        const libusb_interface *inter;
        const libusb_interface_descriptor *interdesc;
        const libusb_endpoint_descriptor *epdesc;

        for(int i=0; i < (int)config->bNumInterfaces; i++)
        {
            inter = &config->interface[i];
            for(int j=0; jnum_altsetting; j++)
            {
                interdesc = &inter->altsetting[j];
                out << "Interface Number: " << (int)interdesc->bInterfaceNumber << " | ";
                out << "Number of endpoints: "<< (int)interdesc->bNumEndpoints << " | ";

                for(int k=0; k<(int)interdesc->bNumEndpoints; k++)
                {
                    epdesc = &interdesc->endpoint[k];
                    out << "Descriptor Type: " << (int)epdesc->bDescriptorType << " | ";
                    out << "EP Address: " << (int)epdesc->bEndpointAddress << " | ";
                }
            }
        }

        libusb_close(hHandle);  // don't forget to close
        out << endl << endl << endl;
    }
}


void LogikalUSB::getDeviceDBReport(QString *txt)
{
    QTextStream out(txt);

    for (size_t y=0; y < dev_count; y++)
    {
        libusb_device *dev = devs[y];
        libusb_device_descriptor desc;

        int r0_status = libusb_get_device_descriptor(dev, &desc);
        if (r0_status < 0)
        {
            std::cout << "Get Device Descriptor Error " << r0_status << std::endl;
            continue;
        }

        out << "Number of possible configurations: " << (int)desc.bNumConfigurations << "   ";
        out << "Device Class: " << (int)desc.bDeviceClass<<"  ";
        unsigned long vendor = desc.idVendor;
        out << "VendorID: " << vendor << "   " << LogikalUSBInfo::getInstance()->vendorName(QString("%1").arg(vendor, 4, 16, QChar('0'))) << endl;
        unsigned long product = desc.idProduct;
        out << "ProductID: " << product << "   "
            << LogikalUSBInfo::getInstance()->deviceName(QString("%1").arg(vendor, 4, 16, QChar('0')),  QString("%1").arg(product, 4, 16, QChar('0')))
            << endl << endl << endl;
    }
}

 

The last method is the only thing worthy of new discussion. libusb passes ID values back as integers and we need them as HEX strings padded with leading zeros.

QString("%1").arg(vendor, 4, 16, QChar('0'))

That’s where the magic happens. Our width is 4, base is 16 and padding character is ‘0’.

Let us now look at a small sample of edited output. First using only libusb.

Number of possible configurations: 1 
Device Class: 9 VendorID: 32903 ProductID: 36
Vendor: Product: Serial Number:


Number of possible configurations: 1 
Device Class: 9 VendorID: 7531 ProductID: 2
Vendor: Product: EHCI Host Controller Serial Number: 0000:00:1d.0

Number of possible configurations: 1 
Device Class: 0 VendorID: 2362 ProductID: 9488
Vendor: Product: Serial Number: 

Now let us look at the translations our database provided.

Number of possible configurations: 1   
Device Class: 9  
VendorID: 32903   Intel Corp.
ProductID: 36   Integrated Rate Matching Hub


Number of possible configurations: 1   
Device Class: 9  
VendorID: 7531   Linux Foundation
ProductID: 2   2.0 root hub


Number of possible configurations: 1   
Device Class: 0  
VendorID: 2362   Pixart Imaging, Inc.
ProductID: 9488   Optical Mouse

Please feel free to go back to the earlier post where I provided the full edited output if you want more detailed comparison. I even ran the program under sudo from the command line to give libusb it’s best chance, sans any udev rules changes.

This is why usb.ids has become popular on Windows machines. The devices are being made so simple/cheap they dont’ provide enough information to the OS. Granted, part of this is a throwback to the days of proprietary drivers and vendors trying to lock users into only “their” solutions, but, come on, my optical mouse? Yes, there was a time during the days of DOS and OS/2 when you had to use the vendor’s driver, but, that kind of went away with MOUSE.COM and Windows 3.1. I can kind of understand it for the really high end gaming mice with lots of extra buttons and mind numbing resolution, but, honestly, how many of us need a 1200+ DPI mouse to write blog posts and edit code in QtCreator?

So, when I get a bit more time to write code for free others will steal, I’m going to flesh out that stubbed method to pull down a shiny new copy of usb.ids from its place on the Web and feed it into our database creation.

It should be noted that SQLite is good enough for a one-off solution. As a general rule SQLite doesn’t share. When this program is running you cannot open a terminal window and type

sqlite3 usb_id_db

in your home directory to access the database reliably. Yes, I tested it right after I typed that and it seemed to work. I remember earlier testing, perhaps while slow record by record I/O was loading various tables, I got an error message about the database being locked. I know I’ve hit that issue at client sites from time to time.

It is my hope that some form of daemon would eventually be created for all x86 based platforms so all of these text file hashing things could go away. A daemon could, during regular OS updates, pull down the most current file from its place on the Web and create a shiny new database so everyone always had the latest list. A daemon would provide a list of services such as getVendorName(), getDeviceName(), etc. and all applications could just use it. If we are stuck with USB for the foreseable decade, the OS level tools to identify things need to get better.

Roland Hughes started his IT career in the early 1980s. He quickly became a consultant and president of Logikal Solutions, a software consulting firm specializing in OpenVMS application and C++/Qt touchscreen/embedded Linux development. Early in his career he became involved in what is now called cross platform development. Given the dearth of useful books on the subject he ventured into the world of professional author in 1995 writing the first of the "Zinc It!" book series for John Gordon Burke Publisher, Inc.

A decade later he released a massive (nearly 800 pages) tome "The Minimum You Need to Know to Be an OpenVMS Application Developer" which tried to encapsulate the essential skills gained over what was nearly a 20 year career at that point. From there "The Minimum You Need to Know" book series was born.

Three years later he wrote his first novel "Infinite Exposure" which got much notice from people involved in the banking and financial security worlds. Some of the attacks predicted in that book have since come to pass. While it was not originally intended to be a trilogy, it became the first book of "The Earth That Was" trilogy:
Infinite Exposure
Lesedi - The Greatest Lie Ever Told
John Smith - Last Known Survivor of the Microsoft Wars

When he is not consulting Roland Hughes posts about technology and sometimes politics on his blog. He also has regularly scheduled Sunday posts appearing on the Interesting Authors blog.