LibreVNA/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/usbdevice.cpp
2024-09-24 19:19:43 +02:00

286 lines
8.4 KiB
C++

#include "usbdevice.h"
#include "CustomWidgets/informationbox.h"
#include <signal.h>
#include <QDebug>
#include <QString>
#include <QMessageBox>
#include <mutex>
using namespace std;
using USBID = struct {
int VID;
int PID;
};
static constexpr USBID IDs[] = {
{0x0483, 0x4122},
{0x1209, 0x4122},
};
USBDevice::USBDevice(QString serial)
{
rx_cnt = 0;
m_handle = nullptr;
libusb_init(&m_context);
#if LIBUSB_API_VERSION >= 0x01000106
libusb_set_option(m_context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
#endif
SearchDevices([=](libusb_device_handle *handle, QString found_serial) -> bool {
if(serial.isEmpty() || serial == found_serial) {
// accept connection to this device
m_serial = found_serial;
m_handle = handle;
// abort device search
return false;
} else {
// not the requested device, continue search
return true;
}
}, m_context, false);
if(!m_handle) {
QString message = "No device found";
if(!serial.isEmpty()) {
// only show error message if specific device was requested
InformationBox::ShowError("Error opening device", message);
}
libusb_exit(m_context);
throw std::runtime_error(message.toStdString());
return;
}
// Found the correct device, now connect
/* claim the interface */
int ret = libusb_claim_interface(m_handle, 2);
if (ret < 0) {
libusb_close(m_handle);
/* Failed to open */
QString message = "Failed to claim interface: \"";
message.append(libusb_strerror((libusb_error) ret));
message.append("\" Maybe you are already connected to this device?");
qWarning() << message;
InformationBox::ShowError("Error opening device", message);
libusb_exit(m_context);
throw std::runtime_error(message.toStdString());
}
qInfo() << "USB connection established" << Qt::flush;
}
USBDevice::~USBDevice()
{
libusb_release_interface(m_handle, 2);
libusb_close(m_handle);
libusb_exit(m_context);
}
bool USBDevice::Cmd(QString cmd)
{
QString rcv;
bool success = send(cmd) && receive(&rcv);
if(success && rcv == "") {
// empty response expected by commad
return true;
} else {
// failed to send/receive
emit communicationFailure();
return false;
}
}
QString USBDevice::Query(QString query)
{
if(send(query)) {
QString rcv;
if(receive(&rcv)) {
return rcv;
} else {
emit communicationFailure();
}
} else {
emit communicationFailure();
}
return QString();
}
std::set<QString> USBDevice::GetDevices()
{
std::set<QString> serials;
libusb_context *ctx;
libusb_init(&ctx);
#if LIBUSB_API_VERSION >= 0x01000106
libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
#endif
SearchDevices([&serials](libusb_device_handle *, QString serial) -> bool {
serials.insert(serial);
return true;
}, ctx, true);
libusb_exit(ctx);
return serials;
}
void USBDevice::SearchDevices(std::function<bool (libusb_device_handle *, QString)> foundCallback, libusb_context *context, bool ignoreOpenError)
{
libusb_device **devList;
auto ndevices = libusb_get_device_list(context, &devList);
for (ssize_t idx = 0; idx < ndevices; idx++) {
int ret;
libusb_device *device = devList[idx];
libusb_device_descriptor desc = {};
ret = libusb_get_device_descriptor(device, &desc);
if (ret) {
/* some error occured */
qCritical() << "Failed to get device descriptor: "
<< libusb_strerror((libusb_error) ret);
continue;
}
bool correctID = false;
int numIDs = sizeof(IDs)/sizeof(IDs[0]);
for(int i=0;i<numIDs;i++) {
if(desc.idVendor == IDs[i].VID && desc.idProduct == IDs[i].PID) {
correctID = true;
break;
}
}
if(!correctID) {
continue;
}
/* Try to open the device */
libusb_device_handle *handle = nullptr;
ret = libusb_open(device, &handle);
if (ret) {
qDebug() << libusb_strerror((enum libusb_error) ret);
/* Failed to open */
if(!ignoreOpenError) {
QString message = "Found potential device but failed to open usb connection: \"";
message.append(libusb_strerror((libusb_error) ret));
message.append("\" On Linux this is most likely caused by a missing udev rule. "
"On Windows this most likely means that you are already connected to "
"this device (is another instance of the application already runnning?)");
qWarning() << message;
InformationBox::ShowError("Error opening device", message);
}
continue;
}
char c_product[256];
char c_serial[256];
libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber,
(unsigned char*) c_serial, sizeof(c_serial));
ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct,
(unsigned char*) c_product, sizeof(c_product));
if (ret > 0) {
/* managed to read the product string */
QString product(c_product);
if (product == "LibreCAL") {
// this is a match
if(!foundCallback(handle, QString(c_serial))) {
// abort search
break;
}
}
} else {
qWarning() << "Failed to get product descriptor: "
<< libusb_strerror((libusb_error) ret);
}
libusb_close(handle);
}
libusb_free_device_list(devList, 1);
}
bool USBDevice::send(const QString &s)
{
qDebug() << "Send:"<<s;
unsigned char data[s.size()+2];
memcpy(data, s.toLatin1().data(), s.size());
memcpy(&data[s.size()], "\r\n", 2);
int actual;
auto r = libusb_bulk_transfer(m_handle, LIBUSB_ENDPOINT_OUT | 0x03, data, s.size() + 2, &actual, 0);
if(r == 0 && actual == s.size() + 2) {
return true;
} else {
return false;
}
}
bool USBDevice::receive(QString *s, unsigned int timeout)
{
auto checkForCompleteLine = [this, s]() -> bool {
for(unsigned int i=1;i<rx_cnt;i++) {
if(rx_buf[i] == '\n' && rx_buf[i-1] == '\r') {
rx_buf[i-1] = '\0';
if(s) {
*s = QString(rx_buf);
// qDebug() << "Receive:"<<*s;
}
memmove(rx_buf, &rx_buf[i+1], sizeof(rx_buf) - (i+1));
rx_cnt -= i+1;
rx_buf[rx_cnt] = 0;
// qDebug() << "Remaining in buffer:" << QString(rx_buf);
return true;
}
}
return false;
};
int res = 0;
if(!checkForCompleteLine()) {
// needs to receive data
int actual;
do {
res = libusb_bulk_transfer(m_handle, LIBUSB_ENDPOINT_IN | 0x03, (unsigned char*) &rx_buf[rx_cnt], 512, &actual, timeout);
rx_cnt += actual;
// qDebug() << "received" << actual << "bytes. Total in buffer:" << rx_cnt;
if(checkForCompleteLine()) {
return true;
}
} while(res == 0);
}
return res == 0;
// char data[512];
// memset(data, 0, sizeof(data));
// int actual;
// int rcvCnt = 0;
// bool endOfLineFound = false;
// int res;
// do {
// res = libusb_bulk_transfer(m_handle, LIBUSB_ENDPOINT_IN | 0x03, (unsigned char*) &data[rcvCnt], sizeof(data) - rcvCnt, &actual, 2000);
// for(int i=rcvCnt;i<rcvCnt+actual;i++) {
// if(i == 0) {
// continue;
// }
// if(data[i] == '\n' && data[i-1] == '\r') {
// endOfLineFound = true;
// data[i-1] = '\0';
// break;
// }
// }
// rcvCnt += actual;
// } while(res == 0 && !endOfLineFound);
// if(res == 0) {
// if(s) {
// *s = QString(data);
//// qDebug() << "Receive:"<<*s;
// }
// return true;
// } else {
// return false;
// }
}
QString USBDevice::serial() const
{
return m_serial;
}