Commit 4a1617f43c5bf752d800c4d5fb75d1ec995f3c70

Authored by David Graeff
Committed by David Gräff
1 parent 3ccfb5b2

Rework device selection

* Use own QDialog class SelectDevice and clean up main.cpp.
* USBDevice: Introduce a unique usb id for a connected device
* USBDevice: Introduce iteration field, to remember in which find round a device was found
cmake/CPackInfos.cmake
... ... @@ -44,13 +44,17 @@ else()
44 44 )
45 45  
46 46 set(ENV{LANG} "en_US")
47   - execute_process(
48   - COMMAND ${GIT_EXECUTABLE} log -n 10 "--date=format:%a %b %d %Y" "--pretty=format:* %ad %aN <%aE> %h - %s"
49   - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
50   - RESULT_VARIABLE CMD_RESULT
51   - OUTPUT_VARIABLE CHANGELOG
52   - OUTPUT_STRIP_TRAILING_WHITESPACE
53   - )
  47 + if(GIT_VERSION_STRING VERSION_LESS 2.6)
  48 + set(CHANGELOG "")
  49 + else()
  50 + execute_process(
  51 + COMMAND ${GIT_EXECUTABLE} log -n 10 "--date=format:%a %b %d %Y" "--pretty=format:* %ad %aN <%aE> %h - %s"
  52 + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  53 + RESULT_VARIABLE CMD_RESULT
  54 + OUTPUT_VARIABLE CHANGELOG
  55 + OUTPUT_STRIP_TRAILING_WHITESPACE
  56 + )
  57 + endif()
54 58 file(WRITE "${CMAKE_BINARY_DIR}/changelog" "${CHANGELOG}")
55 59 endif()
56 60  
... ...
openhantek/res/application.qrc
1 1 <RCC>
2 2 <qresource prefix="/">
3 3 <file alias="openhantek.png">images/openhantek.png</file>
  4 + <file alias="switch.png">images/switch.png</file>
4 5 </qresource>
5 6 <qresource prefix="/actions">
6 7 <file alias="open.png">images/actions/open.png</file>
... ...
openhantek/res/images/switch.png 0 → 100644

120 KB

openhantek/src/main.cpp
1 1 // SPDX-License-Identifier: GPL-2.0+
2 2  
3   -#include <QApplication>
4 3 #include <QDebug>
5   -#include <QDesktopWidget>
  4 +#include <QApplication>
6 5 #include <QLibraryInfo>
7   -#include <QListWidget>
8 6 #include <QLocale>
9   -#include <QMessageBox>
10   -#include <QPushButton>
11   -#include <QTimer>
12 7 #include <QTranslator>
13   -#include <QVBoxLayout>
  8 +
14 9 #include <iostream>
15 10 #include <memory>
16   -
17 11 #include <libusb-1.0/libusb.h>
18 12  
19 13 #include "analyse/dataanalyzer.h"
20 14 #include "hantekdsocontrol.h"
21 15 #include "mainwindow.h"
22 16 #include "settings.h"
23   -#include "usb/finddevices.h"
24   -#include "usb/uploadFirmware.h"
25 17 #include "usb/usbdevice.h"
26 18 #include "dsomodel.h"
  19 +#include "selectdevice/selectdevice.h"
27 20  
28 21 using namespace Hantek;
29 22  
30   -void showMessage(const QString &message) {
31   - QMessageBox::information(nullptr, QCoreApplication::translate("", "No connection established!"), message);
32   -}
33   -
34 23 /// \brief Initialize resources and translations and show the main window.
35 24 int main(int argc, char *argv[]) {
36 25 //////// Set application information ////////
... ... @@ -53,97 +42,17 @@ int main(int argc, char *argv[]) {
53 42 }
54 43  
55 44 //////// Find matching usb devices ////////
56   - libusb_context *context;
  45 + libusb_context *context = nullptr;
57 46 int error = libusb_init(&context);
58   -
59 47 if (error) {
60   - showMessage(QCoreApplication::translate("", "Can't initalize USB: %1").arg(libUsbErrorString(error)));
  48 + SelectDevice().showLibUSBFailedDialogModel(error);
61 49 return -1;
62 50 }
  51 + std::unique_ptr<USBDevice> device = SelectDevice().showSelectDeviceModal(context);
63 52  
64   - FindDevices findDevices;
65   - std::list<std::unique_ptr<USBDevice>> devices = findDevices.findDevices();
66   -
67   - if (devices.empty()) {
68   - showMessage(QCoreApplication::translate("", "No Hantek oscilloscope found. Please check if your "
69   - "device is supported by this software, is connected, "
70   - "in the right mode (oscilloscope mode) and if the "
71   - "driver is correctly installed. Refer to the <a "
72   - "href='https://github.com/OpenHantek/openhantek/"
73   - "'>website</a> for help: %1")
74   - .arg(findDevices.getErrorMessage()));
75   - return -1;
76   - }
77   -
78   - //////// Upload firmwares for all connected devices ////////
79   - for (const auto &i : devices) {
80   - QString modelName = QString::fromStdString(i->getModel()->name);
81   - if (i->needsFirmware()) {
82   - UploadFirmware uf;
83   - uf.startUpload(i.get());
84   - }
85   - }
86   - devices.clear();
87   -
88   -#define TR(str) QCoreApplication::translate("Firmware upload dialog", str)
89   -
90   - //////// Select device - Autoselect if only one device is ready ////////
91   - std::unique_ptr<QDialog> dialog = std::unique_ptr<QDialog>(new QDialog);
92   - QListWidget *w = new QListWidget(dialog.get());
93   -
94   - devices = findDevices.findDevices();
95   - for (auto &i : devices) {
96   - QString modelName = QString::fromStdString(i->getModel()->name);
97   -
98   - if (i->needsFirmware()) {
99   - if (UploadFirmware().startUpload(&*i)) {
100   - w->addItem(TR("%1: Upload failed").arg(modelName));
101   - } else {
102   - w->addItem(TR("%1: Upload failed").arg(modelName));
103   -
104   - }
105   - continue;
106   - }
107   - QString errorMessage;
108   - if (i->connectDevice(errorMessage)) {
109   - w->addItem(TR("%1: Ready").arg(modelName));
110   - w->setCurrentRow(w->count() - 1);
111   - } else {
112   - w->addItem(TR("%1: %2").arg(modelName).arg(findDevices.getErrorMessage()));
113   - }
114   - }
115   -
116   - if (w->currentRow() == -1 || devices.size() > 1) {
117   - QPushButton *btn = new QPushButton(QCoreApplication::translate("", "Connect to first device"), dialog.get());
118   - dialog->move(QApplication::desktop()->screen()->rect().center() - w->rect().center());
119   - dialog->setWindowTitle(QCoreApplication::translate("", "Firmware upload"));
120   - dialog->setLayout(new QVBoxLayout());
121   - dialog->layout()->addWidget(w);
122   - dialog->layout()->addWidget(btn);
123   - btn->connect(btn, &QPushButton::clicked, QCoreApplication::instance(), &QCoreApplication::quit);
124   - dialog->show();
125   - openHantekApplication.exec();
126   - dialog->close();
127   - }
128   - int selectedDevice = w->currentRow();
129   - dialog.reset(nullptr);
130   -
131   - std::unique_ptr<USBDevice> device;
132   - int indexCounter = 0;
133   - for (auto &i : devices) {
134   - if (indexCounter == selectedDevice) {
135   - device = std::move(i);
136   - break;
137   - }
138   - }
139   - devices.clear();
140   -
141   - if (device == nullptr || device->needsFirmware() || !device->isConnected()) {
142   - showMessage(QCoreApplication::translate("", "A device was found, but the "
143   - "firmware upload seem to have "
144   - "failed or the connection "
145   - "could not be established: %1")
146   - .arg(findDevices.getErrorMessage()));
  53 + QString errorMessage;
  54 + if (device == nullptr || !device->connectDevice(errorMessage)) {
  55 + libusb_exit(context);
147 56 return -1;
148 57 }
149 58  
... ...
openhantek/src/selectdevice/devicelistentry.h 0 → 100644
  1 +#pragma once
  2 +
  3 +#include <QString>
  4 +#include "usb/usbdevice.h"
  5 +
  6 +/**
  7 + * Represents an entry in the {@link DevicesListModel}.
  8 + */
  9 +struct DeviceListEntry {
  10 + UniqueUSBid id;
  11 + QString name;
  12 + bool canConnect;
  13 + bool needFirmware;
  14 + QString errorMessage;
  15 + QString getStatus() const {
  16 + return errorMessage.size()? errorMessage : (canConnect?"Ready":(needFirmware?"Firmware upload":"Cannot connect"));
  17 + }
  18 +};
... ...
openhantek/src/selectdevice/deviceslistmodel.cpp 0 → 100644
  1 +#include "deviceslistmodel.h"
  2 +#include "usb/finddevices.h"
  3 +#include "usb/uploadFirmware.h"
  4 +#include "dsomodel.h"
  5 +#include <QColor>
  6 +
  7 +DevicesListModel::DevicesListModel(FindDevices *findDevices) :findDevices(findDevices) {}
  8 +
  9 +int DevicesListModel::rowCount(const QModelIndex &) const
  10 +{
  11 + return entries.size();
  12 +}
  13 +
  14 +int DevicesListModel::columnCount(const QModelIndex &parent) const
  15 +{
  16 + return 2;
  17 +}
  18 +
  19 +QVariant DevicesListModel::headerData(int section, Qt::Orientation orientation, int role) const
  20 +{
  21 + if (orientation == Qt::Vertical)
  22 + return QAbstractTableModel::headerData(section, orientation, role);
  23 + if (role == Qt::DisplayRole) {
  24 + switch(section) {
  25 + case 0: return tr("Devicename");
  26 + case 1: return tr("Status");
  27 + default: return QVariant();
  28 + }
  29 + }
  30 + return QAbstractTableModel::headerData(section, orientation, role);
  31 +}
  32 +
  33 +QVariant DevicesListModel::data(const QModelIndex &index, int role) const
  34 +{
  35 + if (!index.isValid()) return QVariant();
  36 + if (role==Qt::UserRole) return QVariant::fromValue(entries[index.row()].id);
  37 + else if (role==Qt::UserRole+1) return QVariant::fromValue(entries[index.row()].canConnect);
  38 + else if (role==Qt::UserRole+2) return QVariant::fromValue(entries[index.row()].needFirmware);
  39 +
  40 + if (role == Qt::DisplayRole) {
  41 + if (index.column() == 0) {
  42 + return entries[index.row()].name;
  43 + } else if (index.column() == 1) {
  44 + return entries[index.row()].getStatus();
  45 + }
  46 + }
  47 +
  48 + if (role == Qt::BackgroundRole) {
  49 + if (entries[index.row()].canConnect) return QColor(Qt::darkGreen).lighter();
  50 + else if (entries[index.row()].needFirmware) return QColor(Qt::yellow).lighter();
  51 + }
  52 +
  53 + return QVariant();
  54 +}
  55 +
  56 +void DevicesListModel::updateDeviceList()
  57 +{
  58 + beginResetModel();
  59 + entries.clear();
  60 + endResetModel();
  61 + const FindDevices::DeviceList* devices = findDevices->getDevices();
  62 + beginInsertRows(QModelIndex(),0,devices->size());
  63 + for (auto &i : *devices) {
  64 + DeviceListEntry entry;
  65 + entry.name= QString::fromStdString(i.second->getModel()->name);
  66 + entry.id = i.first;
  67 + if (i.second->needsFirmware()) {
  68 + UploadFirmware uf;
  69 + if (!uf.startUpload(i.second.get())) {
  70 + entry.errorMessage = uf.getErrorMessage();
  71 + }
  72 + entry.needFirmware = true;
  73 + } else if (i.second->connectDevice(entry.errorMessage)) {
  74 + entry.canConnect = true;
  75 + i.second->disconnectFromDevice();
  76 + } else {
  77 + entry.canConnect = false;
  78 + }
  79 + entries.push_back(entry);
  80 + }
  81 + endInsertRows();
  82 +}
... ...
openhantek/src/selectdevice/deviceslistmodel.h 0 → 100644
  1 +#pragma once
  2 +
  3 +#include <QAbstractTableModel>
  4 +#include "devicelistentry.h"
  5 +
  6 +class FindDevices;
  7 +
  8 +/**
  9 + * Provides a Model for the Qt Model/View concept. The {@see FindDevices} is required
  10 + * to update the list of available devices.
  11 + */
  12 +class DevicesListModel: public QAbstractTableModel {
  13 +public:
  14 + DevicesListModel(FindDevices* findDevices);
  15 + // QAbstractItemModel interface
  16 + virtual int rowCount(const QModelIndex &parent) const override;
  17 + virtual int columnCount(const QModelIndex &parent) const override;
  18 + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
  19 + virtual QVariant data(const QModelIndex &index, int role) const override;
  20 + void updateDeviceList();
  21 +private:
  22 + std::vector<DeviceListEntry> entries;
  23 + FindDevices* findDevices;
  24 +};
... ...
openhantek/src/selectdevice/selectdevice.cpp 0 → 100644
  1 +#include <libusb-1.0/libusb.h>
  2 +
  3 +#include "utils/printutils.h"
  4 +#include "selectdevice.h"
  5 +#include "usb/uploadFirmware.h"
  6 +#include "usb/finddevices.h"
  7 +#include "dsomodel.h"
  8 +#include <QApplication>
  9 +#include <QTimer>
  10 +#include <QAbstractTableModel>
  11 +#include <QHeaderView>
  12 +#include <QFile>
  13 +
  14 +#include "devicelistentry.h"
  15 +#include "deviceslistmodel.h"
  16 +
  17 +SelectDevice::SelectDevice() {
  18 + btn = new QPushButton(this);
  19 + w = new QTableView(this);
  20 + label = new QLabel();
  21 + label->setWordWrap(true);
  22 + move(QApplication::desktop()->screen()->rect().center() - w->rect().center());
  23 + setWindowTitle(tr("Select device"));
  24 + setLayout(new QVBoxLayout());
  25 + layout()->addWidget(w);
  26 + layout()->addWidget(label);
  27 + layout()->addWidget(btn);
  28 + qRegisterMetaType<UniqueUSBid>();
  29 + connect(btn, &QPushButton::clicked, [this]() {
  30 + if (w->currentIndex().isValid()) {
  31 + selectedDevice = w->currentIndex().data(Qt::UserRole).value<UniqueUSBid>();
  32 + }
  33 + QCoreApplication::instance()->quit();
  34 + });
  35 +}
  36 +
  37 +std::unique_ptr<USBDevice> SelectDevice::showSelectDeviceModal(libusb_context *&context)
  38 +{
  39 +
  40 + std::unique_ptr<FindDevices> findDevices = std::unique_ptr<FindDevices>(new FindDevices(context));
  41 + std::unique_ptr<DevicesListModel> model = std::unique_ptr<DevicesListModel>(new DevicesListModel(findDevices.get()));
  42 + w->setModel(model.get());
  43 + w->verticalHeader()->hide();
  44 + w->horizontalHeader()->hide();
  45 + w->setSelectionBehavior(QAbstractItemView::SelectRows);
  46 + // w->setColumnWidth(1,60);
  47 + w->horizontalHeader()->setStretchLastSection(true);
  48 + connect(w->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelectDevice::currentChanged);
  49 +
  50 + QTimer timer;
  51 + timer.setInterval(1000);
  52 + connect(&timer, &QTimer::timeout, [this, &model, &findDevices]() {
  53 + if (findDevices->updateDeviceList())
  54 + model->updateDeviceList();
  55 + if (model->rowCount(QModelIndex())) {
  56 + w->setVisible(true);
  57 + label->setVisible(false);
  58 + currentChanged(w->currentIndex(),QModelIndex());
  59 + } else {
  60 + QString generalMessage = tr("<p>OpenHantek did not find any compatible devices.</p>"
  61 + "<p><img align='right' height='150' src='qrc:///switch.png'>"
  62 + "Don't forget to switch your device into oscilloscope mode if it has multiple modes.</p>"
  63 + );
  64 +#if defined(Q_OS_WIN)
  65 + generalMessage = generalMessage.arg("Please make sure you have installed the windows usb driver correctly");
  66 +#elif defined(Q_OS_LINUX)
  67 + QFile file("/lib/udev/rules.d/60-hantek.rules");
  68 + if (!file.exists()) {
  69 + generalMessage += tr("<p>Please make sure you have copied the udev rules file to <b>%1</b> for correct USB access permissions.</p>").arg(file.fileName());
  70 + }
  71 +#else
  72 +#endif
  73 + generalMessage += tr("<p>Visit the build and run instruction "
  74 + "<a href='https://github.com/OpenHantek/openhantek/blob/master/docs/build.md'>website</a> for help.</p>");
  75 + makeErrorDialog(generalMessage);
  76 + }
  77 + if (!w->currentIndex().isValid()) {
  78 + currentChanged(QModelIndex(),QModelIndex());
  79 + }
  80 + });
  81 + timer.start();
  82 + QCoreApplication::sendEvent(&timer, new QTimerEvent(timer.timerId())); // immediate timer event
  83 +
  84 + show();
  85 + QCoreApplication::instance()->exec();
  86 + timer.stop();
  87 + close();
  88 +
  89 + return findDevices->takeDevice(selectedDevice);
  90 +}
  91 +
  92 +void SelectDevice::showLibUSBFailedDialogModel(int error)
  93 +{
  94 + makeErrorDialog(tr("Can't initalize USB: %1").arg(libUsbErrorString(error)));
  95 + show();
  96 + QCoreApplication::instance()->exec();
  97 + close();
  98 +}
  99 +
  100 +void SelectDevice::makeErrorDialog(const QString &message)
  101 +{
  102 + w->setVisible(false);
  103 + label->setText(message);
  104 + label->setVisible(true);
  105 +}
  106 +
  107 +void SelectDevice::currentChanged(const QModelIndex &current, const QModelIndex &)
  108 +{
  109 + if (!current.isValid()) {
  110 + btn->setText(tr("Quit application"));
  111 + btn->setEnabled(true);
  112 + return;
  113 + }
  114 + if (current.data(Qt::UserRole+1).toBool()) {
  115 + btn->setText(tr("Use device %1").arg(current.sibling(current.row(),0).data(Qt::DisplayRole).toString()));
  116 + btn->setEnabled(true);
  117 + } else {
  118 + btn->setEnabled(false);
  119 + if (current.data(Qt::UserRole+2).toBool()) {
  120 + btn->setText(tr("Upload in progress..."));
  121 + } else {
  122 + btn->setText(tr("Connection failed!"));
  123 + }
  124 + }
  125 +}
... ...
openhantek/src/selectdevice/selectdevice.h 0 → 100644
  1 +#pragma once
  2 +
  3 +#include <QTableView>
  4 +#include <QPushButton>
  5 +#include <QVBoxLayout>
  6 +#include <QDesktopWidget>
  7 +#include <QDialog>
  8 +#include <QLabel>
  9 +#include <memory>
  10 +#include "usb/usbdevice.h"
  11 +
  12 +struct libusb_context;
  13 +
  14 +/**
  15 + * Offers the user a selection dialog. If you call any of the -Modal methods,
  16 + * the method will block and show a dialog for selection or for a usb error
  17 + * message. The method returns as soon as the user closes the dialog.
  18 + *
  19 + * An example to get a device:
  20 + * std::unique_ptr<USBDevice> device = SelectDevice().showSelectDeviceModal(context);
  21 + */
  22 +class SelectDevice: public QDialog {
  23 +public:
  24 + SelectDevice();
  25 + std::unique_ptr<USBDevice> showSelectDeviceModal(libusb_context *&context);
  26 + void showLibUSBFailedDialogModel(int error);
  27 +private:
  28 + void makeErrorDialog(const QString& message);
  29 + void updateDeviceList();
  30 + void currentChanged(const QModelIndex &current, const QModelIndex &previous);
  31 + QTableView *w;
  32 + QLabel *label;
  33 + QPushButton *btn;
  34 + UniqueUSBid selectedDevice = 0;
  35 +};
... ...
openhantek/src/usb/finddevices.cpp
... ... @@ -7,6 +7,7 @@
7 7 #include <QList>
8 8 #include <QTemporaryFile>
9 9  
  10 +#include <algorithm>
10 11 #include "ezusb.h"
11 12 #include "utils/printutils.h"
12 13 #include <libusb-1.0/libusb.h>
... ... @@ -16,50 +17,64 @@
16 17 FindDevices::FindDevices(libusb_context *context) : context(context) {}
17 18  
18 19 // Iterate through all usb devices
19   -std::list<std::unique_ptr<USBDevice>> FindDevices::findDevices() {
20   - std::list<std::unique_ptr<USBDevice>> devices;
21   -
  20 +int FindDevices::updateDeviceList() {
22 21 libusb_device **deviceList;
23 22 ssize_t deviceCount = libusb_get_device_list(context, &deviceList);
24 23 if (deviceCount < 0) {
25   - errorMessage = QCoreApplication::translate("", "Failed to get device list");
26   - return devices;
  24 + return (int) deviceCount;
27 25 }
28 26  
29   - noAccessDevices = false;
30   - int noAccessDeviceCount = 0;
  27 + ++findIteration;
  28 + int changes = 0;
31 29  
32 30 for (ssize_t deviceIterator = 0; deviceIterator < deviceCount; ++deviceIterator) {
33 31 libusb_device *device = deviceList[deviceIterator];
34 32 // Get device descriptor
35 33 struct libusb_device_descriptor descriptor;
36   - if (libusb_get_device_descriptor(device, &descriptor) < 0) continue;
  34 + libusb_get_device_descriptor(device, &descriptor);
  35 +
  36 + DeviceList::const_iterator inList = devices.find(USBDevice::computeUSBdeviceID(device));
  37 +
  38 + if (inList != devices.end()) {
  39 + inList->second->setFindIteration(findIteration);
  40 + continue;
  41 + }
37 42  
38 43 for (DSOModel* model : supportedModels) {
39 44 // Check VID and PID for firmware flashed devices
40   - if (descriptor.idVendor == model->vendorID && descriptor.idProduct == model->productID) {
41   - devices.push_back(std::unique_ptr<USBDevice>(new USBDevice(model, device)));
42   - break;
43   - }
  45 + bool supported = descriptor.idVendor == model->vendorID && descriptor.idProduct == model->productID;
44 46 // Devices without firmware have different VID/PIDs
45   - if (descriptor.idVendor == model->vendorIDnoFirmware && descriptor.idProduct == model->productIDnoFirmware) {
46   - devices.push_back(std::unique_ptr<USBDevice>(new USBDevice(model, device)));
47   - break;
  47 + supported |= descriptor.idVendor == model->vendorIDnoFirmware && descriptor.idProduct == model->productIDnoFirmware;
  48 + if (supported) {
  49 + ++changes;
  50 + devices[USBDevice::computeUSBdeviceID(device)] = std::unique_ptr<USBDevice>(new USBDevice(model, device, findIteration));
48 51 }
49 52 }
50 53 }
51 54  
52   - if (noAccessDeviceCount == deviceCount) {
53   - noAccessDevices = true;
54   - errorMessage =
55   - QCoreApplication::translate("", "Please make sure to have read/write access to your usb device. On "
56   - "linux you need to install the correct udev file for example.");
  55 + // Remove non existing devices
  56 + for (DeviceList::iterator it=devices.begin();it!=devices.end();) {
  57 + if (it->second->getFindIteration() != findIteration) {
  58 + ++changes;
  59 + it = devices.erase(it);
  60 + } else {
  61 + ++it;
  62 + }
57 63 }
58 64  
59 65 libusb_free_device_list(deviceList, true);
60   - return devices;
  66 +
  67 + return changes;
61 68 }
62 69  
63   -const QString &FindDevices::getErrorMessage() const { return errorMessage; }
  70 +const FindDevices::DeviceList* FindDevices::getDevices()
  71 +{
  72 + return &devices;
  73 +}
64 74  
65   -bool FindDevices::allDevicesNoAccessError() const { return noAccessDevices; }
  75 +std::unique_ptr<USBDevice> FindDevices::takeDevice(UniqueUSBid id)
  76 +{
  77 + DeviceList::iterator i = devices.find(id);
  78 + if (i==devices.end()) return nullptr;
  79 + return std::move(i->second);
  80 +}
... ...
openhantek/src/usb/finddevices.h
... ... @@ -4,6 +4,8 @@
4 4  
5 5 #include <QString>
6 6 #include <memory>
  7 +#include <map>
  8 +#include <list>
7 9  
8 10 #include "definitions.h"
9 11 #include "usbdevice.h"
... ... @@ -11,19 +13,30 @@
11 13 struct libusb_context;
12 14  
13 15 /**
14   - * @brief Search for Hantek devices and connect to the selected one.
  16 + * @brief Search for Hantek devices.
  17 + * Use usually want to call `updateDeviceList` and then retrieve the list via `getDevices`.
  18 + * You can call `updateDeviceList` as often as you want.
  19 + * If you have found your favorite device, you want to call `takeDevice`. The device will
  20 + * not be available in `getDevices` anymore and this will not change with calls to `updateDeviceList`.
15 21 *
16   - * At the moment this class connects to the first found devic automatically.
  22 + * Do not close the given usb context before this class object is destroyed.
17 23 */
18   -class FindDevices {
  24 +class FindDevices{
19 25 public:
20   - FindDevices(libusb_context *context = nullptr);
21   - std::list<std::unique_ptr<USBDevice>> findDevices();
22   - const QString &getErrorMessage() const;
23   - bool allDevicesNoAccessError() const;
24   -
  26 + typedef std::map<UniqueUSBid, std::unique_ptr<USBDevice>> DeviceList;
  27 + FindDevices(libusb_context *context);
  28 + /// Updates the device list. To clear the list, just dispose this object
  29 + /// \return If negative it represents a libusb error code otherwise the amount of updates
  30 + int updateDeviceList();
  31 + const DeviceList *getDevices();
  32 + /**
  33 + * @brief takeDevice
  34 + * @param id The unique usb id for the current bus layout
  35 + * @return A shared reference to the
  36 + */
  37 + std::unique_ptr<USBDevice> takeDevice(UniqueUSBid id);
25 38 private:
26 39 libusb_context *context; ///< The usb context used for this device
27   - QString errorMessage;
28   - bool noAccessDevices = false;
  40 + DeviceList devices;
  41 + unsigned findIteration = 0;
29 42 };
... ...
openhantek/src/usb/uploadFirmware.cpp
... ... @@ -14,6 +14,8 @@
14 14  
15 15 #include "dsomodel.h"
16 16  
  17 +#define TR(str) QCoreApplication::translate("UploadFirmware", str)
  18 +
17 19 bool UploadFirmware::startUpload(USBDevice *device) {
18 20 if (device->isConnected() || !device->needsFirmware()) return false;
19 21  
... ... @@ -22,7 +24,7 @@ bool UploadFirmware::startUpload(USBDevice *device) {
22 24 int errorCode = libusb_open(device->getRawDevice(), &handle);
23 25 if (errorCode != LIBUSB_SUCCESS) {
24 26 handle = nullptr;
25   - errorMessage = QCoreApplication::translate("", "Couldn't open device: %1").arg(libUsbErrorString(errorCode));
  27 + errorMessage = TR("Couldn't open device: %1").arg(libUsbErrorString(errorCode));
26 28 return false;
27 29 }
28 30  
... ... @@ -41,7 +43,7 @@ bool UploadFirmware::startUpload(USBDevice *device) {
41 43 libusb_set_auto_detach_kernel_driver(handle, 1);
42 44 int status = libusb_claim_interface(handle, 0);
43 45 if (status != LIBUSB_SUCCESS) {
44   - errorMessage = QString("libusb_claim_interface() failed: %1").arg(libusb_error_name(status));
  46 + errorMessage = TR("libusb_claim_interface() failed: %1").arg(libusb_error_name(status));
45 47 libusb_close(handle);
46 48 return false;
47 49 }
... ... @@ -50,7 +52,7 @@ bool UploadFirmware::startUpload(USBDevice *device) {
50 52 status = ezusb_load_ram(handle, temp_loader_path->fileName().toUtf8().constData(), FX_TYPE_FX2, IMG_TYPE_HEX, 0);
51 53  
52 54 if (status != LIBUSB_SUCCESS) {
53   - errorMessage = QString("ezusb_load_ram(loader_path) failed: %1").arg(libusb_error_name(status));
  55 + errorMessage = TR("Writing the loader firmware failed: %1").arg(libusb_error_name(status));
54 56 libusb_release_interface(handle, 0);
55 57 libusb_close(handle);
56 58 return false;
... ... @@ -60,7 +62,7 @@ bool UploadFirmware::startUpload(USBDevice *device) {
60 62 status = ezusb_load_ram(handle, temp_firmware_path->fileName().toUtf8().constData(), FX_TYPE_FX2, IMG_TYPE_HEX, 1);
61 63  
62 64 if (status != LIBUSB_SUCCESS) {
63   - errorMessage = QString("ezusb_load_ram(firmware_path) failed: %1").arg(libusb_error_name(status));
  65 + errorMessage = TR("Writing the main firmware failed: %1").arg(libusb_error_name(status));
64 66 libusb_release_interface(handle, 0);
65 67 libusb_close(handle);
66 68 return false;
... ... @@ -72,3 +74,4 @@ bool UploadFirmware::startUpload(USBDevice *device) {
72 74 }
73 75  
74 76 const QString &UploadFirmware::getErrorMessage() const { return errorMessage; }
  77 +
... ...
openhantek/src/usb/uploadFirmware.h
... ... @@ -14,7 +14,6 @@ class UploadFirmware {
14 14 public:
15 15 bool startUpload(USBDevice *device);
16 16 const QString &getErrorMessage() const;
17   -
18 17 private:
19 18 QString errorMessage;
20 19 };
... ...
openhantek/src/usb/usbdevice.cpp
... ... @@ -11,8 +11,14 @@
11 11 #include "models.h"
12 12 #include "utils/printutils.h"
13 13  
  14 +UniqueUSBid USBDevice::computeUSBdeviceID(libusb_device *device) {
  15 + UniqueUSBid v=0;
  16 + libusb_get_port_numbers(device, (uint8_t*)&v, sizeof(v));
  17 + return v;
  18 +}
14 19  
15   -USBDevice::USBDevice(DSOModel *model, libusb_device *device) : model(model), device(device) {
  20 +USBDevice::USBDevice(DSOModel *model, libusb_device *device, unsigned findIteration) :
  21 + model(model), device(device), findIteration(findIteration), uniqueUSBdeviceID(computeUSBdeviceID(device)) {
16 22 libusb_ref_device(device);
17 23 libusb_get_device_descriptor(device, &descriptor);
18 24 }
... ... @@ -59,7 +65,10 @@ bool USBDevice::connectDevice(QString &amp;errorMessage) {
59 65 return true;
60 66 }
61 67  
62   -USBDevice::~USBDevice() { connectionLost(); }
  68 +USBDevice::~USBDevice() {
  69 + disconnectFromDevice();
  70 + device = nullptr;
  71 +}
63 72  
64 73 int USBDevice::claimInterface(const libusb_interface_descriptor *interfaceDescriptor, int endpointOut, int endPointIn) {
65 74 int errorCode = libusb_claim_interface(this->handle, interfaceDescriptor->bInterfaceNumber);
... ... @@ -82,7 +91,8 @@ int USBDevice::claimInterface(const libusb_interface_descriptor *interfaceDescri
82 91 return LIBUSB_SUCCESS;
83 92 }
84 93  
85   -void USBDevice::connectionLost() {
  94 +void USBDevice::disconnectFromDevice() {
  95 + if (!device) return;
86 96 libusb_unref_device(device);
87 97  
88 98 if (!this->handle) return;
... ... @@ -93,7 +103,7 @@ void USBDevice::connectionLost() {
93 103  
94 104 // Close device handle
95 105 libusb_close(this->handle);
96   - this->handle = 0;
  106 + this->handle = nullptr;
97 107  
98 108 emit deviceDisconnected();
99 109 }
... ... @@ -104,6 +114,16 @@ bool USBDevice::needsFirmware() {
104 114 return this->descriptor.idProduct != model->productID || this->descriptor.idVendor != model->vendorID;
105 115 }
106 116  
  117 +void USBDevice::setFindIteration(unsigned iteration)
  118 +{
  119 + findIteration = iteration;
  120 +}
  121 +
  122 +unsigned USBDevice::getFindIteration() const
  123 +{
  124 + return findIteration;
  125 +}
  126 +
107 127 int USBDevice::bulkTransfer(unsigned char endpoint, unsigned char *data, unsigned int length, int attempts,
108 128 unsigned int timeout) {
109 129 if (!this->handle) return LIBUSB_ERROR_NO_DEVICE;
... ... @@ -113,7 +133,7 @@ int USBDevice::bulkTransfer(unsigned char endpoint, unsigned char *data, unsigne
113 133 for (int attempt = 0; (attempt < attempts || attempts == -1) && errorCode == LIBUSB_ERROR_TIMEOUT; ++attempt)
114 134 errorCode = libusb_bulk_transfer(this->handle, endpoint, data, length, &transferred, timeout);
115 135  
116   - if (errorCode == LIBUSB_ERROR_NO_DEVICE) connectionLost();
  136 + if (errorCode == LIBUSB_ERROR_NO_DEVICE) disconnectFromDevice();
117 137 if (errorCode < 0)
118 138 return errorCode;
119 139 else
... ... @@ -211,7 +231,7 @@ int USBDevice::controlTransfer(unsigned char type, unsigned char request, unsign
211 231 for (int attempt = 0; (attempt < attempts || attempts == -1) && errorCode == LIBUSB_ERROR_TIMEOUT; ++attempt)
212 232 errorCode = libusb_control_transfer(this->handle, type, request, value, index, data, length, HANTEK_TIMEOUT);
213 233  
214   - if (errorCode == LIBUSB_ERROR_NO_DEVICE) connectionLost();
  234 + if (errorCode == LIBUSB_ERROR_NO_DEVICE) disconnectFromDevice();
215 235 return errorCode;
216 236 }
217 237  
... ... @@ -276,6 +296,11 @@ int USBDevice::getPacketSize() {
276 296  
277 297 libusb_device *USBDevice::getRawDevice() const { return device; }
278 298  
  299 +unsigned long USBDevice::getUniqueUSBDeviceID() const
  300 +{
  301 + return uniqueUSBdeviceID;
  302 +}
  303 +
279 304 const DSOModel* USBDevice::getModel() const { return model; }
280 305  
281 306 void USBDevice::setEnableBulkTransfer(bool enable) { allowBulkTransfer = enable; }
... ...
openhantek/src/usb/usbdevice.h
... ... @@ -14,21 +14,31 @@
14 14  
15 15 class DSOModel;
16 16  
  17 +typedef unsigned long UniqueUSBid;
  18 +
17 19 /// \brief This class handles the USB communication with an usb device that has
18 20 /// one in and one out endpoint.
19 21 class USBDevice : public QObject {
20 22 Q_OBJECT
21 23  
22 24 public:
23   - USBDevice(DSOModel* model, libusb_device *device);
  25 + USBDevice(DSOModel* model, libusb_device *device, unsigned findIteration = 0);
24 26 ~USBDevice();
25 27 bool connectDevice(QString &errorMessage);
  28 + void disconnectFromDevice();
26 29  
27 30 /// \brief Check if the oscilloscope is connected.
28 31 /// \return true, if a connection is up.
29 32 bool isConnected();
30 33 bool needsFirmware();
31 34  
  35 + /**
  36 + * Keep track of the find iteration on which this device was found
  37 + * @param iteration The new iteration value
  38 + */
  39 + void setFindIteration(unsigned iteration);
  40 + unsigned getFindIteration() const;
  41 +
32 42 // Various methods to handle USB transfers
33 43  
34 44 /// \brief Bulk transfer to/from the oscilloscope.
... ... @@ -57,16 +67,31 @@ class USBDevice : public QObject {
57 67 int getConnectionSpeed();
58 68 int getPacketSize();
59 69  
  70 + /**
  71 + * @return Returns the raw libusb device
  72 + */
60 73 libusb_device *getRawDevice() const;
  74 +
  75 + /**
  76 + * @return Return the unique usb device id {@link USBDevice::computeUSBdeviceID()}.
  77 + */
  78 + unsigned long getUniqueUSBDeviceID() const;
  79 + /**
  80 + * The USB bus is organised in a tree hierarchy. A device is connected to a port on a bus device,
  81 + * which is connected to a port on another bus device etc up to the root usb device.
  82 + *
  83 + * The USB 3.0 standard allows up to 7 levels with 256 devices on each level (1 Byte). We generate
  84 + * a unique number for the connected device.
  85 + */
  86 + static UniqueUSBid computeUSBdeviceID(libusb_device *device);
  87 +
61 88 /// \brief Get the oscilloscope model.
62 89 /// \return The ::Model of the connected Hantek DSO.
63 90 const DSOModel *getModel() const;
64 91 void setEnableBulkTransfer(bool enable);
65 92 void overwriteInPacketLength(int len);
66   -
67 93 protected:
68 94 int claimInterface(const libusb_interface_descriptor *interfaceDescriptor, int endpointOut, int endPointIn);
69   - void connectionLost();
70 95  
71 96 // Command buffers
72 97 ControlBeginCommand beginCommandControl;
... ... @@ -76,6 +101,8 @@ class USBDevice : public QObject {
76 101 struct libusb_device_descriptor descriptor;
77 102 libusb_device *device; ///< The USB handle for the oscilloscope
78 103 libusb_device_handle *handle = nullptr;
  104 + unsigned findIteration;
  105 + const unsigned long uniqueUSBdeviceID;
79 106 int interface;
80 107 int outPacketLength; ///< Packet length for the OUT endpoint
81 108 int inPacketLength; ///< Packet length for the IN endpoint
... ...