diff --git a/Documentation/UserManual/ProgrammingGuide.pdf b/Documentation/UserManual/ProgrammingGuide.pdf index 021a813..c9ef6c0 100644 Binary files a/Documentation/UserManual/ProgrammingGuide.pdf and b/Documentation/UserManual/ProgrammingGuide.pdf differ diff --git a/Documentation/UserManual/ProgrammingGuide.tex b/Documentation/UserManual/ProgrammingGuide.tex index 4236ce5..09e5808 100644 --- a/Documentation/UserManual/ProgrammingGuide.tex +++ b/Documentation/UserManual/ProgrammingGuide.tex @@ -505,6 +505,18 @@ $$ S_{11}...S_{1n},S_{21}...S_{2n},...,S_{n1}...S_{nn} $$ \subsubsection{VNA:CALibration:BUSY} \query{Queries whether a calibration measurement is ongoing}{VNA:CALibration:BUSY?}{None}{TRUE or FALSE} +\subsubsection{VNA:CALibration:SAVE} +\event{Saves the active calibration to a file}{VNA:CALibration:SAVE}{} +Important points when saving/loading calibration files through SCPI commands: +\begin{itemize} +\item Filenames must be either absolute or relative to the location of the GUI application. +\item SCPI parsing implicitly capitalizes all commands, the file will be saved using only uppercase letters. Similarly, it is not possible to load a file whose filename contains lowercase characters. +\item If the LibreVNA-GUI (and thus also the SCPI server) is running on a different machine than the SCPI client, the calibration files will be saved/loaded from the machine that runs the GUI. +\end{itemize} + +\subsubsection{VNA:CALibration:LOAD} +\query{Loads a calibration file}{VNA:CALibration:LOAD?}{}{TRUE or FALSE} + \subsection{Signal Generator Commands} These commands change or query signal generator settings. Although most of them are available regardless of the current device mode, they usually only have an effect once the generator mode is active. diff --git a/Software/PC_Application/Calibration/amplitudecaldialog.cpp b/Software/PC_Application/Calibration/amplitudecaldialog.cpp index b8373a0..3dc8280 100644 --- a/Software/PC_Application/Calibration/amplitudecaldialog.cpp +++ b/Software/PC_Application/Calibration/amplitudecaldialog.cpp @@ -82,7 +82,7 @@ AmplitudeCalDialog::AmplitudeCalDialog(Device *dev, QWidget *parent) : file >> j; for(auto point : j) { if(!point.contains("Frequency") || !point.contains("Port1") || !point.contains("Port2")) { - QMessageBox::warning(this, "Error loading file", "Failed to parse calibration point"); + InformationBox::ShowError("Error loading file", "Failed to parse calibration point"); return; } CorrectionPoint p; @@ -425,9 +425,7 @@ void AmplitudeCalDialog::ReceivedMeasurement(Protocol::SpectrumAnalyzerResult re bool AmplitudeCalDialog::ConfirmActionIfEdited() { if(edited) { - auto reply = QMessageBox::question(this, "Confirm action", "Some points have been edited but not saved in the device yet. If you continue, all changes will be lost (unless they are already saved to a file). Do you want to continue?", - QMessageBox::Yes|QMessageBox::No); - return reply == QMessageBox::Yes; + return InformationBox::AskQuestion("Confirm action", "Some points have been edited but not saved in the device yet. If you continue, all changes will be lost (unless they are already saved to a file). Do you want to continue?", true); } else { // not edited yet, nothing to confirm return true; diff --git a/Software/PC_Application/Calibration/calibration.cpp b/Software/PC_Application/Calibration/calibration.cpp index a26405f..4579cec 100644 --- a/Software/PC_Application/Calibration/calibration.cpp +++ b/Software/PC_Application/Calibration/calibration.cpp @@ -6,6 +6,7 @@ #include "unit.h" #include #include "Tools/parameters.h" +#include "CustomWidgets/informationbox.h" using namespace std; @@ -127,7 +128,7 @@ bool Calibration::constructErrorTerms(Calibration::Type type) + "The measured calibration data covers " + Unit::ToString(minFreq, "Hz", " kMG", 4) + " to " + Unit::ToString(maxFreq, "Hz", " kMG", 4) + ", however the calibration kit is only valid from " + Unit::ToString(kit_minFreq, "Hz", " kMG", 4) + " to " + Unit::ToString(kit_maxFreq, "Hz", " kMG", 4) + ".\n\n" + "Please adjust the calibration kit or the span and take the calibration measurements again."; - QMessageBox::critical(nullptr, "Unable to perform calibration", msg); + InformationBox::ShowError("Unable to perform calibration", msg); qWarning() << msg; return false; } @@ -732,6 +733,13 @@ bool Calibration::openFromFile(QString filename) return false; } } + + // force correct file ending + if(filename.toLower().endsWith(".cal")) { + filename.chop(4); + filename += ".cal"; + } + qDebug() << "Attempting to open calibration from file" << filename; // reset all data before loading new calibration @@ -749,16 +757,24 @@ bool Calibration::openFromFile(QString filename) try { kit = Calkit::fromFile(calkit_file); } catch (runtime_error e) { - QMessageBox::warning(nullptr, "Missing calibration kit", "The calibration kit file associated with the selected calibration could not be parsed. The calibration might not be accurate. (" + QString(e.what()) + ")"); + InformationBox::ShowError("Missing calibration kit", "The calibration kit file associated with the selected calibration could not be parsed. The calibration might not be accurate. (" + QString(e.what()) + ")"); qWarning() << "Parsing of calibration kit failed while opening calibration file: " << e.what(); } ifstream file; + file.open(filename.toStdString()); + if(!file.good()) { + QString msg = "Unable to open file: "+filename; + InformationBox::ShowError("Error", msg); + qWarning() << msg; + return false; + } + try { file >> *this; - } catch(runtime_error e) { - QMessageBox::warning(nullptr, "File parsing error", e.what()); + } catch(exception e) { + InformationBox::ShowError("File parsing error", e.what()); qWarning() << "Calibration file parsing failed: " << e.what(); return false; } @@ -778,7 +794,7 @@ bool Calibration::saveToFile(QString filename) } } - if(filename.endsWith(".cal")) { + if(filename.toLower().endsWith(".cal")) { filename.chop(4); } auto calibration_file = filename + ".cal"; diff --git a/Software/PC_Application/Calibration/calkit.cpp b/Software/PC_Application/Calibration/calkit.cpp index a180058..37ddb1d 100644 --- a/Software/PC_Application/Calibration/calkit.cpp +++ b/Software/PC_Application/Calibration/calkit.cpp @@ -7,6 +7,7 @@ #include "json.hpp" #include #include +#include "CustomWidgets/informationbox.h" using json = nlohmann::json; using namespace std; @@ -79,7 +80,11 @@ Calkit Calkit::fromFile(QString filename) } json j; - file >> j; + try { + file >> j; + } catch (exception &e) { + throw runtime_error("JSON parsing error: " + string(e.what())); + } if(j.contains("SOLT")) { qDebug() << "JSON format detected"; // calkit file uses json format, parse @@ -167,13 +172,9 @@ Calkit Calkit::fromFile(QString filename) c.TRL.Line.minFreq = readLine(file).toDouble(); c.TRL.Line.maxFreq = readLine(file).toDouble(); - auto msg = new QMessageBox(); - msg->setWindowTitle("Loading calkit file"); - msg->setText("The file \"" + filename + "\" is stored in a deprecated" + InformationBox::ShowMessage("Loading calkit file", "The file \"" + filename + "\" is stored in a deprecated" " calibration kit format. Future versions of this application might not support" " it anymore. Please save the calibration kit to update to the new format"); - msg->setStandardButtons(QMessageBox::Ok); - msg->show(); } file.close(); diff --git a/Software/PC_Application/Calibration/calkitdialog.cpp b/Software/PC_Application/Calibration/calkitdialog.cpp index 3298ce1..924eca7 100644 --- a/Software/PC_Application/Calibration/calkitdialog.cpp +++ b/Software/PC_Application/Calibration/calkitdialog.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "CustomWidgets/informationbox.h" using namespace std; @@ -119,7 +120,12 @@ CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) : connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){ auto filename = QFileDialog::getOpenFileName(this, "Open calibration kit coefficients", "", "Calibration kit files (*.calkit)", nullptr, QFileDialog::DontUseNativeDialog); if(filename.length() > 0) { - ownKit = Calkit::fromFile(filename); + try { + ownKit = Calkit::fromFile(filename); + } catch (runtime_error e) { + InformationBox::ShowError("Error", "The calibration kit file could not be parsed (" + QString(e.what()) + ")"); + qWarning() << "Parsing of calibration kit failed while opening calibration file: " << e.what(); + } updateEntries(); } }); diff --git a/Software/PC_Application/CustomWidgets/informationbox.cpp b/Software/PC_Application/CustomWidgets/informationbox.cpp index 5357b29..223c22f 100644 --- a/Software/PC_Application/CustomWidgets/informationbox.cpp +++ b/Software/PC_Application/CustomWidgets/informationbox.cpp @@ -3,8 +3,15 @@ #include #include +bool InformationBox::has_gui = true; + void InformationBox::ShowMessage(QString title, QString message, QString messageID, bool block) { + if(!has_gui) { + // no gui option active, do not show any messages + return; + } + // check if the user still wants to see this message unsigned int hash; if(messageID.isEmpty()) { @@ -31,12 +38,21 @@ void InformationBox::ShowMessageBlocking(QString title, QString message, QString void InformationBox::ShowError(QString title, QString message) { + if(!has_gui) { + // no gui option active, do not show any messages + return; + } auto box = new InformationBox(title, message, QMessageBox::Information, 0, nullptr); box->show(); } bool InformationBox::AskQuestion(QString title, QString question, bool defaultAnswer, QString messageID) { + if(!has_gui) { + // no gui option active, do not show any messages + return defaultAnswer; + } + // check if the user still wants to see this message unsigned int hash; if(messageID.isEmpty()) { @@ -61,6 +77,11 @@ bool InformationBox::AskQuestion(QString title, QString question, bool defaultAn } } +void InformationBox::setGUI(bool enable) +{ + has_gui = enable; +} + InformationBox::InformationBox(QString title, QString message, Icon icon, unsigned int hash, QWidget *parent) : QMessageBox(parent), hash(hash) diff --git a/Software/PC_Application/CustomWidgets/informationbox.h b/Software/PC_Application/CustomWidgets/informationbox.h index d1e5494..f43b5ca 100644 --- a/Software/PC_Application/CustomWidgets/informationbox.h +++ b/Software/PC_Application/CustomWidgets/informationbox.h @@ -12,11 +12,14 @@ public: static void ShowError(QString title, QString message); // Display a dialog with yes/no buttons. Returns true if yes is clicked, false otherwise. If the user has selected to never see this message again, defaultAnswer is returned instead static bool AskQuestion(QString title, QString question, bool defaultAnswer, QString messageID = QString()); + + static void setGUI(bool enable); private: InformationBox(QString title, QString message, QMessageBox::Icon icon, unsigned int hash, QWidget *parent); ~InformationBox(); static QString hashToSettingsKey(unsigned int hash); unsigned int hash; + static bool has_gui; }; #endif // INFORMATIONBOX_H diff --git a/Software/PC_Application/Device/device.cpp b/Software/PC_Application/Device/device.cpp index f33e8e4..c0d3728 100644 --- a/Software/PC_Application/Device/device.cpp +++ b/Software/PC_Application/Device/device.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "CustomWidgets/informationbox.h" using namespace std; @@ -169,8 +170,7 @@ Device::Device(QString serial) if(!m_handle) { QString message = "No device found"; - auto msg = new QMessageBox(QMessageBox::Icon::Warning, "Error opening device", message); - msg->show(); + InformationBox::ShowError("Error opening device", message); libusb_exit(m_context); throw std::runtime_error(message.toStdString()); return; @@ -186,8 +186,7 @@ Device::Device(QString serial) message.append(libusb_strerror((libusb_error) ret)); message.append("\" Maybe you are already connected to this device?"); qWarning() << message; - auto msg = new QMessageBox(QMessageBox::Icon::Warning, "Error opening device", message); - msg->show(); + InformationBox::ShowError("Error opening device", message); libusb_exit(m_context); throw std::runtime_error(message.toStdString()); } @@ -359,8 +358,7 @@ void Device::SearchDevices(std::function "this device (is another instance of the application already runnning? " "If that is not the case, you can try installing the WinUSB driver using Zadig (https://zadig.akeo.ie/)"); qWarning() << message; - auto msg = new QMessageBox(QMessageBox::Icon::Warning, "Error opening device", message); - msg->show(); + InformationBox::ShowError("Error opening device", message); } continue; } diff --git a/Software/PC_Application/Traces/Math/medianfilter.cpp b/Software/PC_Application/Traces/Math/medianfilter.cpp index 5f225f2..c0a9691 100644 --- a/Software/PC_Application/Traces/Math/medianfilter.cpp +++ b/Software/PC_Application/Traces/Math/medianfilter.cpp @@ -2,7 +2,7 @@ #include "ui_medianfilterdialog.h" #include "ui_medianexplanationwidget.h" -#include +#include "CustomWidgets/informationbox.h" using namespace Math; using namespace std; @@ -34,7 +34,7 @@ void MedianFilter::edit() connect(ui->kernelSize, qOverload(&QSpinBox::valueChanged), [=](int newval) { if((newval & 0x01) == 0) { - QMessageBox::information(d, "Median filter", "Only odd values are allowed for the kernel size"); + InformationBox::ShowMessageBlocking("Median filter", "Only odd values are allowed for the kernel size"); newval++; } ui->kernelSize->setValue(newval); diff --git a/Software/PC_Application/VNA/Deembedding/portextension.h b/Software/PC_Application/VNA/Deembedding/portextension.h index 5c7348c..53e4e81 100644 --- a/Software/PC_Application/VNA/Deembedding/portextension.h +++ b/Software/PC_Application/VNA/Deembedding/portextension.h @@ -41,12 +41,10 @@ private: // status variables for automatic measurements Calkit *kit; -// bool measuring; bool isPort1; bool isOpen; bool isIdeal; -// std::vector measurements; - QMessageBox *msgBox; + Ui::PortExtensionEditDialog *ui; }; diff --git a/Software/PC_Application/VNA/Deembedding/twothru.h b/Software/PC_Application/VNA/Deembedding/twothru.h index 786cb2b..f8c0bdf 100644 --- a/Software/PC_Application/VNA/Deembedding/twothru.h +++ b/Software/PC_Application/VNA/Deembedding/twothru.h @@ -41,7 +41,6 @@ private: std::vector points; bool measuring2xthru; bool measuringDUT; - QMessageBox *msgBox; Ui::TwoThruDialog *ui; }; diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index d236cb7..0859876 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -1062,7 +1062,7 @@ void VNA::ApplyCalibration(Calibration::Type type) DisableCalibration(true); } } catch (runtime_error e) { - QMessageBox::critical(window, "Calibration failure", e.what()); + InformationBox::ShowError("Calibration failure", e.what()); DisableCalibration(true); } } else { @@ -1325,6 +1325,35 @@ void VNA::SetupSCPI() scpi_cal->add(new SCPICommand("BUSy", nullptr, [=](QStringList) -> QString { return CalibrationMeasurementActive() ? "TRUE" : "FALSE"; })); + scpi_cal->add(new SCPICommand("SAVE", [=](QStringList params) -> QString { + if(params.size() != 1 || !calValid) { + // no filename given or no calibration active + return "ERROR"; + } + if(!cal.saveToFile(params[0])) { + // some error when writing the calibration file + return "ERROR"; + } + calEdited = false; + return ""; + }, nullptr)); + scpi_cal->add(new SCPICommand("LOAD", nullptr, [=](QStringList params) -> QString { + if(params.size() != 1) { + // no filename given or no calibration active + return "FALSE"; + } + if(!cal.openFromFile(params[0])) { + // some error when loading the calibration file + return "FALSE"; + } + if(cal.getType() == Calibration::Type::None) { + DisableCalibration(); + } else { + ApplyCalibration(cal.getType()); + } + calEdited = false; + return "TRUE"; + })); } void VNA::ConstrainAndUpdateFrequencies() diff --git a/Software/PC_Application/appwindow.cpp b/Software/PC_Application/appwindow.cpp index c63ec15..011b09d 100644 --- a/Software/PC_Application/appwindow.cpp +++ b/Software/PC_Application/appwindow.cpp @@ -49,6 +49,7 @@ #include "Calibration/frequencycaldialog.h" #include #include "CustomWidgets/jsonpickerdialog.h" +#include "CustomWidgets/informationbox.h" #include #include "Util/app_common.h" #include "about.h" @@ -160,7 +161,12 @@ AppWindow::AppWindow(QWidget *parent) return; } nlohmann::json j; - file >> j; + try { + file >> j; + } catch (exception &e) { + InformationBox::ShowError("Error", "Failed to parse the setup file (" + QString(e.what()) + ")"); + qWarning() << "Parsing of setup file failed: " << e.what(); + } file.close(); LoadSetup(j); }); @@ -229,8 +235,11 @@ AppWindow::AppWindow(QWidget *parent) ConnectToDevice(); } if(!parser.isSet("no-gui")) { + InformationBox::setGUI(true); resize(1280, 800); show(); + } else { + InformationBox::setGUI(false); } } @@ -328,7 +337,7 @@ void AppWindow::DisconnectDevice() void AppWindow::DeviceConnectionLost() { DisconnectDevice(); - QMessageBox::warning(this, "Disconnected", "The USB connection to the device has been lost"); + InformationBox::ShowError("Disconnected", "The USB connection to the device has been lost"); UpdateDeviceList(); } @@ -856,12 +865,11 @@ void AppWindow::StartFirmwareUpdateDialog() void AppWindow::DeviceNeedsUpdate(int reported, int expected) { - auto ret = QMessageBox::warning(this, "Warning", + auto ret = InformationBox::AskQuestion("Warning", "The device reports a different protocol" "version (" + QString::number(reported) + ") than expected (" + QString::number(expected) + ").\n" - "A firmware update is strongly recommended. Do you want to update now?", - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - if (ret == QMessageBox::Yes) { + "A firmware update is strongly recommended. Do you want to update now?", false); + if (ret) { StartFirmwareUpdateDialog(); } } diff --git a/Software/PC_Application/preferences.cpp b/Software/PC_Application/preferences.cpp index c737855..738d10e 100644 --- a/Software/PC_Application/preferences.cpp +++ b/Software/PC_Application/preferences.cpp @@ -99,7 +99,7 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) : // Reset and OK action connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, [=](){ - if(QMessageBox::question(this, "Restore defaults?", "Do you really want to set all preferences to their default values?") == QMessageBox::StandardButton::Yes) { + if(InformationBox::AskQuestion("Restore defaults?", "Do you really want to set all preferences to their default values?", true)) { p->setDefault(); setInitialGUIState(); }