#include "librecaldialog.h" #include "ui_librecaldialog.h" #include "caldevice.h" #include "usbdevice.h" #include "CustomWidgets/informationbox.h" #include "preferences.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); setAttribute(Qt::WA_DeleteOnClose); createPortAssignmentUI(); connect(this, &LibreCALDialog::portAssignmentChanged, this, &LibreCALDialog::updateCalibrationStartStatus); connect(ui->cbDevice, &QComboBox::currentTextChanged, [=](QString text) { if(device) { delete device; device = nullptr; } try { device = new CalDevice(text); } catch (exception &e) { device = nullptr; InformationBox::ShowError("Failed to connect", e.what(), this); } if(device) { createPortAssignmentUI(); connect(device, &CalDevice::updateCoefficientsPercent, ui->progressCoeff, &QProgressBar::setValue); connect(device, &CalDevice::updateCoefficientsDone, this, [=](bool success){ busy = false; updateCalibrationStartStatus(); if(success) { ui->progressCoeff->setValue(100); ui->lCalibrationStatus->setText("Coefficients loaded."); coeffSet = device->getCoefficientSets()[0]; if(validateCoefficients()) { startCalibration(); } else { enableUI(); } } else { ui->progressCal->setValue(0); ui->lCalibrationStatus->setText("Failed to load coefficients"); } }, Qt::QueuedConnection); ui->cbCoefficients->clear(); ui->cbCoefficients->addItem("Select..."); for(auto c : device->getCoefficientSetNames()) { ui->cbCoefficients->addItem(c); } ui->cbCoefficients->setEnabled(true); // select first available coefficient set if(ui->cbCoefficients->count() > 1) { ui->cbCoefficients->setCurrentIndex(1); } } 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); coeffSet = CalDevice::CoefficientSet(); ui->lCalibrationStatus->setText("No coefficients loaded"); updateCalibrationStartStatus(); }); auto deviceList = USBDevice::GetDevices(); for(auto device : deviceList) { ui->cbDevice->addItem(device); } connect(ui->start, &QPushButton::clicked, this, &LibreCALDialog::determineAutoPorts); connect(this, &LibreCALDialog::autoPortComplete, this, &LibreCALDialog::loadCoefficients); updateCalibrationStartStatus(); updateDeviceStatus(); connect(&updateTimer, &QTimer::timeout, this, &LibreCALDialog::updateDeviceStatus); updateTimer.start(1000); } LibreCALDialog::~LibreCALDialog() { if(device) { device->abortCoefficientLoading(); } delete device; delete ui; } bool LibreCALDialog::validatePortSelection(bool autoAllowed) { set usedCalPorts; QString status; bool canStart = true; // Check port mapping for duplicate entries (and at least one used port) for(auto port : portAssignment) { if(autoAllowed && port == -1) { // auto port, to be determined later usedCalPorts.insert(port); // skip duplicate selection continue; } else 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) { ui->lCalibrationStatus->setText(status); ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }"); } return canStart; } bool LibreCALDialog::validateCoefficients() { bool canStart = true; QString status; double coeffMinFreq = numeric_limits::max(); double coeffMaxFreq = numeric_limits::lowest(); auto checkCoefficient = [&](CalDevice::CoefficientSet::Coefficient *c) -> bool { if(!c) { return false; } 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; } }; set usedCalPorts; // Check port mapping for duplicate entries (and at least one used port) for(auto port : portAssignment) { if(port > 0) { usedCalPorts.insert(port); } } // check if coefficients for all ports are available for(auto i : usedCalPorts) { // Check if OSL coefficients are there if(!checkCoefficient(coeffSet.getOpen(i))) { status = "Open coefficient for LibreCAL port "+QString::number(i)+" is missing."; canStart = false; break; } if(!checkCoefficient(coeffSet.getShort(i))) { status = "Short coefficient for LibreCAL port "+QString::number(i)+" is missing."; canStart = false; break; } if(!checkCoefficient(coeffSet.getLoad(i))) { 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; } } } if(!canStart) { ui->lCalibrationStatus->setText(status); ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }"); } return canStart; } void LibreCALDialog::updateCalibrationStartStatus() { bool canStart = true; if(!device) { canStart = false; ui->lCalibrationStatus->setText("Not connected to a LibreCAL device"); ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }"); } if(canStart) { if(ui->cbCoefficients->currentIndex() == 0) { canStart = false; ui->lCalibrationStatus->setText("No coefficient set selected"); ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }"); } } if(canStart) { canStart = validatePortSelection(true); } if(canStart) { if(!Preferences::getInstance().Acquisition.allowUseOfUnstableLibreCALTemp && !device->stabilized()) { canStart = false; ui->lCalibrationStatus->setText("LibreCAL temperature unstable"); ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }"); } } ui->start->setEnabled(canStart); if(canStart) { ui->lCalibrationStatus->setText("Ready to start"); ui->lCalibrationStatus->setStyleSheet(""); } } 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(""); } else { ui->lDeviceStatus->setText("Heating up, please wait with calibration"); ui->lDeviceStatus->setStyleSheet("QLabel { color : orange; }"); } updateCalibrationStartStatus(); } void LibreCALDialog::determineAutoPorts() { disableUI(); ui->progressCal->setValue(0); ui->lCalibrationStatus->setText("Autodetecting port connections..."); ui->lCalibrationStatus->setStyleSheet("QLabel { color : green; }"); // check if any ports are set to auto bool usesAuto = false; for(auto port : portAssignment) { if(port == -1) { usesAuto = true; break; } } if(usesAuto) { driver = DeviceDriver::getActiveDriver(); emit driver->acquireControl(); // Determine ports by setting all ports to open and then switching them to short one at a time while observing the change in the VNA measurement for(unsigned int i=1;i<=device->getNumPorts();i++) { device->setStandard(i, CalDevice::Standard::Type::Open); } autoPortMeasurements.clear(); startSweep(); } else { // no auto ports emit autoPortComplete(); } } void LibreCALDialog::loadCoefficients() { // validate ports again, at this point no "auto" selection is allowed anymore if(!validatePortSelection(false)) { enableUI(); updateCalibrationStartStatus(); } else { // can continue with loading coefficients // they might already be loaded from a previous calibration run, check first if(!validateCoefficients()) { // only load required coefficients ui->progressCal->setValue(0); ui->lCalibrationStatus->setText("Loading coefficients..."); ui->lCalibrationStatus->setStyleSheet("QLabel { color : green; }"); // determine the used ports of the LibreCAL QList usedPorts; for(auto port : portAssignment) { if(port > 0) { usedPorts.append(port); } } busy = true; device->loadCoefficientSets({ui->cbCoefficients->currentText()}, usedPorts); } else { // can proceed with calibration directly startCalibration(); } } } void LibreCALDialog::handleIncomingMeasurement(DeviceDriver::VNAMeasurement m) { stopSweep(); autoPortMeasurements.push_back(m); auto nextPort = autoPortMeasurements.size(); // switch LibreCAL to next port if(nextPort <= device->getNumPorts()) { device->setStandard(nextPort, CalDevice::Standard::Type::Short); } if(nextPort > 1) { device->setStandard(nextPort-1, CalDevice::Standard::Type::Open); } if(nextPort > device->getNumPorts()) { // auto port measurements complete // reset LibreCAL ports for(unsigned int i=1;i<=device->getNumPorts();i++) { device->setStandard(i, CalDevice::Standard::Type::None); } // evaluate results for(unsigned int p=0;p> measurements; for(auto m : autoPortMeasurements) { if(m.measurements.count(param)) { measurements.push_back(m.measurements[param]); } } if(measurements.size() != device->getNumPorts()+1) { // not all measurements available (maybe the port has no stimulus?), set to unused portAssignmentComboBoxes[p]->setCurrentIndex(0); continue; } // got all required measurements, determine which one deviates the most from the baseline double maxDeviation = 0.0; int maxDevIndex = 0; for(unsigned int i=1;i<=device->getNumPorts();i++) { auto diff = abs(measurements[i] - measurements[0]); if (diff > maxDeviation) { maxDeviation = diff; maxDevIndex = i; } } constexpr double minRequiredDeviation = 0.25; if(maxDeviation > minRequiredDeviation) { portAssignmentComboBoxes[p]->setCurrentIndex(maxDevIndex+1); } else { // not enough deviation, probably unused portAssignmentComboBoxes[p]->setCurrentIndex(0); } } emit driver->releaseControl(); emit autoPortComplete(); } else { // trigger the next measurement startSweep(); } } void LibreCALDialog::startSweep() { // set up a sweep with a single measurement point at the start frequency auto info = driver->getInfo(); DeviceDriver::VNASettings s = {}; s.dBmStart = info.Limits.VNA.maxdBm; s.dBmStop = info.Limits.VNA.maxdBm; auto freq = info.Limits.VNA.minFreq == 0 ? 100000 : info.Limits.VNA.minFreq; s.freqStart = freq; s.freqStop = freq; s.IFBW = 100; s.logSweep = false; s.points = 1; for(unsigned int i=1;i<=info.Limits.VNA.ports;i++) { s.excitedPorts.push_back(i); } driver->setVNA(s, [=](bool){ connect(driver, &DeviceDriver::VNAmeasurementReceived, this, &LibreCALDialog::handleIncomingMeasurement, Qt::DirectConnection); }); } void LibreCALDialog::stopSweep() { disconnect(driver, &DeviceDriver::VNAmeasurementReceived, this, &LibreCALDialog::handleIncomingMeasurement); driver->setIdle(); } void LibreCALDialog::startCalibration() { disableUI(); busy = true; ui->progressCal->setValue(0); ui->lCalibrationStatus->setText("Creating calibration kit from coefficients..."); ui->lCalibrationStatus->setStyleSheet(""); auto& kit = cal->getKit(); kit.clearStandards(); kit.manufacturer = "LibreCAL ("+coeffSet.name+")"; kit.serialnumber = device->serial(); kit.description = "Automatically created from LibreCAL module"; // determine the used ports of the LibreCAL QList usedPorts; for(auto port : portAssignment) { if(port > 0) { usedPorts.append(port); } } std::sort(usedPorts.begin(), usedPorts.end()); std::map openStandards; std::map shortStandards; std::map loadStandards; std::map throughStandards; for(unsigned int idx=0;idxt.points() > 0) { auto o = new CalStandard::Open(); o->setName("Port "+QString::number(i)); o->setMeasurement(coeffSet.getOpen(i)->t); openStandards[i] = o; kit.addStandard(o); } if(coeffSet.getShort(i)->t.points() > 0) { auto o = new CalStandard::Short(); o->setName("Port "+QString::number(i)); o->setMeasurement(coeffSet.getShort(i)->t); shortStandards[i] = o; kit.addStandard(o); } if(coeffSet.getLoad(i)->t.points() > 0) { auto o = new CalStandard::Load(); o->setName("Port "+QString::number(i)); o->setMeasurement(coeffSet.getLoad(i)->t); loadStandards[i] = o; kit.addStandard(o); } for(unsigned int jdx=idx+1;jdxt.points() > 0) { auto o = new CalStandard::Through(); o->setName("Port "+QString::number(i)+" to "+QString::number(j)); o->setMeasurement(c->t); throughStandards[coeffSet.portsToThroughIndex(i, j)] = o; kit.addStandard(o); } } } ui->lCalibrationStatus->setText("Creating calibration measurements..."); cal->reset(); auto vnaPorts = DeviceDriver::getInfo(DeviceDriver::getActiveDriver()).Limits.VNA.ports; set openMeasurements; set shortMeasurements; set loadMeasurements; vector throughMeasurements; for(unsigned int p=0;psetPort(p+1); open->setStandard(openStandards[portAssignment[p]]); openMeasurements.insert(open); cal->measurements.push_back(open); auto _short = new CalibrationMeasurement::Short(cal); _short->setPort(p+1); _short->setStandard(shortStandards[portAssignment[p]]); shortMeasurements.insert(_short); cal->measurements.push_back(_short); auto load = new CalibrationMeasurement::Load(cal); load->setPort(p+1); load->setStandard(loadStandards[portAssignment[p]]); loadMeasurements.insert(load); cal->measurements.push_back(load); for(unsigned int p2=p+1;p2setPort1(p+1); through->setPort2(p2+1); // find correct through standard int libreCALp1 = portAssignment[p]; int libreCALp2 = portAssignment[p2]; QString forwardName = "Port "+QString::number(libreCALp1)+" to "+QString::number(libreCALp2); QString reverseName = "Port "+QString::number(libreCALp2)+" to "+QString::number(libreCALp1); for(auto ts : throughStandards) { if(ts.second->getName() == forwardName) { through->setStandard(ts.second); through->setReverseStandard(false); } else if(ts.second->getName() == reverseName) { through->setStandard(ts.second); through->setReverseStandard(true); } } throughMeasurements.push_back(through); cal->measurements.push_back(through); } } ui->lCalibrationStatus->setText("Taking calibration measurements..."); measurementsTaken = 0; auto setTerminationOnAllUsedPorts = [=](CalDevice::Standard s) { for(auto p : portAssignment) { if(p > 0) { device->setStandard(p, s); } } }; auto startNextCalibrationStep = [=]() { // indicate calibration percentage auto totalMeasurements = 3 + throughMeasurements.size(); ui->progressCal->setValue(measurementsTaken * 100 / totalMeasurements); switch(measurementsTaken) { case 0: setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::Open)); emit cal->startMeasurements(openMeasurements); break; case 1: setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::Short)); emit cal->startMeasurements(shortMeasurements); break; case 2: setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::Load)); emit cal->startMeasurements(loadMeasurements); break; default: { // into through measurements now unsigned int throughIndex = measurementsTaken - 3; if(throughIndex >= throughMeasurements.size()) { // this was the last measurement // Try to apply the calibration Calibration::CalType type; type.type = Calibration::Type::SOLT; for(unsigned int i=0;i 0) { // this VNA port was used in the calibration type.usedPorts.push_back(i+1); } } auto res = cal->compute(type); if(res) { ui->progressCal->setValue(100); ui->lCalibrationStatus->setText("Calibration activated."); } else { ui->progressCal->setValue(0); ui->lCalibrationStatus->setText("Failed to activate calibration."); ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }"); } // sever connection to this function disconnect(cal, &Calibration::measurementsUpdated, this, nullptr); setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None)); enableUI(); busy = false; break; } setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None)); auto m = throughMeasurements[throughIndex]; device->setStandard(portAssignment[m->getPort1()-1], CalDevice::Standard(portAssignment[m->getPort2()-1])); emit cal->startMeasurements({m}); } break; } measurementsTaken++; }; disconnect(cal, &Calibration::measurementsUpdated, this, nullptr); connect(cal, &Calibration::measurementsUpdated, this, startNextCalibrationStep, Qt::QueuedConnection); connect(cal, &Calibration::measurementsAborted, this, [=](){ setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None)); enableUI(); busy = false; ui->lCalibrationStatus->setText("Ready to start"); }); startNextCalibrationStep(); } void LibreCALDialog::disableUI() { ui->cbDevice->setEnabled(false); ui->cbCoefficients->setEnabled(false); ui->start->setEnabled(false); for(auto cb : portAssignmentComboBoxes) { cb->setEnabled(false); } } void LibreCALDialog::enableUI() { ui->cbDevice->setEnabled(true); ui->cbCoefficients->setEnabled(true); ui->start->setEnabled(true); for(auto cb : portAssignmentComboBoxes) { cb->setEnabled(true); } } void LibreCALDialog::createPortAssignmentUI() { auto layout = static_cast(ui->assignmentBox->layout()); // Clear any possible previous elements portAssignment.clear(); portAssignmentComboBoxes.clear(); while(layout->rowCount() > 1) { layout->removeRow(1); } auto vnaPorts = DeviceDriver::getInfo(DeviceDriver::getActiveDriver()).Limits.VNA.ports; portAssignment.resize(vnaPorts, 0); auto calPorts = 0; if(device) { calPorts = device->getNumPorts(); } QStringList choices = {"Unused", "Auto"}; for(int i=1;i<=calPorts;i++) { choices.push_back("Port "+QString::number(i)); } for(unsigned 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), [=](){ if(comboBox->currentText().startsWith("Unused")) { portAssignment[p-1] = 0; } else if(comboBox->currentText().startsWith("Auto")) { portAssignment[p-1] = -1; } else if(comboBox->currentText().startsWith("Port")) { portAssignment[p-1] = comboBox->currentText().back().digitValue(); } emit portAssignmentChanged(); }); comboBox->setCurrentIndex(1); layout->addRow(label, comboBox); portAssignmentComboBoxes.push_back(comboBox); } }