diff --git a/Software/PC_Application/Calibration/LibreCAL/caldevice.cpp b/Software/PC_Application/Calibration/LibreCAL/caldevice.cpp new file mode 100644 index 0000000..d15ca47 --- /dev/null +++ b/Software/PC_Application/Calibration/LibreCAL/caldevice.cpp @@ -0,0 +1,354 @@ +#include "caldevice.h" + +#include + +#include + +using namespace std; + +CalDevice::CalDevice(QString serial) : + usb(new USBDevice(serial)) +{ + // Check device identification + auto id = usb->Query("*IDN?"); + if(!id.startsWith("LibreCAL_")) { + delete usb; + throw std::runtime_error("Invalid response to *IDN?: "+id.toStdString()); + } + + firmware = usb->Query(":FIRMWARE?"); + QString ports = usb->Query(":PORTS?"); + bool okay; + numPorts = ports.toInt(&okay); + if(!okay) { + numPorts = 0; + } +} + +CalDevice::~CalDevice() +{ + delete usb; +} + +QString CalDevice::StandardToString(CalDevice::Standard s) +{ + switch(s) { + case Standard::Open: return "OPEN"; + case Standard::Short: return "SHORT"; + case Standard::Load: return "LOAD"; + case Standard::Through: return "THROUGH"; + case Standard::None: return "NONE"; + } +} + +CalDevice::Standard CalDevice::StandardFromString(QString s) +{ + for(int i=0;i<=(int) Standard::None;i++) { + if(s == StandardToString((Standard) i)) { + return (Standard) i; + } + } + return Standard::None; +} + +CalDevice::Standard CalDevice::getStandard(int port) +{ + auto query = ":PORT? "+QString::number(port); + auto response = usb->Query(query); + return StandardFromString(response); +} + +bool CalDevice::setStandard(int port, CalDevice::Standard s) +{ + auto cmd = ":PORT "+QString::number(port)+" "+StandardToString(s); + return usb->Cmd(cmd); +} + +std::vector CalDevice::availableStandards() +{ + return {Standard::None, Standard::Open, Standard::Short, Standard::Load, Standard::Through}; +} + +double CalDevice::getTemperature() +{ + QString tempString = usb->Query(":TEMP?"); + bool okay; + double temp = tempString.toDouble(&okay); + if(!okay) { + temp = 0.0; + } + return temp; +} + +bool CalDevice::stabilized() +{ + auto stable = usb->Query(":TEMPerature:STABLE?"); + return stable == "TRUE"; +} + +double CalDevice::getHeaterPower() +{ + QString tempString = usb->Query(":HEATER:POWER?"); + bool okay; + double power = tempString.toDouble(&okay); + if(!okay) { + power = 0.0; + } + return power; +} + +QString CalDevice::serial() +{ + return usb->serial(); +} + +QString CalDevice::getFirmware() const +{ + return firmware; +} + +int CalDevice::getNumPorts() const +{ + return numPorts; +} + +void CalDevice::loadCoefficientSets(QStringList names) +{ + coeffSets.clear(); + new std::thread(&CalDevice::loadCoefficientSetsThread, this, names); +} + +void CalDevice::saveCoefficientSets() +{ + if(!hasModifiedCoefficients()) { + // nothing to do, already done + emit updateCoefficientsDone(true); + } else { + new std::thread(&CalDevice::saveCoefficientSetsThread, this); + } +} + +void CalDevice::loadCoefficientSetsThread(QStringList names) +{ + QStringList coeffList = getCoefficientSetNames(); + if(coeffList.empty()) { + // something went wrong + emit updateCoefficientsDone(false); + return; + } + if(names.size() > 0) { + // check if all the requested names are actually available + for(auto n : names) { + if(!coeffList.contains(n)) { + // this coefficient does not exist + emit updateCoefficientsDone(false); + return; + } + } + coeffList = names; + } + // get total number of coefficient points for accurate percentage calculation + unsigned long totalPoints = 0; + for(auto name : coeffList) { + for(int i=1;i<=numPorts;i++) { + totalPoints += usb->Query(":COEFF:NUM? "+name+" P"+QString::number(i)+"_OPEN").toInt(); + totalPoints += usb->Query(":COEFF:NUM? "+name+" P"+QString::number(i)+"_SHORT").toInt(); + totalPoints += usb->Query(":COEFF:NUM? "+name+" P"+QString::number(i)+"_LOAD").toInt(); + for(int j=i+1;j<=numPorts;j++) { + totalPoints += usb->Query(":COEFF:NUM? "+name+" P"+QString::number(i)+QString::number(j)+"_THROUGH").toInt(); + } + } + } + unsigned long readPoints = 0; + int lastPercentage = 0; + for(auto name : coeffList) { + // create the coefficient set + CoefficientSet set; + set.name = name; + set.ports = numPorts; + // Read this coefficient set + for(int i=1;i<=numPorts;i++) { + auto createCoefficient = [&](QString setName, QString paramName) -> CoefficientSet::Coefficient* { + int points = usb->Query(":COEFF:NUM? "+setName+" "+paramName).toInt(); + CoefficientSet::Coefficient *c = new CoefficientSet::Coefficient(); + if(paramName.endsWith("THROUGH")) { + c->t = Touchstone(2); + } else { + c->t = Touchstone(1); + } + for(int i=0;iQuery(":COEFF:GET? "+setName+" "+paramName+" "+QString::number(i)); + QStringList values = pString.split(","); + Touchstone::Datapoint p; + p.frequency = values[0].toDouble() * 1e9; + for(int j = 0;j<(values.size()-1)/2;j++) { + double real = values[1+j*2].toDouble(); + double imag = values[2+j*2].toDouble(); + p.S.push_back(complex(real, imag)); + } + if(p.S.size() == 4) { + // S21 and S12 are swapped in the touchstone file order (S21 goes first) + // but Touchstone::AddDatapoint expects S11 S12 S21 S22 order. Swap to match that + swap(p.S[1], p.S[2]); + } + c->t.AddDatapoint(p); + readPoints++; + int newPercentage = readPoints * 100 / totalPoints; + if(newPercentage != lastPercentage) { + lastPercentage = newPercentage; + emit updateCoefficientsPercent(newPercentage); + } + } + c->t.setFilename("LibreCAL/"+paramName); + return c; + }; + set.opens.push_back(createCoefficient(name, "P"+QString::number(i)+"_OPEN")); + set.shorts.push_back(createCoefficient(name, "P"+QString::number(i)+"_SHORT")); + set.loads.push_back(createCoefficient(name, "P"+QString::number(i)+"_LOAD")); + for(int j=i+1;j<=numPorts;j++) { + set.throughs.push_back(createCoefficient(name, "P"+QString::number(i)+QString::number(j)+"_THROUGH")); + } + } + coeffSets.push_back(set); + } + emit updateCoefficientsDone(true); +} + +void CalDevice::saveCoefficientSetsThread() +{ + // figure out how many points need to be transferred + unsigned long totalPoints = 0; + for(auto set : coeffSets) { + for(auto c : set.opens) { + if(c->modified) { + totalPoints += c->t.points(); + } + } + for(auto c : set.shorts) { + if(c->modified) { + totalPoints += c->t.points(); + } + } + for(auto c : set.loads) { + if(c->modified) { + totalPoints += c->t.points(); + } + } + for(auto c : set.throughs) { + if(c->modified) { + totalPoints += c->t.points(); + } + } + } + unsigned long transferredPoints = 0; + int lastPercentage = 0; + bool success = true; + for(auto set : coeffSets) { + auto createCoefficient = [&](QString setName, QString paramName, Touchstone &t, bool &modified) -> bool { + if(!modified) { + // no changes, nothing to do + return true; + } + int points = t.points(); + if(points > 0) { + // create the file + if(!usb->Cmd(":COEFF:CREATE "+setName+" "+paramName)) { + return false; + } + for(unsigned int i=0;iCmd(cmd)) { + return false; + } + transferredPoints++; + int newPercentage = transferredPoints * 100 / totalPoints; + if(newPercentage != lastPercentage) { + lastPercentage = newPercentage; + emit updateCoefficientsPercent(newPercentage); + } + } + if(!usb->Cmd(":COEFF:FIN")) { + return false; + } + } else { + // no points, delete coefficient + if(!usb->Cmd(":COEFF:DEL "+setName+" "+paramName)) { + return false; + } + } + modified = false; + return true; + }; + for(int i=1;i<=numPorts;i++) { + success &= createCoefficient(set.name, "P"+QString::number(i)+"_OPEN", set.opens[i-1]->t, set.opens[i-1]->modified); + success &= createCoefficient(set.name, "P"+QString::number(i)+"_SHORT", set.shorts[i-1]->t, set.shorts[i-1]->modified); + success &= createCoefficient(set.name, "P"+QString::number(i)+"_LOAD", set.loads[i-1]->t, set.loads[i-1]->modified); + for(int j=i+1;j<=numPorts;j++) { + success &= createCoefficient(set.name, "P"+QString::number(i)+QString::number(j)+"_THROUGH", set.getThrough(i,j)->t, set.getThrough(i,j)->modified); + } + } + } + emit updateCoefficientsDone(success); +} + +std::vector CalDevice::getCoefficientSets() const +{ + return coeffSets; +} + +QStringList CalDevice::getCoefficientSetNames() +{ + QString resp = usb->Query(":COEFF:LIST?"); + if(!resp.startsWith("FACTORY")) { + return QStringList(); + } + return resp.split(","); +} + +bool CalDevice::hasModifiedCoefficients() +{ + for(auto set : coeffSets) { + for(auto c : set.opens) { + if(c->modified) { + return true; + } + } + for(auto c : set.shorts) { + if(c->modified) { + return true; + } + } + for(auto c : set.loads) { + if(c->modified) { + return true; + } + } + for(auto c : set.throughs) { + if(c->modified) { + return true; + } + } + } +} + +CalDevice::CoefficientSet::Coefficient *CalDevice::CoefficientSet::getThrough(int port1, int port2) const +{ + if(port1 > ports || port2 > ports || port1 >= port2) { + return nullptr; + } + int index = port2 - port1 - 1; + while(port1 > 1) { + index += ports - port1 + 1; + port1--; + } + return throughs[index]; +} diff --git a/Software/PC_Application/Calibration/LibreCAL/caldevice.h b/Software/PC_Application/Calibration/LibreCAL/caldevice.h new file mode 100644 index 0000000..4e15368 --- /dev/null +++ b/Software/PC_Application/Calibration/LibreCAL/caldevice.h @@ -0,0 +1,90 @@ +#ifndef CALDEVICE_H +#define CALDEVICE_H + +#include "usbdevice.h" +#include "touchstone.h" + +#include +#include + +class CalDevice : public QObject +{ + Q_OBJECT +public: + CalDevice(QString serial); + ~CalDevice(); + + enum class Standard { + Open, + Short, + Load, + Through, + None + }; + + static QString StandardToString(Standard s); + static Standard StandardFromString(QString s); + + Standard getStandard(int port); + bool setStandard(int port, Standard s); + static std::vector availableStandards(); + + double getTemperature(); + bool stabilized(); + double getHeaterPower(); + + QString serial(); + QString getFirmware() const; + int getNumPorts() const; + + class CoefficientSet { + public: + QString name; + int ports; + class Coefficient { + public: + Coefficient() : t(Touchstone(1)), modified(false) {} + Touchstone t; + bool modified; + }; + + std::vector opens; + std::vector shorts; + std::vector loads; + std::vector throughs; + + Coefficient *getThrough(int port1, int port2) const; + }; + + // Extracts the coefficients from the device. This is done with a dedicated thread. + // Do not call any other functions until the update is finished. Process can be + // monitored through the updateCoefficientsPercent and updateCoefficientsDone signals + void loadCoefficientSets(QStringList names = QStringList()); + // Writes coefficient sets to the device. This will only write modified files to save + // time. This is done with a dedicated thread. + // Do not call any other functions until the update is finished. Process can be + // monitored through the updateCoefficientsPercent and updateCoefficientsDone signals + void saveCoefficientSets(); + std::vector getCoefficientSets() const; + + QStringList getCoefficientSetNames(); + + bool hasModifiedCoefficients(); + +signals: + void updateCoefficientsPercent(int percent); + // emitted when all coefficients have been received and it is safe to call all functions again + void updateCoefficientsDone(bool success); + +private: + void loadCoefficientSetsThread(QStringList names = QStringList()); + void saveCoefficientSetsThread(); + + USBDevice *usb; + QString firmware; + int numPorts; + + std::vector coeffSets; +}; + +#endif // CALDEVICE_H diff --git a/Software/PC_Application/Calibration/LibreCAL/librecaldialog.cpp b/Software/PC_Application/Calibration/LibreCAL/librecaldialog.cpp new file mode 100644 index 0000000..25492df --- /dev/null +++ b/Software/PC_Application/Calibration/LibreCAL/librecaldialog.cpp @@ -0,0 +1,261 @@ +#include "librecaldialog.h" +#include "ui_librecaldialog.h" + +#include "caldevice.h" +#include "usbdevice.h" +#include "Device/virtualdevice.h" + +#include + +#include + +using namespace std; + +LibreCALDialog::LibreCALDialog(Calibration *cal) : + QDialog(nullptr), + ui(new Ui::LibreCALDialog), + cal(cal), + device(nullptr), + busy(false) +{ + ui->setupUi(this); + + createPortAssignmentUI(); + + connect(this, &LibreCALDialog::portAssignmentChanged, this, &LibreCALDialog::updateCalibrationStartStatus); + + connect(ui->cbDevice, &QComboBox::currentTextChanged, [=](QString text) { + if(device) { + delete device; + } + device = new CalDevice(text); + if(device) { + createPortAssignmentUI(); + connect(device, &CalDevice::updateCoefficientsPercent, ui->progressCoeff, &QProgressBar::setValue); + connect(device, &CalDevice::updateCoefficientsDone, [=](bool success){ + busy = false; + if(success) { + ui->progressCoeff->setValue(100); + ui->lCoefficientStatus->setText("Coefficients loaded."); + coeffSet = device->getCoefficientSets()[0]; + } else { + ui->progressCal->setValue(0); + ui->lCoefficientStatus->setText("Failed to load coefficients"); + } + updateCalibrationStartStatus(); + }); + + ui->cbCoefficients->clear(); + ui->cbCoefficients->addItem("Select..."); + for(auto c : device->getCoefficientSetNames()) { + ui->cbCoefficients->addItem(c); + } + ui->cbCoefficients->setEnabled(true); + } else { + ui->cbCoefficients->clear(); + ui->cbCoefficients->setEnabled(false); + ui->start->setEnabled(false); + } + }); + + connect(ui->cbCoefficients, &QComboBox::currentTextChanged, [=](){ + // no coefficient set selected + ui->progressCoeff->setValue(0); + ui->lCoefficientStatus->setText("No coefficients loaded"); + coeffSet = CalDevice::CoefficientSet(); + updateCalibrationStartStatus(); + + if(ui->cbCoefficients->currentIndex() > 0) { + if(!device) { + qWarning() << "Coefficients selected without connected device"; + return; + } + busy = true; + ui->lCoefficientStatus->setText("Loading coefficients..."); + device->loadCoefficientSets({ui->cbCoefficients->currentText()}); + } + }); + + auto deviceList = USBDevice::GetDevices(); + for(auto device : deviceList) { + ui->cbDevice->addItem(device); + } + + connect(this, &QDialog::finished, [=](){ + delete device; + device = nullptr; + }); + + connect(ui->start, &QPushButton::clicked, this, &LibreCALDialog::startCalibration); + + updateCalibrationStartStatus(); + updateDeviceStatus(); + connect(&updateTimer, &QTimer::timeout, this, &LibreCALDialog::updateDeviceStatus); + updateTimer.start(1000); +} + +LibreCALDialog::~LibreCALDialog() +{ + delete device; + delete ui; +} + +void LibreCALDialog::updateCalibrationStartStatus() +{ + bool canStart = true; + QString status = "Ready to start"; + if(!device) { + status = "Not connected to a LibreCAL device."; + canStart = false; + } + set usedCalPorts; + if(canStart) { + // Check port mapping for duplicate entries (and at least one used port) + for(auto port : portAssignment) { + if(port < 1) { + // skip unused ports + continue; + } + if(usedCalPorts.count(port)) { + status = "LibreCAL port "+QString::number(port)+" is assigned to multiple VNA ports."; + canStart = false; + break; + } else { + usedCalPorts.insert(port); + } + } + } + if(canStart) { + // at least one port must be used + if(usedCalPorts.size() == 0) { + status = "At least one port must be assigned."; + canStart = false; + } + } + if(canStart) { + // check if coefficients have been loaded + if(coeffSet.opens.size() != device->getNumPorts()) { + status = "Coefficients not loaded"; + canStart = false; + } + } + if(canStart) { + double coeffMinFreq = numeric_limits::max(); + double coeffMaxFreq = numeric_limits::lowest(); + + auto checkCoefficient = [&](CalDevice::CoefficientSet::Coefficient *c) -> bool { + if(c->t.points() == 0) { + return false; + } else { + if(c->t.minFreq() < coeffMinFreq) { + coeffMinFreq = c->t.minFreq(); + } + if(c->t.maxFreq() > coeffMaxFreq) { + coeffMaxFreq = c->t.maxFreq(); + } + return true; + } + }; + + // check if coefficients for all ports are available + for(auto i : usedCalPorts) { + // Check if OSL coefficients are there + if(!checkCoefficient(coeffSet.opens[i-1])) { + status = "Open coefficient for LibreCAL port "+QString::number(i)+" is missing."; + canStart = false; + break; + } + if(!checkCoefficient(coeffSet.shorts[i-1])) { + status = "Short coefficient for LibreCAL port "+QString::number(i)+" is missing."; + canStart = false; + break; + } + if(!checkCoefficient(coeffSet.loads[i-1])) { + status = "Load coefficient for LibreCAL port "+QString::number(i)+" is missing."; + canStart = false; + break; + } + for(auto j : usedCalPorts) { + if(j <= i) { + continue; + } + if(!checkCoefficient(coeffSet.getThrough(i,j))) { + status = "Through coefficient for LibreCAL port "+QString::number(i)+" to "+QString::number(j)+" is missing."; + canStart = false; + break; + } + } + } + } + + ui->lCalibrationStatus->setText(status); + ui->start->setEnabled(canStart); + if(canStart) { + ui->lCalibrationStatus->setStyleSheet("QLabel { color : black; }"); + } else { + ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }"); + } +} + +void LibreCALDialog::updateDeviceStatus() +{ + if(!device) { + ui->lDeviceStatus->setText("No LibreCAL connected"); + ui->lDeviceStatus->setStyleSheet("QLabel { color : red; }"); + return; + } + if(busy) { + // can't update while busy reading coefficients + return; + } + if(device->stabilized()) { + ui->lDeviceStatus->setText("LibreCAL ready for calibration"); + ui->lDeviceStatus->setStyleSheet("QLabel { color : black; }"); + } else { + ui->lDeviceStatus->setText("Heating up, please wait with calibration"); + ui->lDeviceStatus->setStyleSheet("QLabel { color : orange; }"); + } +} + +void LibreCALDialog::startCalibration() +{ + ui->cbDevice->setEnabled(false); + ui->cbCoefficients->setEnabled(false); + ui->start->setEnabled(false); + + ui->lCalibrationStatus->setText("Creating calibration kit from coefficients..."); + auto& kit = cal->getKit(); + kit = Calkit::fromLibreCAL(device, coeffSet); +} + +void LibreCALDialog::createPortAssignmentUI() +{ + auto layout = static_cast(ui->assignmentBox->layout()); + // Clear any possible previous elements + portAssignment.clear(); + while(layout->rowCount() > 1) { + layout->removeRow(1); + } + auto vnaPorts = VirtualDevice::getInfo(VirtualDevice::getConnected()).ports; + portAssignment.resize(vnaPorts, 0); + auto calPorts = 0; + if(device) { + calPorts = device->getNumPorts(); + } + QStringList choices = {"Unused"}; + for(int i=1;i<=calPorts;i++) { + choices.push_back("Port "+QString::number(i)); + } + for(int p = 1;p<=vnaPorts;p++) { + auto label = new QLabel("Port "+QString::number(p)+":"); + auto comboBox = new QComboBox(); + comboBox->addItems(choices); + connect(comboBox, qOverload(&QComboBox::currentIndexChanged), [=](){ + portAssignment[p-1] = comboBox->currentIndex(); + emit portAssignmentChanged(); + }); + // try to set the default + comboBox->setCurrentIndex(p); + layout->addRow(label, comboBox); + } +} diff --git a/Software/PC_Application/Calibration/LibreCAL/librecaldialog.h b/Software/PC_Application/Calibration/LibreCAL/librecaldialog.h new file mode 100644 index 0000000..15c2f0b --- /dev/null +++ b/Software/PC_Application/Calibration/LibreCAL/librecaldialog.h @@ -0,0 +1,40 @@ +#ifndef LIBRECALDIALOG_H +#define LIBRECALDIALOG_H + +#include "Calibration/calibration.h" +#include "caldevice.h" + +#include +#include + +namespace Ui { +class LibreCALDialog; +} + +class LibreCALDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LibreCALDialog(Calibration *cal); + ~LibreCALDialog(); + +private: +signals: + void portAssignmentChanged(); +private slots: + void updateCalibrationStartStatus(); + void updateDeviceStatus(); + void startCalibration(); +private: + void createPortAssignmentUI(); + Ui::LibreCALDialog *ui; + Calibration *cal; + CalDevice *device; + CalDevice::CoefficientSet coeffSet; + QTimer updateTimer; + bool busy; + std::vector portAssignment; +}; + +#endif // LIBRECALDIALOG_H diff --git a/Software/PC_Application/Calibration/LibreCAL/librecaldialog.ui b/Software/PC_Application/Calibration/LibreCAL/librecaldialog.ui new file mode 100644 index 0000000..ecc4e0c --- /dev/null +++ b/Software/PC_Application/Calibration/LibreCAL/librecaldialog.ui @@ -0,0 +1,144 @@ + + + LibreCALDialog + + + + 0 + 0 + 346 + 395 + + + + Electronic Calibration Dialog + + + true + + + + + + Device Selection + + + + + + + + Device: + + + + + + + + + + + + DEVICE_STATUS_PLACEHOLDER + + + + + + + + + Coefficients: + + + + + + + + + + + + COEFFICIENT_STATUS_PLACEHOLDER + + + + + + + 0 + + + + + + + + + + Port Assignments + + + + + + LibreVNA + + + Qt::AlignCenter + + + + + + + LibreCAL + + + Qt::AlignCenter + + + + + + + + + + Calibration + + + + + + Start + + + + + + + + + + CALIBRATION_STATUS_PLACEHOLDER + + + + + + + 0 + + + + + + + + + + + diff --git a/Software/PC_Application/Calibration/LibreCAL/usbdevice.cpp b/Software/PC_Application/Calibration/LibreCAL/usbdevice.cpp new file mode 100644 index 0000000..0007275 --- /dev/null +++ b/Software/PC_Application/Calibration/LibreCAL/usbdevice.cpp @@ -0,0 +1,253 @@ +#include "usbdevice.h" + +#include "CustomWidgets/informationbox.h" + +#include +#include +#include +#include +#include + +using namespace std; + +using USBID = struct { + int VID; + int PID; +}; +static constexpr USBID IDs[] = { + {0x0483, 0x4122}, +}; + +USBDevice::USBDevice(QString serial) +{ + 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" << 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) { + // empty response expected by commad + return rcv == ""; + } else { + // failed to send/receive + return false; + } +} + +QString USBDevice::Query(QString query) +{ + if(send(query)) { + QString rcv; + if(receive(&rcv)) { + return rcv; + } + } + return QString(); +} + + +std::set USBDevice::GetDevices() +{ + std::set 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 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 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:"< +#include +#include +#include + +class USBDevice +{ +public: + // connect to a CAL device. If serial is specified only connecting to this device, otherwise to the first one found + USBDevice(QString serial = QString()); + ~USBDevice(); + + bool Cmd(QString cmd); + QString Query(QString query); + QString serial() const; + + // Returns serial numbers of all connected devices + static std::set GetDevices(); + +private: + static void SearchDevices(std::function foundCallback, libusb_context *context, bool ignoreOpenError); + bool send(const QString &s); + bool receive(QString *s); + bool flushRX(); + libusb_device_handle *m_handle; + libusb_context *m_context; + + QString m_serial; +}; + +#endif // DEVICE_H diff --git a/Software/PC_Application/Calibration/calibration.cpp b/Software/PC_Application/Calibration/calibration.cpp index c89d735..0689568 100644 --- a/Software/PC_Application/Calibration/calibration.cpp +++ b/Software/PC_Application/Calibration/calibration.cpp @@ -3,6 +3,7 @@ #include "CustomWidgets/informationbox.h" #include "Util/app_common.h" #include "unit.h" +#include "LibreCAL/librecaldialog.h" #include "Eigen/Dense" @@ -339,6 +340,11 @@ void Calibration::edit() updateCalibrationList(); }); + connect(ui->eCal, &QPushButton::clicked, [=](){ + auto d = new LibreCALDialog(this); + d->show(); + }); + QObject::connect(ui->table, &QTableWidget::currentCellChanged, updateTableEditButtons); auto addMenu = new QMenu(); diff --git a/Software/PC_Application/Calibration/calibrationdialogui.ui b/Software/PC_Application/Calibration/calibrationdialogui.ui index ca94193..3e6a04c 100644 --- a/Software/PC_Application/Calibration/calibrationdialogui.ui +++ b/Software/PC_Application/Calibration/calibrationdialogui.ui @@ -159,6 +159,14 @@ + + + + Electronic +Calibration + + + diff --git a/Software/PC_Application/Calibration/calkit.cpp b/Software/PC_Application/Calibration/calkit.cpp index 07c8748..a536dc6 100644 --- a/Software/PC_Application/Calibration/calkit.cpp +++ b/Software/PC_Application/Calibration/calkit.cpp @@ -328,6 +328,47 @@ Calkit Calkit::fromFile(QString filename) return c; } +Calkit Calkit::fromLibreCAL(CalDevice *device, CalDevice::CoefficientSet s) +{ + Calkit ret; + ret.manufacturer = "LibreCAL ("+s.name+")"; + ret.serialnumber = device->serial(); + ret.description = "Automatically created from LibreCAL module"; + for(int i=1;i<=device->getNumPorts();i++) { + if(s.opens[i-1]->t.points() > 0) { + auto o = new CalStandard::Open(); + o->setName("Port "+QString::number(i)+" Open"); + o->setMeasurement(s.opens[i-1]->t); + ret.standards.push_back(o); + } + if(s.shorts[i-1]->t.points() > 0) { + auto o = new CalStandard::Short(); + o->setName("Port "+QString::number(i)+" Short"); + o->setMeasurement(s.shorts[i-1]->t); + ret.standards.push_back(o); + } + if(s.loads[i-1]->t.points() > 0) { + auto o = new CalStandard::Load(); + o->setName("Port "+QString::number(i)+" Load"); + o->setMeasurement(s.loads[i-1]->t); + ret.standards.push_back(o); + } + for(int j=i+1;j<=device->getNumPorts();j++) { + auto c = s.getThrough(i,j); + if(!c) { + continue; + } + if(c->t.points() > 0) { + auto o = new CalStandard::Through(); + o->setName("Port "+QString::number(i)+" to "+QString::number(j)+" Through"); + o->setMeasurement(c->t); + ret.standards.push_back(o); + } + } + } + return ret; +} + void Calkit::edit(std::function updateCal) { auto dialog = new CalkitDialog(*this); diff --git a/Software/PC_Application/Calibration/calkit.h b/Software/PC_Application/Calibration/calkit.h index 976ce8c..59db070 100644 --- a/Software/PC_Application/Calibration/calkit.h +++ b/Software/PC_Application/Calibration/calkit.h @@ -6,6 +6,8 @@ #include "calstandard.h" #include "savable.h" +#include "LibreCAL/caldevice.h" + #include #include #include @@ -41,6 +43,7 @@ public: void toFile(QString filename); static Calkit fromFile(QString filename); + static Calkit fromLibreCAL(CalDevice *device, CalDevice::CoefficientSet s); void edit(std::function updateCal = nullptr); std::vector getStandards() const; diff --git a/Software/PC_Application/Calibration/calstandard.cpp b/Software/PC_Application/Calibration/calstandard.cpp index c4234e5..270ecbf 100644 --- a/Software/PC_Application/Calibration/calstandard.cpp +++ b/Software/PC_Application/Calibration/calstandard.cpp @@ -86,6 +86,16 @@ unsigned long long Virtual::getID() return id; } +QString Virtual::getName() const +{ + return name; +} + +void Virtual::setName(const QString &value) +{ + name = value; +} + void OnePort::setMeasurement(const Touchstone &ts, int port) { if(!touchstone) { diff --git a/Software/PC_Application/Calibration/calstandard.h b/Software/PC_Application/Calibration/calstandard.h index d4b356f..c882cf8 100644 --- a/Software/PC_Application/Calibration/calstandard.h +++ b/Software/PC_Application/Calibration/calstandard.h @@ -43,6 +43,9 @@ public: unsigned long long getID(); + QString getName() const; + void setName(const QString &value); + protected: QString name; double minFreq; diff --git a/Software/PC_Application/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI.pro index 87d493f..4e3837e 100644 --- a/Software/PC_Application/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI.pro @@ -1,5 +1,8 @@ HEADERS += \ ../VNA_embedded/Application/Communication/Protocol.hpp \ + Calibration/LibreCAL/caldevice.h \ + Calibration/LibreCAL/librecaldialog.h \ + Calibration/LibreCAL/usbdevice.h \ Calibration/amplitudecaldialog.h \ Calibration/calibration.h \ Calibration/calibrationmeasurement.h \ @@ -139,6 +142,9 @@ HEADERS += \ SOURCES += \ ../VNA_embedded/Application/Communication/Protocol.cpp \ + Calibration/LibreCAL/caldevice.cpp \ + Calibration/LibreCAL/librecaldialog.cpp \ + Calibration/LibreCAL/usbdevice.cpp \ Calibration/amplitudecaldialog.cpp \ Calibration/calibration.cpp \ Calibration/calibrationmeasurement.cpp \ @@ -276,6 +282,7 @@ FORMS += \ Calibration/CalStandardOpenEditDialog.ui \ Calibration/CalStandardShortEditDialog.ui \ Calibration/CalStandardThroughEditDialog.ui \ + Calibration/LibreCAL/librecaldialog.ui \ Calibration/addamplitudepointsdialog.ui \ Calibration/amplitudecaldialog.ui \ Calibration/automaticamplitudedialog.ui \ diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index 28eab3e..f6e396b 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -42,6 +42,8 @@ Trace::Trace(QString name, QColor color, QString live) mathCalcTimer.setSingleShot(true); connect(&mathCalcTimer, &QTimer::timeout, this, &Trace::calculateMath); + fromLivedata(LivedataType::Overwrite, live); + self.enabled = false; dataType = DataType::Frequency; connect(this, &Trace::typeChanged, [=](){ diff --git a/Software/PC_Application/main.cpp b/Software/PC_Application/main.cpp index cabdd98..33393a6 100644 --- a/Software/PC_Application/main.cpp +++ b/Software/PC_Application/main.cpp @@ -5,10 +5,6 @@ #include #endif -#include "Tools/parameters.h" -#include -using namespace std; - static QApplication *app; static AppWindow *window; @@ -32,33 +28,7 @@ int main(int argc, char *argv[]) { window->getAppGitHash().left(9)); Device::RegisterTypes(); - - auto S11 = complex(-0.5, 0.25); - auto S22 = complex(0.5, 0.15); - auto S33 = complex(0.8, -0.25); - - auto S12 = complex(0.1, 0); - auto S21 = complex(0.2, 0.3); - - auto S13 = complex(0.3, -0.2); - auto S31 = complex(0.4, 0.4); - - auto S23 = complex(0.5, 0.2); - auto S32 = complex(0.6, -0.2); - - auto p12 = Sparam(S11, S12, S21, S22); - auto p12_only = Sparam(0.0, S12, 1.0, 0.0); - auto p13 = Sparam(S11, S13, S31, S33); - auto p23 = Sparam(S22, S23, S32, S33); - - // convert to 75 ohm - auto p12_75 = Sparam(ABCDparam(p12, 50.0), 75.0); - auto p12_only_75 = Sparam(ABCDparam(p12_only, 50.0), 75.0); - auto p13_75 = Sparam(ABCDparam(p12, 50.0), 75.0); - auto Zp23_75 = Sparam(ABCDparam(p12, 50.0), 75.0); - - auto p1 = Sparam(S11, 0.0, 0.0, 1.0); - auto p1_75 = Sparam(ABCDparam(p12, 50.0), 75.0); + VirtualDevice::RegisterTypes(); #ifdef Q_OS_UNIX signal(SIGINT, tryExitGracefully); diff --git a/Software/PC_Application/touchstone.cpp b/Software/PC_Application/touchstone.cpp index f4d033c..d51c1da 100644 --- a/Software/PC_Application/touchstone.cpp +++ b/Software/PC_Application/touchstone.cpp @@ -445,3 +445,8 @@ void Touchstone::setReferenceImpedance(double value) { referenceImpedance = value; } + +void Touchstone::setFilename(const QString &value) +{ + filename = value; +} diff --git a/Software/PC_Application/touchstone.h b/Software/PC_Application/touchstone.h index 10ae410..f9da94f 100644 --- a/Software/PC_Application/touchstone.h +++ b/Software/PC_Application/touchstone.h @@ -47,6 +47,7 @@ public: void reduceTo1Port(unsigned int port); unsigned int ports() { return m_ports; } QString getFilename() const; + void setFilename(const QString &value); virtual nlohmann::json toJSON(); virtual void fromJSON(nlohmann::json j); @@ -54,6 +55,7 @@ public: double getReferenceImpedance() const; void setReferenceImpedance(double value); + private: unsigned int m_ports; double referenceImpedance;