Information Technology, Raspberry Pi

Qt and USB – Pt. 1

Recently I’ve had some discussions on the qt-interest mailing list about Qt and USB and why it is not integrated in the package. Many of the kids there aren’t old enough to shave so they don’t remember this _exact_ same journey with serial ports. Couldn’t be done in a cross platform blah blah blah. Then there were various “unofficial” Qt based serial port classes. Then we had “almost” official stuff showing up in the Qt Playground. Eventually it all lead to the QSerialPort class. So much for that “can’t be done in a cross platform blah blah blah.”

Adding insult to injury, there already is a cross platform C library for generic USB access. It is called libusb. It is OpenSource with a list of platforms almost as long as the Qt list of supported platforms. So, instead creating a QUsb wrapper class for this library, each project rolls their own. No, they can never get rolled back in. The process of trying to submit a user contribution and push it all the way through to inclusion is akin to rolling naked twelve miles through broken glass. Takes about as long too. So, rather than getting something included and maintained as part of the library we will once again head down a broken and disjointed path. (Yes, I’m one of those people who was using all of those different, incompatible serial port classes and then had to rewrite everything once again when QSerialPort was officially adopted. They didn’t even keep the class name from the playground!)

I had to pack up my Raspberry Pi to clean off a table for an upcoming project. Will be quite some time before I can test this code on Raspberry Pi. I have been applying all updates, but, this machine is otherwise close to where we left off with the Raspberry Pi experiments.

As to the install of libusb development files, that took me two attempts.

You can visit the site and read through the doc, but, there is a parting of the ways between 0.1-4 and the 1.0 and forward stuff. Everyone wants to get the 1.0 and newer stuff for new development.

One must start out with baby steps. Given that mantra, my sample application doesn’t do much.

It’s just a main window with a text browser. The text browser gave me some place to dump what would otherwise be standard output. I’m even going to be honest here and tell you much of the code to retrieve that information was stolen/ported from examples found at dreamincode.net. One thing I want to do for the next baby step is find a method of translating VendorID and ProductID values into some human usable text. There appears to be at least one OpenSource effort, but I haven’t had time to drill down into that project. Been helping with corn planting season so you all can eat. There are no “amber waves of grain” until someone puts the seed in the ground so keep that in mind the next time you and your can’t-look-up-from-the-idiot-phone friends are bitching about the farm bill.

White 2-195 – My most favorite tractor ever – not me in image though

I know you are going to steal this code so let me say this.

You are not allowed to change the class name!

 

#ifndef LOGIKALUSB_H
#define LOGIKALUSB_H

#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);
 libusb_device *getDevice(unsigned int sub);
 libusb_device_descriptor *getDeviceDescriptor(int sub);

signals:

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

Like I said, this is baby steps. I am just creating a wrapper class around libusb.

 

#include "logikalusb.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;
        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;

        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 << " | ";
                }
            }
        }

        out << endl << endl << endl;
    }
}

 

We had to have a class destructor here because of the dynamic allocation. One of my big regrets here is the API is obviously set up for pre-C11 standards, making us keep a count and utilize older style for() loops.

The biggest benefit this USB library brings to the table is “user mode.” I just ran this as myself. I didn’t create any udev rules or have to jump through any other hoops as of yet. This was a major pain in the early iterations for the various serial port classes. You almost always had to be root. Truth be told, I am just reading right now. I haven’t chosen two ports to use for direct communication between two instances of this application. Ultimately  would like to find a place to set up my Raspberry Pi with a long enough cable, cross compiling for target, then running.

That reminds me. I need to email a client of mine. That is the last place I saw my 20 or so foot long USB cable.

I will work on this project as long as rain and time allow. Very soon I will be starting a new contract so we are talking hours to devote to this, not weeks or months. I really do want to get these ID values translating to human consumable strings. That will make the evolution of this experiment much cleaner. If we can simply and easily get real names we can have a pick list.