usb application screenshot
Information Technology, Raspberry Pi

Qt and USB – Pt. 2

Hopefully you can all see the featured image. It’s a screen shot of our little application so far. No, I didn’t make it pretty. That is not the purpose. Here is a slightly different shot of the same screen. This is the way it looks when the system is slow and the database is being created.

The little circle with bars is an animated gif I generated on this site. When the main window catches the loading signal it executes this slot.

void MainWindow::loading()
{
    cout << "loading() slot called " << endl; ui->busyMovieLabel->setVisible(true);
    ui->loadingLabel->setVisible(true);
    ui->genDBReportBtn->hide();
    movie = new QMovie(":/main/graphics/ajax-loader.gif");
    ui->busyMovieLabel->setMovie(movie);
    movie->start();
}

Since I’m not worried about resources this was the quick and easy method. In tiny resourced embedded systems I typically use a series of images and a timer in their own little class.

Before we jump into the code, here is the output. I hand edited it down to just the minimum so we can discuss it.

Current device count: 9

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: 


Number of possible configurations: 1   
Device Class: 0  VendorID: 1500   ProductID: 43037
Vendor:   Product:   Serial Number: AA8BF02FAUF8PRH6


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:1a.0


Number of possible configurations: 1   
Device Class: 9  VendorID: 7531   ProductID: 3
Vendor:   Product: Linux 4.13.0-43-generic xhci-hcd  Serial Number: 0000:00:14.0


Number of possible configurations: 1   
Device Class: 0  VendorID: 1309   ProductID: 2
Vendor:   Product: Back-UPS RS 1000G FW:869.L3 .D USB FW:L3   Serial Number: 3B1241X11465  


Number of possible configurations: 1   
Device Class: 9  VendorID: 7531   ProductID: 2
Vendor:   Product: xHCI Host Controller  Serial Number: 0000:00:14.0


=============================


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


Number of possible configurations: 1   
Device Class: 0  
VendorID: 1500   Lexar Media, Inc.
ProductID: 43037   LJDTT16G [JumpDrive 16GB]


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: 9  
VendorID: 7531   Linux Foundation
ProductID: 3   3.0 root hub


Number of possible configurations: 1   
Device Class: 0  
VendorID: 1309   American Power Conversion
ProductID: 2   Uninterruptible Power Supply


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

Above the line of === is the trimmed output of our libusb code. Below that line is still using libusb to obtain the device descriptors, but looking up vendor and product information in a database this application creates. Yes, the file usb.ids is available on the Internet and locally on most Linux systems. Yes, there are various hashing techniques to locating values in it, but, most of those techniques are used by people who never learned how to use a database. Eventually I will get around to writing the code which pulls the file down to create the database from it, but, we aren’t there yet.

Database access and creation is being provided by a singleton class. Yes, I know there is a deep religious cult against singleton classes, but they have their uses. In a production type environment a database would provide an external service your application simply connected with and used. This application needs to be self contained and is mostly for educational purposes.

#ifndef LOGIKALUSBINFO_H
#define LOGIKALUSBINFO_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 
#include 
#include 

class LogikalUSBInfo final : public QObject
{
    Q_OBJECT
public:
    static LogikalUSBInfo* getInstance();

    void initializeDatabase(QString destDir="", QString destName="", QString usbIdsPath="");


    QString fileName() { return QDir(databasePath).filePath(dbName);}

    void setDatabasePath(QString nuPath) { databasePath = nuPath;}
    void setDbName(QString nuName) { dbName = nuName;}

    bool isDBReady()  {return (databaseInitialized && databaseIsCurrent());}

    QString vendorName(QString vendorID);
    QString deviceName(QString vendorID, QString productID);

    // Remove the no-nos for a singleton class
    //
    LogikalUSBInfo(LogikalUSBInfo const&) = delete;             // copy constructor
    LogikalUSBInfo(LogikalUSBInfo&&) = delete;                  // move constructor
    LogikalUSBInfo& operator=(LogikalUSBInfo const&) = delete;  // copy assign
    LogikalUSBInfo& operator=(LogikalUSBInfo &&) = delete;      // move assign

signals:
    void loading();
    void loadingComplete();
    void databaseReady();

protected:
    explicit LogikalUSBInfo(QObject *parent = nullptr);

protected slots:
    void databaseLoaded();

private:
    bool idFileLocated( QString usbIdsPath="");
    bool databaseIsCurrent();
    bool databaseExists();
    void createNewDatabase();
    void retrieveFileAndCreateDb();

    bool databaseInitialized = false;
    QString idFilePath;
    QString databasePath;
    QString dbName = QString("usb_id_db");
    const QString driverName = QString("QSQLITE");
    const QString connectionName = QString("usb_ids");
    QFutureWatcher watcher;

};

#endif // LOGIKALUSBINFO_H

By default, with no options passed to the iniatializeDatabase() method, the database will be created in the user’s home directory. I provided some “set” methods so you could configure the location and still call initializeDatabase with no parameters to create the database in a location of your choosing.

Our isDBReady() method along with the 3 signals provide applications using this class with the ability to know the current state of the database. In our application I use the 3 signals to control what displays on the right side of our window. When the database is loading the “loading” text label and spinning wheel appear, but the button to generate the second report is hidden. When either loading completes or the database was located in a ready state during initialization we only see the button to generate the report.

vendorName() and deviceName() provide the main API. The strings passed as parameters must be zero padded HEX values because that is how they come from usb.ids.