diff --git a/Documentation/UserManual/ProgrammingGuide.pdf b/Documentation/UserManual/ProgrammingGuide.pdf index 0063d713..a06fe98e 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 59be0371..b46d8770 100644 --- a/Documentation/UserManual/ProgrammingGuide.tex +++ b/Documentation/UserManual/ProgrammingGuide.tex @@ -153,7 +153,8 @@ \begin{document} \maketitle -\setcounter{tocdepth}{3} +\setcounter{tocdepth}{4} +\setcounter{secnumdepth}{4} \tableofcontents \clearpage @@ -257,14 +258,6 @@ \subsubsection{DEVice:CONNect} 206039903350 \end{example} -\subsubsection{DEVice:UPDATE} -\event{Updates the firmware of the connected device}{DEVice:UPDATE }{ Path to the firmware file} -Important points when saving/loading setup files through SCPI commands: -\begin{itemize} -\item The path must be either absolute or relative to the location of the GUI application. -\item If the LibreVNA-GUI (and thus also the SCPI server) is running on a different machine than the SCPI client, the firmware file must be stored on the machine that runs the GUI. -\end{itemize} - \subsubsection{DEVice:LIST} \query{Lists all available devices by their serial numbers}{DEVice:LIST?}{None}{List of serialnumbers} \begin{example} @@ -343,12 +336,6 @@ \subsubsection{DEVice:INFo:HWREVision} :DEV:INF:HWREV? B \end{example} -\subsubsection{DEVice:INFo:TEMPeratures} -\query{Queries the temperatures of certain chips}{DEVice:INFo:TEMPeratures?}{None}{/<1.LO>/} -\begin{example} -:DEV:INF:TEMP? -45/51/31 -\end{example} \subsubsection{DEVice:INFo:LIMits:MINFrequency} \query{Queries the lowest frequency the device can measure}{DEVice:INFo:LIMits:MINFrequency?}{None}{lowest frequency in Hz} @@ -646,6 +633,17 @@ \subsubsection{VNA:CALibration:SAVE} \subsubsection{VNA:CALibration:LOAD} \query{Loads a calibration file}{VNA:CALibration:LOAD?}{}{TRUE or FALSE} +\subsubsection{VNA:CALibration:KIT:SAVE} +\event{Saves the active calibration kit to a file}{VNA:CALibration:KIT:SAVE}{} +Important points when saving/loading calibration kit files through SCPI commands: +\begin{itemize} +\item Filenames must be either absolute or relative to the location of the GUI application. +\item If the LibreVNA-GUI (and thus also the SCPI server) is running on a different machine than the SCPI client, the calibration kit files will be saved/loaded from the machine that runs the GUI. +\end{itemize} + +\subsubsection{VNA:CALibration:KIT:LOAD} +\query{Loads a calibration kit file}{VNA:CALibration:KIT: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. @@ -861,6 +859,261 @@ \subsubsection{SA:TRACe:TYPE} \event{Sets the storage type of a trace}{SA:TRACe:TYPE}{, either by name or by index\\, options are OVERWRITE, MAXHOLD or MINHOLD} \query{Queries the storage type of a trace}{SA:TRACe:TYPE?}{, either by name or by index}{OVERWRITE, MAXHOLD or MINHOLD} +\section{Custom Driver Commands} +The \gui{} is mainly intended to be used with the \vna{}. However, the interface between the \gui{} and the actual VNA is abstracting certain hardware features to allow the \gui{} to interact with other devices as well. This is mainly intended for future extensions and only very few other devices are supported for testing and demonstration purposes. + +Each device driver may implement additional SCPI commands that are too specific to be useful with every device. These commands are only available when the \gui{} is connected to the device. + +\subsection{LibreVNA Version 1} +The following commands are available when connected to a LibreVNA (hardware version 1): + +\subsubsection{DEVice:UPDATE} +\event{Updates the firmware of the connected device}{DEVice:UPDATE }{ Path to the firmware file} +Important points when saving/loading setup files through SCPI commands: +\begin{itemize} +\item The path must be either absolute or relative to the location of the GUI application. +\item If the LibreVNA-GUI (and thus also the SCPI server) is running on a different machine than the SCPI client, the firmware file must be stored on the machine that runs the GUI. +\end{itemize} + +\subsubsection{DEVice:INFo:TEMPeratures} +\query{Queries the temperatures of certain chips}{DEVice:INFo:TEMPeratures?}{None}{/<1.LO>/} +\begin{example} +:DEV:INF:TEMP? +45/51/31 +\end{example} + +\subsubsection{MANual:STArt} +\event{Starts the manual device control dialog}{MANual:STArt}{None} + +The manual device control dialog allows the user to directly access all the hardware components. This can be considered as a forth mode (in addition to VNA, signal generator and spectrum analyzer). As long as it is active, it takes priority over any other mode. After starting the manual control dialog, all hardware components are disabled by default. While the manual control dialog is active, additional SCPI commands are available to configure the hardware and retrieve ADC readings. + +\subsubsection{MANual:STOp} +\event{Stops the manual device control dialog}{MANual:STOp}{None} + +\subsubsection{MANual:HSRC\_CE} +Only available when the manual control dialog is active. + +\event{Controls the chip enable for the highband source}{MANual:HSRC\_CE}{TRUE or FALSE} +\query{Queries the status of the chip enable for the highband source}{MANual:HSRC\_CE?}{None}{TRUE or FALSE} + +\subsubsection{MANual:HSRC\_RFEN} +Only available when the manual control dialog is active. + +\event{Controls the RF enable for the highband source}{MANual:HSRC\_RFEN}{TRUE or FALSE} +\query{Queries the status of the RF enable for the highband source}{MANual:HSRC\_RFEN?}{None}{TRUE or FALSE} + +\subsubsection{MANual:HSRC\_LOCKed} +Only available when the manual control dialog is active. + +\query{Queries the lock status of the highband source}{MANual:HSRC\_LOCKed?}{None}{TRUE or FALSE} + +\subsubsection{MANual:HSRC\_PWR} +Only available when the manual control dialog is active. + +\event{Sets the output power of the highband source}{MANual:HSRC\_PWR}{, output power in dBm, allowed values are -4, -1, 2 and 5} +\query{Queries the output power of the highband source}{MANual:HSRC\_PWR?}{None}{, output power in dBm} + +\subsubsection{MANual:HSRC\_FREQ} +Only available when the manual control dialog is active. + +\event{Sets the target frequency of the highband source}{MANual:HSRC\_FREQ}{, frequency in Hz} +\query{Queries the target frequency of the highband source}{MANual:HSRC\_FREQ?}{None}{, frequency in Hz} + +\subsubsection{MANual:HSRC\_LPF} +Only available when the manual control dialog is active. + +\event{Sets the lowpass filter of the highband source}{MANual:HSRC\_LPF}{, Cutoff frequency in MHz, allowed values are 947, 1880, 3500 and\\0 (no filter)} +\query{Queries the lowpass filter setting of the highband source}{MANual:HSRC\_LPF?}{None}{, Cutoff frequency in MHz} + +\subsubsection{MANual:LSRC\_EN} +Only available when the manual control dialog is active. + +\event{Controls the enable signal for the lowband source}{MANual:LSRC\_EN}{TRUE or FALSE} +\query{Queries the status of the enable signal for the lowband source}{MANual:LSRC\_EN?}{None}{TRUE or FALSE} + +\subsubsection{MANual:LSRC\_PWR} +Only available when the manual control dialog is active. + +\event{Sets the output power of the lowband source}{MANual:LSRC\_PWR}{, output power in mA, allowed values are 2, 4, 6 and 8} +\query{Queries the output power of the lowband source}{MANual:LSRC\_PWR?}{None}{, output power in mA} + +\subsubsection{MANual:LSRC\_FREQ} +Only available when the manual control dialog is active. + +\event{Sets the target frequency of the lowband source}{MANual:LSRC\_FREQ}{, frequency in Hz} +\query{Queries the target frequency of the lowband source}{MANual:LSRC\_FREQ?}{None}{, frequency in Hz} + +\subsubsection{MANual:BAND\_SW} +Only available when the manual control dialog is active. + +\event{Controls the band selector switch}{MANual:BAND\_SW}{TRUE for highband, FALSE for lowband} +\query{Queries the status of the band selector switch}{MANual:BAND\_SW?}{None}{TRUE for highband, FALSE for lowband} + +\subsubsection{MANual:ATTenuator} +Only available when the manual control dialog is active. + +\event{Sets the attenuator value}{MANual:ATTenuator}{, Attenuation in dB, values between -31.75 and 0} +\query{Queries the attenuator value}{MANual:ATTenuator?}{None}{, Attenuation in dB} + +\subsubsection{MANual:AMP\_EN} +Only available when the manual control dialog is active. + +\event{Controls the enable signal for the amplifier}{MANual:AMP\_EN}{TRUE or FALSE} +\query{Queries the status of the enable signal for the amplifier}{MANual:AMP\_EN?}{None}{TRUE or FALSE} + +\subsubsection{MANual:PORT\_SW} +Only available when the manual control dialog is active. + +\event{Controls the port selector switch}{MANual:PORT\_SW}{, either 1 or 2} +\query{Queries the status of the port selector switch}{MANual:PORT\_SW?}{None}{} + +\subsubsection{MANual:LO1\_CE} +Only available when the manual control dialog is active. + +\event{Controls the chip enable for the LO1 PLL}{MANual:LO1\_CE}{TRUE or FALSE} +\query{Queries the status of the chip enable for the LO1 PLL}{MANual:LO1\_CE?}{None}{TRUE or FALSE} + +\subsubsection{MANual:LO1\_RFEN} +Only available when the manual control dialog is active. + +\event{Controls the RF enable for the LO1 PLL}{MANual:LO1\_RFEN}{TRUE or FALSE} +\query{Queries the status of the RF enable for the LO1 PLL}{MANual:LO1\_RFEN?}{None}{TRUE or FALSE} + +\subsubsection{MANual:LO1\_LOCKed} +Only available when the manual control dialog is active. + +\query{Queries the lock status of the LO1 PLL}{MANual:LO1\_LOCKed?}{None}{TRUE or FALSE} + +\subsubsection{MANual:LO1\_FREQ} +Only available when the manual control dialog is active. + +\event{Sets the target frequency of the LO1 PLL}{MANual:LO1\_FREQ}{, frequency in Hz} +\query{Queries the target frequency of the LO1 PLL}{MANual:LO1\_FREQ?}{None}{, frequency in Hz} + +\subsubsection{MANual:IF1\_FREQ} +Only available when the manual control dialog is active. + +\event{Sets the IF1 frequency}{MANual:IF1\_FREQ}{, frequency in Hz} +\query{Queries the IF1 frequency}{MANual:IF1\_FREQ?}{None}{, frequency in Hz} + +\subsubsection{MANual:LO2\_EN} +Only available when the manual control dialog is active. + +\event{Controls the enable signal for the LO2 PLL}{MANual:LO2\_EN}{TRUE or FALSE} +\query{Queries the status of the enable signal for the LO2 PLL}{MANual:LO2\_EN?}{None}{TRUE or FALSE} + +\subsubsection{MANual:LO2\_FREQ} +Only available when the manual control dialog is active. + +\event{Sets the target frequency of the LO2 PLL}{MANual:LO2\_FREQ}{, frequency in Hz} +\query{Queries the target frequency of the LO2 PLL}{MANual:LO2\_FREQ?}{None}{, frequency in Hz} + +\subsubsection{MANual:IF2\_FREQ} +Only available when the manual control dialog is active. + +\event{Sets the IF2 frequency}{MANual:IF2\_FREQ}{, frequency in Hz} +\query{Queries the IF2 frequency}{MANual:IF2\_FREQ?}{None}{, frequency in Hz} + +\subsubsection{MANual:PORT1\_EN} +Only available when the manual control dialog is active. + +\event{Controls the enable signal for the port 1 receiver}{MANual:PORT1\_EN}{TRUE or FALSE} +\query{Queries the status of the enable signal for the port 1 receiver}{MANual:PORT1\_EN?}{None}{TRUE or FALSE} + +\subsubsection{MANual:PORT2\_EN} +Only available when the manual control dialog is active. + +\event{Controls the enable signal for the port 2 receiver}{MANual:PORT2\_EN}{TRUE or FALSE} +\query{Queries the status of the enable signal for the port 2 receiver}{MANual:PORT2\_EN?}{None}{TRUE or FALSE} + +\subsubsection{MANual:REF\_EN} +Only available when the manual control dialog is active. + +\event{Controls the enable signal for the reference receiver}{MANual:REF\_EN}{TRUE or FALSE} +\query{Queries the status of the enable signal for the reference receiver}{MANual:REF\_EN?}{None}{TRUE or FALSE} + +\subsubsection{MANual:SAMPLES} +Only available when the manual control dialog is active. + +\event{Sets the number of ADC samples per reading}{MANual:SAMPLES}{, value between 16 and 131072 in increments of 16} +\query{Queries the number of ADC samples per reading}{MANual:SAMPLES?}{None}{} + +\subsubsection{MANual:WINdow} +Only available when the manual control dialog is active. + +\event{Sets the window for the DFT calculation}{MANual:WINdow}{, either NONE, KAISER, HANN or FLATTOP} +\query{Queries the window for the DFT calculatio}{MANual:WINdow?}{None}{NONE, KAISER, HANN or FLATTOP} + +\subsubsection{MANual:PORT1\_MIN} +Only available when the manual control dialog is active. + +\query{Queries the minimum observed port 1 ADC value in the last sampling period}{MANual:PORT1\_MIN?}{None}{minimum ADC value} + +\subsubsection{MANual:PORT1\_MAX} +Only available when the manual control dialog is active. + +\query{Queries the maximum observed port 1 ADC value in the last sampling period}{MANual:PORT1\_MAX?}{None}{maximum ADC value} + +\subsubsection{MANual:PORT1\_MAG} +Only available when the manual control dialog is active. + +\query{Queries the observerd magnitude at port 1 in the last sampling period}{MANual:PORT1\_MAG?}{None}{port 1 magnitude (linear unit, arbitrary scale)} + +\subsubsection{MANual:PORT1\_PHAse} +Only available when the manual control dialog is active. + +\query{Queries the observed phase at port 1 in the last sampling period}{MANual:PORT1\_PHAse?}{None}{phase in degree (random value because it is relativ to an internal sampling clock)} + +\subsubsection{MANual:PORT1\_REFerenced} +Only available when the manual control dialog is active. + +\query{Queries the observed signal at port 1 relative to the reference in the last sampling period}{MANual:PORT1\_REFerenced?}{None}{, } + +\subsubsection{MANual:PORT2\_MIN} +Only available when the manual control dialog is active. + +\query{Queries the minimum observed port 2 ADC value in the last sampling period}{MANual:PORT2\_MIN?}{None}{minimum ADC value} + +\subsubsection{MANual:PORT2\_MAX} +Only available when the manual control dialog is active. + +\query{Queries the maximum observed port 2 ADC value in the last sampling period}{MANual:PORT2\_MAX?}{None}{maximum ADC value} + +\subsubsection{MANual:PORT2\_MAG} +Only available when the manual control dialog is active. + +\query{Queries the observerd magnitude at port 2 in the last sampling period}{MANual:PORT2\_MAG?}{None}{port 1 magnitude (linear unit, arbitrary scale)} + +\subsubsection{MANual:PORT2\_PHAse} +Only available when the manual control dialog is active. + +\query{Queries the observed phase at port 2 in the last sampling period}{MANual:PORT2\_PHAse?}{None}{phase in degree (random value because it is relativ to an internal sampling clock)} + +\subsubsection{MANual:PORT2\_REFerenced} +Only available when the manual control dialog is active. + +\query{Queries the observed signal at port 2 relative to the reference in the last sampling period}{MANual:PORT2\_REFerenced?}{None}{, } + +\subsubsection{MANual:REF\_MIN} +Only available when the manual control dialog is active. + +\query{Queries the minimum observed reference ADC value in the last sampling period}{MANual:REF\_MIN?}{None}{minimum ADC value} + +\subsubsection{MANual:REF\_MAX} +Only available when the manual control dialog is active. + +\query{Queries the maximum observed reference ADC value in the last sampling period}{MANual:REF\_MAX?}{None}{maximum ADC value} + +\subsubsection{MANual:REF\_MAG} +Only available when the manual control dialog is active. + +\query{Queries the observerd magnitude at the reference receiver in the last sampling period}{MANual:REF\_MAG?}{None}{port 1 magnitude (linear unit, arbitrary scale)} + +\subsubsection{MANual:REF\_PHAse} +Only available when the manual control dialog is active. + +\query{Queries the observed phase at the reference receiver in the last sampling period}{MANual:REF\_PHAse?}{None}{phase in degree (random value because it is relativ to an internal sampling clock)} + \section{Streaming data} The SCPI server works well for configuring the device and also for reading trace data once an acquition is done. But it isn't very well suited for reading data while the device is capturing it. For some applications (e.g. when running continuous sweeps) it may be beneficial to process the data externally as it getting captured. For this purpose, the LibreVNA-GUI supports streaming the data over dedicated ports. diff --git a/Software/Integrationtests/tests/TestTemperature.py b/Software/Integrationtests/tests/TestTemperature.py new file mode 100644 index 00000000..4a6013ee --- /dev/null +++ b/Software/Integrationtests/tests/TestTemperature.py @@ -0,0 +1,9 @@ +from tests.TestBase import TestBase + +class TestConnect(TestBase): + def test_temperature(self): + res = self.vna.query(":DEV:INF:TEMP?") + self.assertEqual(res.split("/"), 3) + + + diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnadriver.cpp b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnadriver.cpp index 9c49f412..8f545972 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnadriver.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnadriver.cpp @@ -116,25 +116,33 @@ LibreVNADriver::LibreVNADriver() hardwareVersion = 0; protocolVersion = 0; setSynchronization(Synchronization::Disabled, false); + manualControlDialog = nullptr; - auto manual = new QAction("Manual Control"); - connect(manual, &QAction::triggered, this, [=](){ - QDialog *d = nullptr; + // Add driver specific actions + + auto startManualControl = [=](){ + manualControlDialog = nullptr; switch(hardwareVersion) { case 1: - d = new ManualControlDialogV1(*this); + manualControlDialog = new ManualControlDialogV1(*this); break; case 0xFE: - d = new ManualControlDialogVFE(*this); + manualControlDialog = new ManualControlDialogVFE(*this); break; case 0xFF: - d = new ManualControlDialogVFF(*this); + manualControlDialog = new ManualControlDialogVFF(*this); break; } - if(d) { - d->show(); + if(manualControlDialog) { + manualControlDialog->show(); + connect(manualControlDialog, &QDialog::finished, this, [=](){ + manualControlDialog = nullptr; + }); } - }); + }; + + auto manual = new QAction("Manual Control"); + connect(manual, &QAction::triggered, this, startManualControl); specificActions.push_back(manual); auto config = new QAction("Configuration"); @@ -199,6 +207,50 @@ LibreVNADriver::LibreVNADriver() d->show(); }); specificActions.push_back(log); + + // Create driver specific commands + specificSCPIcommands.push_back(new SCPICommand("DEVice:INFo:TEMPeratures", nullptr, [=](QStringList) -> QString { + if(!connected) { + return SCPI::getResultName(SCPI::Result::Error); + } + return QString::number(lastStatus.V1.temp_source)+"/"+QString::number(lastStatus.V1.temp_LO1)+"/"+QString::number(lastStatus.V1.temp_MCU); + })); + + specificSCPIcommands.push_back(new SCPICommand("DEVice:UPDATE", [=](QStringList params) -> QString { + if(!connected) { + return SCPI::getResultName(SCPI::Result::Error); + } + if(params.size() != 1) { + // no file given + return SCPI::getResultName(SCPI::Result::Error); + } + auto ret = updateFirmware(params[0]); + if(!ret) { + // update failed + return SCPI::getResultName(SCPI::Result::Error); + } else { + // update succeeded + return SCPI::getResultName(SCPI::Result::Empty); + } + }, nullptr, false)); + + specificSCPIcommands.push_back(new SCPICommand("MANual:STArt", [=](QStringList) -> QString { + if(!manualControlDialog) { + startManualControl(); + if(!manualControlDialog) { + return SCPI::getResultName(SCPI::Result::Error); + } + } + return SCPI::getResultName(SCPI::Result::Empty); + }, nullptr)); + + specificSCPIcommands.push_back(new SCPICommand("MANual:STOp", [=](QStringList) -> QString { + if(manualControlDialog) { + delete manualControlDialog; + manualControlDialog = nullptr; + } + return SCPI::getResultName(SCPI::Result::Empty); + }, nullptr)); } std::set LibreVNADriver::getFlags() diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnadriver.h b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnadriver.h index 3599d5a0..ff9d9cd1 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnadriver.h +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnadriver.h @@ -222,6 +222,8 @@ protected slots: double SARBWLimitForDFT; bool VNASuppressInvalidPeaks; bool VNAAdjustPowerLevel; + + QDialog *manualControlDialog; }; Q_DECLARE_METATYPE(Protocol::PacketInfo) diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.cpp b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.cpp index b4632b8d..98d782d4 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.cpp @@ -186,11 +186,204 @@ ManualControlDialogV1::ManualControlDialogV1(LibreVNADriver &dev, QWidget *paren connect(ui->Samples, qOverload(&QSpinBox::valueChanged), [=](double) { UpdateDevice(); }); connect(ui->cbWindow, qOverload(&QComboBox::activated), [=](int) { UpdateDevice(); }); + // Create the SCPI commands + + auto addBooleanManualSetting = [=](QString cmd, void(ManualControlDialogV1::*set)(bool), bool(ManualControlDialogV1::*get)(void)) { + commands.push_back(new SCPICommand(cmd, [=](QStringList params) -> QString { + bool enable; + if(!SCPI::paramToBool(params, 0, enable)) { + return SCPI::getResultName(SCPI::Result::Error); + } + auto set_fn = std::bind(set, this, std::placeholders::_1); + set_fn(enable); + return SCPI::getResultName(SCPI::Result::Empty); + }, [=](QStringList) -> QString { + auto get_fn = std::bind(get, this); + return get_fn() ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); + })); + }; + + auto addDoubleManualSetting = [=](QString cmd, void(ManualControlDialogV1::*set)(double), double(ManualControlDialogV1::*get)(void)) { + commands.push_back(new SCPICommand(cmd, [=](QStringList params) -> QString { + double value; + if(!SCPI::paramToDouble(params, 0, value)) { + return SCPI::getResultName(SCPI::Result::Error); + } + auto set_fn = std::bind(set, this, std::placeholders::_1); + set_fn(value); + return SCPI::getResultName(SCPI::Result::Empty); + }, [=](QStringList) -> QString { + auto get_fn = std::bind(get, this); + return QString::number(get_fn()); + })); + }; + auto addIntegerManualSetting = [=](QString cmd, void(ManualControlDialogV1::*set)(int), int(ManualControlDialogV1::*get)(void)) { + commands.push_back(new SCPICommand(cmd, [=](QStringList params) -> QString { + double value; + if(!SCPI::paramToDouble(params, 0, value)) { + return SCPI::getResultName(SCPI::Result::Error); + } + auto set_fn = std::bind(set, this, std::placeholders::_1); + set_fn(value); + return SCPI::getResultName(SCPI::Result::Empty); + }, [=](QStringList) -> QString { + auto get_fn = std::bind(get, this); + return QString::number(get_fn()); + })); + }; + auto addIntegerManualSettingWithReturnValue = [=](QString cmd, bool(ManualControlDialogV1::*set)(int), int(ManualControlDialogV1::*get)(void)) { + commands.push_back(new SCPICommand(cmd, [=](QStringList params) -> QString { + double value; + if(!SCPI::paramToDouble(params, 0, value)) { + return SCPI::getResultName(SCPI::Result::Error); + } + auto set_fn = std::bind(set, this, std::placeholders::_1); + if(set_fn(value)) { + return SCPI::getResultName(SCPI::Result::Empty); + } else { + return SCPI::getResultName(SCPI::Result::Error); + } + }, [=](QStringList) -> QString { + auto get_fn = std::bind(get, this); + return QString::number(get_fn()); + })); + }; + auto addIntegerManualQuery = [=](QString cmd, int(ManualControlDialogV1::*get)(void)) { + commands.push_back(new SCPICommand(cmd, nullptr, [=](QStringList) -> QString { + auto get_fn = std::bind(get, this); + return QString::number(get_fn()); + })); + }; + auto addDoubleManualQuery = [=](QString cmd, double(ManualControlDialogV1::*get)(void)) { + commands.push_back(new SCPICommand(cmd, nullptr, [=](QStringList) -> QString { + auto get_fn = std::bind(get, this); + return QString::number(get_fn()); + })); + }; + auto addBooleanManualQuery = [=](QString cmd, bool(ManualControlDialogV1::*get)(void)) { + commands.push_back(new SCPICommand(cmd, nullptr, [=](QStringList) -> QString { + auto get_fn = std::bind(get, this); + return get_fn() ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); + })); + }; + auto addComplexManualQuery = [=](QString cmd, std::complex(ManualControlDialogV1::*get)(void)) { + commands.push_back(new SCPICommand(cmd, nullptr, [=](QStringList) -> QString { + auto get_fn = std::bind(get, this); + auto res = get_fn(); + return QString::number(res.real())+","+QString::number(res.imag()); + })); + }; + + addBooleanManualSetting("MANual:HSRC_CE", &ManualControlDialogV1::setHighSourceChipEnable, &ManualControlDialogV1::getHighSourceChipEnable); + addBooleanManualSetting("MANual:HSRC_RFEN", &ManualControlDialogV1::setHighSourceRFEnable, &ManualControlDialogV1::getHighSourceRFEnable); + addBooleanManualQuery("MANual:HSRC_LOCKed", &ManualControlDialogV1::getHighSourceLocked); + addIntegerManualSettingWithReturnValue("MANual:HSRC_PWR", &ManualControlDialogV1::setHighSourcePower, &ManualControlDialogV1::getHighSourcePower); + addDoubleManualSetting("MANual:HSRC_FREQ", &ManualControlDialogV1::setHighSourceFrequency, &ManualControlDialogV1::getHighSourceFrequency); + commands.push_back(new SCPICommand("MANual:HSRC_LPF", [=](QStringList params) -> QString { + long value; + if(!SCPI::paramToLong(params, 0, value)) { + return SCPI::getResultName(SCPI::Result::Error); + } + switch(value) { + case 947: + setHighSourceLPF(ManualControlDialogV1::LPF::M947); + break; + case 1880: + setHighSourceLPF(ManualControlDialogV1::LPF::M1880); + break; + case 3500: + setHighSourceLPF(ManualControlDialogV1::LPF::M3500); + break; + case 0: + setHighSourceLPF(ManualControlDialogV1::LPF::None); + break; + default: + return SCPI::getResultName(SCPI::Result::Error); + } + return SCPI::getResultName(SCPI::Result::Empty); + }, [=](QStringList) -> QString { + auto lpf = getHighSourceLPF(); + switch(lpf) { + case ManualControlDialogV1::LPF::M947: return "947"; + case ManualControlDialogV1::LPF::M1880: return "1880"; + case ManualControlDialogV1::LPF::M3500: return "3500"; + case ManualControlDialogV1::LPF::None: return "0"; + default: return SCPI::getResultName(SCPI::Result::Error); + } + })); + addBooleanManualSetting("MANual:LSRC_EN", &ManualControlDialogV1::setLowSourceEnable, &ManualControlDialogV1::getLowSourceEnable); + addIntegerManualSettingWithReturnValue("MANual:LSRC_PWR", &ManualControlDialogV1::setLowSourcePower, &ManualControlDialogV1::getLowSourcePower); + addDoubleManualSetting("MANual:LSRC_FREQ", &ManualControlDialogV1::setLowSourceFrequency, &ManualControlDialogV1::getLowSourceFrequency); + addBooleanManualSetting("MANual:BAND_SW", &ManualControlDialogV1::setHighband, &ManualControlDialogV1::getHighband); + addDoubleManualSetting("MANual:ATTenuator", &ManualControlDialogV1::setAttenuator, &ManualControlDialogV1::getAttenuator); + addBooleanManualSetting("MANual:AMP_EN", &ManualControlDialogV1::setAmplifierEnable, &ManualControlDialogV1::getAmplifierEnable); + addIntegerManualSettingWithReturnValue("MANual:PORT_SW", &ManualControlDialogV1::setPortSwitch, &ManualControlDialogV1::getPortSwitch); + addBooleanManualSetting("MANual:LO1_CE", &ManualControlDialogV1::setLO1ChipEnable, &ManualControlDialogV1::getLO1ChipEnable); + addBooleanManualSetting("MANual:LO1_RFEN", &ManualControlDialogV1::setLO1RFEnable, &ManualControlDialogV1::getLO1RFEnable); + addBooleanManualQuery("MANual:LO1_LOCKed", &ManualControlDialogV1::getLO1Locked); + addDoubleManualSetting("MANual:LO1_FREQ", &ManualControlDialogV1::setLO1Frequency, &ManualControlDialogV1::getLO1Frequency); + addDoubleManualSetting("MANual:IF1_FREQ", &ManualControlDialogV1::setIF1Frequency, &ManualControlDialogV1::getIF1Frequency); + addBooleanManualSetting("MANual:LO2_EN", &ManualControlDialogV1::setLO2Enable, &ManualControlDialogV1::getLO2Enable); + addDoubleManualSetting("MANual:LO2_FREQ", &ManualControlDialogV1::setLO2Frequency, &ManualControlDialogV1::getLO2Frequency); + addDoubleManualSetting("MANual:IF2_FREQ", &ManualControlDialogV1::setIF2Frequency, &ManualControlDialogV1::getIF2Frequency); + addBooleanManualSetting("MANual:PORT1_EN", &ManualControlDialogV1::setPort1Enable, &ManualControlDialogV1::getPort1Enable); + addBooleanManualSetting("MANual:PORT2_EN", &ManualControlDialogV1::setPort2Enable, &ManualControlDialogV1::getPort2Enable); + addBooleanManualSetting("MANual:REF_EN", &ManualControlDialogV1::setRefEnable, &ManualControlDialogV1::getRefEnable); + addIntegerManualSetting("MANual:SAMPLES", &ManualControlDialogV1::setNumSamples, &ManualControlDialogV1::getNumSamples); + commands.push_back(new SCPICommand("MANual:WINdow", [=](QStringList params) -> QString { + if(params.size() < 1) { + return SCPI::getResultName(SCPI::Result::Error); + } + if (params[0] == "NONE") { + setWindow(ManualControlDialogV1::Window::None); + } else if(params[0] == "KAISER") { + setWindow(ManualControlDialogV1::Window::Kaiser); + } else if(params[0] == "HANN") { + setWindow(ManualControlDialogV1::Window::Hann); + } else if(params[0] == "FLATTOP") { + setWindow(ManualControlDialogV1::Window::FlatTop); + } else { + return "INVALID WINDOW"; + } + return SCPI::getResultName(SCPI::Result::Empty); + }, [=](QStringList) -> QString { + switch((ManualControlDialogV1::Window) getWindow()) { + case ManualControlDialogV1::Window::None: return "NONE"; + case ManualControlDialogV1::Window::Kaiser: return "KAISER"; + case ManualControlDialogV1::Window::Hann: return "HANN"; + case ManualControlDialogV1::Window::FlatTop: return "FLATTOP"; + default: return SCPI::getResultName(SCPI::Result::Error); + } + })); + addIntegerManualQuery("MANual:PORT1_MIN", &ManualControlDialogV1::getPort1MinADC); + addIntegerManualQuery("MANual:PORT1_MAX", &ManualControlDialogV1::getPort1MaxADC); + addDoubleManualQuery("MANual:PORT1_MAG", &ManualControlDialogV1::getPort1Magnitude); + addDoubleManualQuery("MANual:PORT1_PHAse", &ManualControlDialogV1::getPort1Phase); + addComplexManualQuery("MANual:PORT1_REFerenced", &ManualControlDialogV1::getPort1Referenced); + + addIntegerManualQuery("MANual:PORT2_MIN", &ManualControlDialogV1::getPort2MinADC); + addIntegerManualQuery("MANual:PORT2_MAX", &ManualControlDialogV1::getPort2MaxADC); + addDoubleManualQuery("MANual:PORT2_MAG", &ManualControlDialogV1::getPort2Magnitude); + addDoubleManualQuery("MANual:PORT2_PHAse", &ManualControlDialogV1::getPort2Phase); + addComplexManualQuery("MANual:PORT2_REFerenced", &ManualControlDialogV1::getPort2Referenced); + + addIntegerManualQuery("MANual:REF_MIN", &ManualControlDialogV1::getRefMinADC); + addIntegerManualQuery("MANual:REF_MAX", &ManualControlDialogV1::getRefMaxADC); + addDoubleManualQuery("MANual:REF_MAG", &ManualControlDialogV1::getRefMagnitude); + addDoubleManualQuery("MANual:REF_PHAse", &ManualControlDialogV1::getRefPhase); + + for(auto c : commands) { + emit dev.addSCPICommand(c); + } + UpdateDevice(); } ManualControlDialogV1::~ManualControlDialogV1() { + for(auto c : commands) { + emit dev.removeSCPICommand(c); + } emit dev.releaseControl(); delete ui; } diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.h b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.h index 2b9c6df4..42658dbb 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.h +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.h @@ -111,6 +111,8 @@ public slots: LibreVNADriver &dev; std::complex port1referenced; std::complex port2referenced; + + std::vector commands; }; #endif // MANUALCONTROLDIALOGV1_H diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.ui b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.ui index b10d7d9b..2a25745f 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.ui +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/manualcontroldialogV1.ui @@ -3,7 +3,7 @@ ManualControlDialogV1 - Qt::ApplicationModal + Qt::WindowModality::ApplicationModal @@ -520,16 +520,16 @@ - 128 + 16 - 130944 + 131072 - 128 + 16 - 130944 + 131072 diff --git a/Software/PC_Application/LibreVNA-GUI/Device/devicedriver.h b/Software/PC_Application/LibreVNA-GUI/Device/devicedriver.h index 3581694a..0a15586b 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/devicedriver.h +++ b/Software/PC_Application/LibreVNA-GUI/Device/devicedriver.h @@ -13,6 +13,7 @@ #include "Tools/parameters.h" #include "savable.h" +#include "scpi.h" #include #include @@ -231,6 +232,24 @@ class DeviceDriver : public QObject */ std::vector driverSpecificActions() {return specificActions;} + /** + * @brief Return driver specific SCPI commands + * + * The returned commands will be added to the :DEV SCPI node + * + * @return List of SCPI commands + */ + std::vector driverSpecificSCPICommands() {return specificSCPIcommands;} + + /** + * @brief Return driver specific SCPI nodes + * + * The returned nodes (which may contain further nodes/commands) will be added to the :DEV SCPI node + * + * @return List of SCPI nodes + */ + std::vector driverSpecificSCPINodes() {return specificSCPInodes;} + class VNASettings { public: // Start/stop frequency. Both values will be identical for power sweeps and zero span @@ -486,6 +505,41 @@ class DeviceDriver : public QObject */ void releaseControl(); + /** + * @brief Emit this to temporarily add a new SCPI command to the root node. + * + * Before deleting the command, removeSCPICommand must be emitted. + * When the device is disconnected, all added commands will be automatically removed. + * + * @param cmd Command to add + */ + void addSCPICommand(SCPICommand *cmd); + + /** + * @brief Emit this to remove a temporarily added SCPI command. + * + * @param cmd Command to remove + */ + void removeSCPICommand(SCPICommand *cmd); + + /** + * @brief Emit this to temporarily add a new SCPI node to the root node. + * + * Before deleting the node, removeSCPINode must be emitted. + * When the device is disconnected, all added nodes will be automatically removed. + * + * @param node Node to add + */ + void addSCPINode(SCPINode *node); + + /** + * @brief Emit this to remove a temporarily added SCPI node. + * + * @param node Node to remove + */ + void removeSCPINode(SCPINode *node); + + public: bool connectDevice(QString serial, bool isIndepedentDriver = false); void disconnectDevice(); @@ -494,9 +548,29 @@ class DeviceDriver : public QObject static unsigned int SApoints(); protected: + // Each driver implementation may add specific actionsm, settings or commands. All of these must + // be created in the constructor and added to the following vectors: + + // A list of actions specific to the driver. They will show up in the device menu std::vector specificActions; + + // A list of settings specific to the driver. They will be stored/recalled as part of the preferences. + // If a setting should be user-changeable in the preferences, createSettingsWidget() must include some + // widget to modify that setting std::vector specificSettings; + // A list of SCPI commands. They will be available at the root node whenever the device driver is in use. + // Avoid name collisions with commands/nodes already implemented in appwindow.cpp. + // Use this for commands that will be available whenever the device is connected. For commands that are + // not always available, use the addSCPICommand and removeSCPICommand signals. + std::vector specificSCPIcommands; + + // A list of SCPI nodes. They will be available at the root node whenever the device driver is in use. + // Avoid name collisions with commands/nodes already implemented in appwindow.cpp + // Use this for nodes that will be available whenever the device is connected. For nodes that are + // not always available, use the addSCPINode and removeSCPINode signals. + std::vector specificSCPInodes; + private: static DeviceDriver *activeDriver; }; diff --git a/Software/PC_Application/LibreVNA-GUI/appwindow.cpp b/Software/PC_Application/LibreVNA-GUI/appwindow.cpp index b28a07ca..b02e7495 100644 --- a/Software/PC_Application/LibreVNA-GUI/appwindow.cpp +++ b/Software/PC_Application/LibreVNA-GUI/appwindow.cpp @@ -354,6 +354,28 @@ bool AppWindow::ConnectToDevice(QString serial, DeviceDriver *driver) lastActiveMode = modeHandler->getActiveMode(); modeHandler->deactivate(lastActiveMode); }); + connect(d, &DeviceDriver::addSCPICommand, this, [=](SCPICommand *cmd){ + temporaryDeviceCommands.push_back(cmd); + scpi.add(cmd); + }); + connect(d, &DeviceDriver::removeSCPICommand, this, [=](SCPICommand *cmd){ + auto it = std::find(temporaryDeviceCommands.begin(), temporaryDeviceCommands.end(), cmd); + if(it != temporaryDeviceCommands.end()) { + temporaryDeviceCommands.erase(it); + } + scpi.remove(cmd); + }); + connect(d, &DeviceDriver::addSCPINode, this, [=](SCPINode *node){ + temporaryDeviceNodes.push_back(node); + scpi.add(node); + }); + connect(d, &DeviceDriver::removeSCPINode, this, [=](SCPINode *node){ + auto it = std::find(temporaryDeviceNodes.begin(), temporaryDeviceNodes.end(), node); + if(it != temporaryDeviceNodes.end()) { + temporaryDeviceNodes.erase(it); + } + scpi.remove(node); + }); if(d->connectDevice(serial)) { device = d; @@ -386,15 +408,16 @@ bool AppWindow::ConnectToDevice(QString serial, DeviceDriver *driver) for(auto a : device->driverSpecificActions()) { ui->menuDevice->insertAction(before, a); } -// if(!vdevice->isCompoundDevice()) { -// ui->actionManual_Control->setEnabled(true); -// ui->actionFirmware_Update->setEnabled(true); -// ui->actionSource_Calibration->setEnabled(true); -// ui->actionReceiver_Calibration->setEnabled(true); -// ui->actionFrequency_Calibration->setEnabled(true); -// } ui->actionPreset->setEnabled(true); + // Add SCPI nodes/commands + for(auto n : device->driverSpecificSCPINodes()) { + scpi.add(n); + } + for(auto c : device->driverSpecificSCPICommands()) { + scpi.add(c); + } + DeviceEntry e; e.serial = device->getSerial(); e.driver = device; @@ -407,12 +430,6 @@ bool AppWindow::ConnectToDevice(QString serial, DeviceDriver *driver) } } -// vdevice->initialize(); - -// UpdateAcquisitionFrequencies(); -// if (modeHandler->getActiveMode()) { -// modeHandler->getActiveMode()->initializeDevice(); -// } return true; } catch (const runtime_error &e) { qWarning() << "Failed to connect:" << e.what(); @@ -425,9 +442,28 @@ bool AppWindow::ConnectToDevice(QString serial, DeviceDriver *driver) void AppWindow::DisconnectDevice() { if(device) { + // remove menu entries for(auto a : device->driverSpecificActions()) { ui->menuDevice->removeAction(a); } + // remove SCPI nodes/commands + for(auto n : device->driverSpecificSCPINodes()) { + scpi.remove(n); + } + for(auto c : device->driverSpecificSCPICommands()) { + scpi.remove(c); + } + + // Remove all temporary SCPI nodes/commands + for(auto n : temporaryDeviceNodes) { + scpi.remove(n); + } + temporaryDeviceNodes.clear(); + for(auto c : temporaryDeviceCommands) { + scpi.remove(c); + } + temporaryDeviceCommands.clear(); + device->disconnectDevice(); disconnect(device, nullptr, &deviceLog, nullptr); disconnect(device, nullptr, this, nullptr); @@ -515,26 +551,6 @@ void AppWindow::SetupSCPI() return "Not connected"; } })); - scpi_dev->add(new SCPICommand("UPDATE", [=](QStringList params) -> QString { - if(params.size() != 1) { - // no file given - return SCPI::getResultName(SCPI::Result::Error); - } - if(!device) { - // not connected to any device - return SCPI::getResultName(SCPI::Result::Error); - } - scpi.setOperationPending(true); - auto ret = device->updateFirmware(params[0]); - scpi.setOperationPending(false); - if(!ret) { - // update failed - return SCPI::getResultName(SCPI::Result::Error); - } else { - // update succeeded - return SCPI::getResultName(SCPI::Result::Empty); - } - }, nullptr, false)); scpi_dev->add(new SCPICommand("LIST", nullptr, [=](QStringList) -> QString { QString ret; UpdateDeviceList(); @@ -728,32 +744,6 @@ void AppWindow::SetupSCPI() return SCPI::getResultName(SCPI::Result::Error); } })); -// scpi_info->add(new SCPICommand("TEMPeratures", nullptr, [=](QStringList){ -// if(!vdevice) { -// return QString("0/0/0"); -// } else if(vdevice->isCompoundDevice()) { -// // show highest temperature of all devices -// int maxTempSource = 0; -// int maxTempLO = 0; -// int maxTempMCU = 0; -// for(auto dev : vdevice->getDevices()) { -// auto status = dev->StatusV1(); -// if(status.temp_source > maxTempSource) { -// maxTempSource = status.temp_source; -// } -// if(status.temp_LO1 > maxTempLO) { -// maxTempLO = status.temp_LO1; -// } -// if(status.temp_MCU > maxTempMCU) { -// maxTempMCU = status.temp_MCU; -// } -// } -// return QString::number(maxTempSource)+"/"+QString::number(maxTempLO)+"/"+QString::number(maxTempMCU); -// } else { -// auto dev = vdevice->getDevice(); -// return QString::number(dev->StatusV1().temp_source)+"/"+QString::number(dev->StatusV1().temp_LO1)+"/"+QString::number(dev->StatusV1().temp_MCU); -// } -// })); auto scpi_limits = new SCPINode("LIMits"); scpi_info->add(scpi_limits); scpi_limits->add(new SCPICommand("MINFrequency", nullptr, [=](QStringList){ @@ -786,234 +776,6 @@ void AppWindow::SetupSCPI() scpi_limits->add(new SCPICommand("MAXHARMonicfrequency", nullptr, [=](QStringList){ return QString::number(DeviceDriver::getInfo(getDevice()).Limits.VNA.maxFreq); })); - - // TODO -// auto scpi_manual = new SCPINode("MANual"); -// scpi_manual->add(new SCPICommand("STArt",[=](QStringList) -> QString { -//// StartManualControl(); -// return SCPI::getResultName(SCPI::Result::Empty); -// }, nullptr)); -// scpi_manual->add(new SCPICommand("STOp",[=](QStringList) -> QString { -// manual->close(); -// delete manual; -// return SCPI::getResultName(SCPI::Result::Empty); -// }, nullptr)); -// -// auto addBooleanManualSetting = [=](QString cmd, void(ManualControlDialog::*set)(bool), bool(ManualControlDialog::*get)(void)) { -// scpi_manual->add(new SCPICommand(cmd, [=](QStringList params) -> QString { -// bool enable; -// if(!manual || !SCPI::paramToBool(params, 0, enable)) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto set_fn = std::bind(set, manual, std::placeholders::_1); -// set_fn(enable); -// return SCPI::getResultName(SCPI::Result::Empty); -// }, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto get_fn = std::bind(get, manual); -// return get_fn() ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); -// })); -// }; - -// auto addDoubleManualSetting = [=](QString cmd, void(ManualControlDialog::*set)(double), double(ManualControlDialog::*get)(void)) { -// scpi_manual->add(new SCPICommand(cmd, [=](QStringList params) -> QString { -// double value; -// if(!manual || !SCPI::paramToDouble(params, 0, value)) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto set_fn = std::bind(set, manual, std::placeholders::_1); -// set_fn(value); -// return SCPI::getResultName(SCPI::Result::Empty); -// }, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto get_fn = std::bind(get, manual); -// return QString::number(get_fn()); -// })); -// }; -// auto addIntegerManualSetting = [=](QString cmd, void(ManualControlDialog::*set)(int), int(ManualControlDialog::*get)(void)) { -// scpi_manual->add(new SCPICommand(cmd, [=](QStringList params) -> QString { -// double value; -// if(!manual || !SCPI::paramToDouble(params, 0, value)) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto set_fn = std::bind(set, manual, std::placeholders::_1); -// set_fn(value); -// return SCPI::getResultName(SCPI::Result::Empty); -// }, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto get_fn = std::bind(get, manual); -// return QString::number(get_fn()); -// })); -// }; -// auto addIntegerManualSettingWithReturnValue = [=](QString cmd, bool(ManualControlDialog::*set)(int), int(ManualControlDialog::*get)(void)) { -// scpi_manual->add(new SCPICommand(cmd, [=](QStringList params) -> QString { -// double value; -// if(!manual || !SCPI::paramToDouble(params, 0, value)) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto set_fn = std::bind(set, manual, std::placeholders::_1); -// if(set_fn(value)) { -// return SCPI::getResultName(SCPI::Result::Empty); -// } else { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// }, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto get_fn = std::bind(get, manual); -// return QString::number(get_fn()); -// })); -// }; -// auto addIntegerManualQuery = [=](QString cmd, int(ManualControlDialog::*get)(void)) { -// scpi_manual->add(new SCPICommand(cmd, nullptr, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto get_fn = std::bind(get, manual); -// return QString::number(get_fn()); -// })); -// }; -// auto addDoubleManualQuery = [=](QString cmd, double(ManualControlDialog::*get)(void)) { -// scpi_manual->add(new SCPICommand(cmd, nullptr, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto get_fn = std::bind(get, manual); -// return QString::number(get_fn()); -// })); -// }; -// auto addBooleanManualQuery = [=](QString cmd, bool(ManualControlDialog::*get)(void)) { -// scpi_manual->add(new SCPICommand(cmd, nullptr, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto get_fn = std::bind(get, manual); -// return get_fn() ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); -// })); -// }; -// auto addComplexManualQuery = [=](QString cmd, std::complex(ManualControlDialog::*get)(void)) { -// scpi_manual->add(new SCPICommand(cmd, nullptr, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto get_fn = std::bind(get, manual); -// auto res = get_fn(); -// return QString::number(res.real())+","+QString::number(res.imag()); -// })); -// }; - -// addBooleanManualSetting("HSRC_CE", &ManualControlDialog::setHighSourceChipEnable, &ManualControlDialog::getHighSourceChipEnable); -// addBooleanManualSetting("HSRC_RFEN", &ManualControlDialog::setHighSourceRFEnable, &ManualControlDialog::getHighSourceRFEnable); -// addBooleanManualQuery("HSRC_LOCKed", &ManualControlDialog::getHighSourceLocked); -// addIntegerManualSettingWithReturnValue("HSRC_PWR", &ManualControlDialog::setHighSourcePower, &ManualControlDialog::getHighSourcePower); -// addDoubleManualSetting("HSRC_FREQ", &ManualControlDialog::setHighSourceFrequency, &ManualControlDialog::getHighSourceFrequency); -// scpi_manual->add(new SCPICommand("HSRC_LPF", [=](QStringList params) -> QString { -// long value; -// if(!manual || !SCPI::paramToLong(params, 0, value)) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// switch(value) { -// case 947: -// manual->setHighSourceLPF(ManualControlDialog::LPF::M947); -// break; -// case 1880: -// manual->setHighSourceLPF(ManualControlDialog::LPF::M1880); -// break; -// case 3500: -// manual->setHighSourceLPF(ManualControlDialog::LPF::M3500); -// break; -// case 0: -// manual->setHighSourceLPF(ManualControlDialog::LPF::None); -// break; -// default: -// return SCPI::getResultName(SCPI::Result::Error); -// } -// return SCPI::getResultName(SCPI::Result::Empty); -// }, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// auto lpf = manual->getHighSourceLPF(); -// switch(lpf) { -// case ManualControlDialog::LPF::M947: return "947"; -// case ManualControlDialog::LPF::M1880: return "1880"; -// case ManualControlDialog::LPF::M3500: return "3500"; -// case ManualControlDialog::LPF::None: return "0"; -// default: return SCPI::getResultName(SCPI::Result::Error); -// } -// })); -// addBooleanManualSetting("LSRC_EN", &ManualControlDialog::setLowSourceEnable, &ManualControlDialog::getLowSourceEnable); -// addIntegerManualSettingWithReturnValue("LSRC_PWR", &ManualControlDialog::setLowSourcePower, &ManualControlDialog::getLowSourcePower); -// addDoubleManualSetting("LSRC_FREQ", &ManualControlDialog::setLowSourceFrequency, &ManualControlDialog::getLowSourceFrequency); -// addBooleanManualSetting("BAND_SW", &ManualControlDialog::setHighband, &ManualControlDialog::getHighband); -// addDoubleManualSetting("ATTenuator", &ManualControlDialog::setAttenuator, &ManualControlDialog::getAttenuator); -// addBooleanManualSetting("AMP_EN", &ManualControlDialog::setAmplifierEnable, &ManualControlDialog::getAmplifierEnable); -// addIntegerManualSettingWithReturnValue("PORT_SW", &ManualControlDialog::setPortSwitch, &ManualControlDialog::getPortSwitch); -// addBooleanManualSetting("LO1_CE", &ManualControlDialog::setLO1ChipEnable, &ManualControlDialog::getLO1ChipEnable); -// addBooleanManualSetting("LO1_RFEN", &ManualControlDialog::setLO1RFEnable, &ManualControlDialog::getLO1RFEnable); -// addBooleanManualQuery("LO1_LOCKed", &ManualControlDialog::getLO1Locked); -// addDoubleManualSetting("LO1_FREQ", &ManualControlDialog::setLO1Frequency, &ManualControlDialog::getLO1Frequency); -// addDoubleManualSetting("IF1_FREQ", &ManualControlDialog::setIF1Frequency, &ManualControlDialog::getIF1Frequency); -// addBooleanManualSetting("LO2_EN", &ManualControlDialog::setLO2Enable, &ManualControlDialog::getLO2Enable); -// addDoubleManualSetting("LO2_FREQ", &ManualControlDialog::setLO2Frequency, &ManualControlDialog::getLO2Frequency); -// addDoubleManualSetting("IF2_FREQ", &ManualControlDialog::setIF2Frequency, &ManualControlDialog::getIF2Frequency); -// addBooleanManualSetting("PORT1_EN", &ManualControlDialog::setPort1Enable, &ManualControlDialog::getPort1Enable); -// addBooleanManualSetting("PORT2_EN", &ManualControlDialog::setPort2Enable, &ManualControlDialog::getPort2Enable); -// addBooleanManualSetting("REF_EN", &ManualControlDialog::setRefEnable, &ManualControlDialog::getRefEnable); -// addIntegerManualSetting("SAMPLES", &ManualControlDialog::setNumSamples, &ManualControlDialog::getNumSamples); -// scpi_manual->add(new SCPICommand("WINdow", [=](QStringList params) -> QString { -// if(!manual || params.size() < 1) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// if (params[0] == "NONE") { -// manual->setWindow(ManualControlDialog::Window::None); -// } else if(params[0] == "KAISER") { -// manual->setWindow(ManualControlDialog::Window::Kaiser); -// } else if(params[0] == "HANN") { -// manual->setWindow(ManualControlDialog::Window::Hann); -// } else if(params[0] == "FLATTOP") { -// manual->setWindow(ManualControlDialog::Window::FlatTop); -// } else { -// return "INVALID WINDOW"; -// } -// return SCPI::getResultName(SCPI::Result::Empty); -// }, [=](QStringList) -> QString { -// if(!manual) { -// return SCPI::getResultName(SCPI::Result::Error); -// } -// switch((ManualControlDialog::Window) manual->getWindow()) { -// case ManualControlDialog::Window::None: return "NONE"; -// case ManualControlDialog::Window::Kaiser: return "KAISER"; -// case ManualControlDialog::Window::Hann: return "HANN"; -// case ManualControlDialog::Window::FlatTop: return "FLATTOP"; -// default: return SCPI::getResultName(SCPI::Result::Error); -// } -// })); -// addIntegerManualQuery("PORT1_MIN", &ManualControlDialog::getPort1MinADC); -// addIntegerManualQuery("PORT1_MAX", &ManualControlDialog::getPort1MaxADC); -// addDoubleManualQuery("PORT1_MAG", &ManualControlDialog::getPort1Magnitude); -// addDoubleManualQuery("PORT1_PHAse", &ManualControlDialog::getPort1Phase); -// addComplexManualQuery("PORT1_REFerenced", &ManualControlDialog::getPort1Referenced); - -// addIntegerManualQuery("PORT2_MIN", &ManualControlDialog::getPort2MinADC); -// addIntegerManualQuery("PORT2_MAX", &ManualControlDialog::getPort2MaxADC); -// addDoubleManualQuery("PORT2_MAG", &ManualControlDialog::getPort2Magnitude); -// addDoubleManualQuery("PORT2_PHAse", &ManualControlDialog::getPort2Phase); -// addComplexManualQuery("PORT2_REFerenced", &ManualControlDialog::getPort2Referenced); - -// addIntegerManualQuery("REF_MIN", &ManualControlDialog::getRefMinADC); -// addIntegerManualQuery("REF_MAX", &ManualControlDialog::getRefMaxADC); -// addDoubleManualQuery("REF_MAG", &ManualControlDialog::getRefMagnitude); -// addDoubleManualQuery("REF_PHAse", &ManualControlDialog::getRefPhase); - -// scpi.add(scpi_manual); } void AppWindow::StartTCPServer(int port) diff --git a/Software/PC_Application/LibreVNA-GUI/appwindow.h b/Software/PC_Application/LibreVNA-GUI/appwindow.h index 2fd231ac..71e5a18a 100644 --- a/Software/PC_Application/LibreVNA-GUI/appwindow.h +++ b/Software/PC_Application/LibreVNA-GUI/appwindow.h @@ -159,6 +159,8 @@ private slots: QCommandLineParser parser; SCPI scpi; + std::vector temporaryDeviceCommands; + std::vector temporaryDeviceNodes; TCPServer *server; StreamingServer *streamVNARawData; StreamingServer *streamVNACalibratedData; diff --git a/Software/PC_Application/LibreVNA-GUI/scpi.cpp b/Software/PC_Application/LibreVNA-GUI/scpi.cpp index f873e9be..36f5c729 100644 --- a/Software/PC_Application/LibreVNA-GUI/scpi.cpp +++ b/Software/PC_Application/LibreVNA-GUI/scpi.cpp @@ -285,39 +285,6 @@ SCPINode::~SCPINode() } } -bool SCPINode::add(SCPINode *node) -{ - if(nameCollision(node->name)) { - qWarning() << "Unable to add SCPI node, name collision: " << node->name; - return false; - } - subnodes.push_back(node); - node->parent = this; - return true; -} - -bool SCPINode::remove(SCPINode *node) -{ - auto it = std::find(subnodes.begin(), subnodes.end(), node); - if(it != subnodes.end()) { - subnodes.erase(it); - node->parent = nullptr; - return true; - } else { - return false; - } -} - -bool SCPINode::add(SCPICommand *cmd) -{ - if(nameCollision(cmd->name())) { - qWarning() << "Unable to add SCPI command, name collision: " << cmd->name(); - return false; - } - commands.push_back(cmd); - return true; -} - bool SCPINode::addDoubleParameter(QString name, double ¶m, bool gettable, bool settable, std::function setCallback) { auto cmd = settable ? [¶m, setCallback](QStringList params) -> QString { @@ -441,14 +408,118 @@ void SCPINode::createCommandList(QString prefix, QString &list) { for(auto c : commands) { if(c->queryable()) { - list += prefix + c->name() + "?\n"; + list += prefix + c->leafName() + "?\n"; } if(c->executable()) { - list += prefix + c->name() + '\n'; + list += prefix + c->leafName() + '\n'; } } for(auto n : subnodes) { - n->createCommandList(prefix + n->name + ":", list); + n->createCommandList(prefix + n->leafName() + ":", list); + } +} + +SCPINode *SCPINode::findSubnode(QString name) +{ + for(auto n : subnodes) { + if(n->name == name) { + return n; + } + } + return nullptr; +} + +bool SCPINode::addInternal(SCPINode *node, int depth) +{ + QStringList parts = node->name.split(":"); + if(depth == parts.size()-1) { + // add to this node + if(nameCollision(parts[depth])) { + qWarning() << "Unable to add SCPI node, name collision: " << node->name; + return false; + } + subnodes.push_back(node); + node->parent = this; + return true; + } else { + // add to subnode + auto subNode = findSubnode(parts[depth]); + if(!subNode) { + // does not exist, create first + subNode = new SCPINode(parts[depth]); + add(subNode); + } + return subNode->addInternal(node, depth+1); + } +} + +bool SCPINode::removeInternal(SCPINode *node, int depth) +{ + QStringList parts = node->name.split(":"); + if(depth == parts.size()-1) { + // remove from this node + auto it = std::find(subnodes.begin(), subnodes.end(), node); + if(it != subnodes.end()) { + subnodes.erase(it); + node->parent = nullptr; + return true; + } else { + return false; + } + } else { + // remove from subnode + auto subNode = findSubnode(parts[depth]); + if(!subNode) { + // does not exist + return false; + } + return subNode->removeInternal(node, depth+1); + } +} + +bool SCPINode::addInternal(SCPICommand *cmd, int depth) +{ + QStringList parts = cmd->name().split(":"); + if(depth == parts.size()-1) { + // add to this node + if(nameCollision(parts[depth])) { + qWarning() << "Unable to add SCPI command, name collision: " << cmd->name(); + return false; + } + commands.push_back(cmd); + return true; + } else { + // add to subnode + auto subNode = findSubnode(parts[depth]); + if(!subNode) { + // does not exist, create first + subNode = new SCPINode(parts[depth]); + add(subNode); + } + return subNode->addInternal(cmd, depth+1); + } +} + +bool SCPINode::removeInternal(SCPICommand *cmd, int depth) +{ + QStringList parts = cmd->name().split(":"); + if(depth == parts.size()-1) { + // remove from this node + auto it = std::find(commands.begin(), commands.end(), cmd); + if(it != commands.end()) { + commands.erase(it); + return true; + } else { + return false; + } + } else { + // remove from subnode + auto subNode = findSubnode(parts[depth]); + if(!subNode) { + // does not exist + return false; + } + return subNode->removeInternal(cmd, depth+1); } } @@ -463,7 +534,7 @@ QString SCPINode::parse(QString cmd, SCPINode* &lastNode) // have not reached a leaf, find next subnode auto subnode = cmd.left(splitPos); for(auto n : subnodes) { - if(SCPI::match(n->name, subnode.toUpper())) { + if(SCPI::match(n->leafName(), subnode.toUpper())) { // pass on to next level return n->parse(cmd.right(cmd.size() - splitPos - 1), lastNode); } @@ -481,7 +552,7 @@ QString SCPINode::parse(QString cmd, SCPINode* &lastNode) cmd.chop(1); } for(auto c : commands) { - if(SCPI::match(c->name(), cmd.toUpper())) { + if(SCPI::match(c->leafName(), cmd.toUpper())) { // save current node in case of non-root for the next command lastNode = this; if(c->convertToUppercase()) { diff --git a/Software/PC_Application/LibreVNA-GUI/scpi.h b/Software/PC_Application/LibreVNA-GUI/scpi.h index 42e122a0..c2bd72c7 100644 --- a/Software/PC_Application/LibreVNA-GUI/scpi.h +++ b/Software/PC_Application/LibreVNA-GUI/scpi.h @@ -21,6 +21,7 @@ class SCPICommand { bool queryable() { return fn_query != nullptr;} bool executable() { return fn_cmd != nullptr;} bool convertToUppercase() { return argAlwaysUppercase;} + QString leafName() {return _name.split(":").back();} private: const QString _name; std::function fn_cmd; @@ -35,15 +36,17 @@ class SCPINode { name(name), parent(nullptr), operationPending(false){} virtual ~SCPINode(); - bool add(SCPINode *node); - bool remove(SCPINode *node); - bool add(SCPICommand *cmd); + bool add(SCPINode *node) {return addInternal(node, 0);} + bool remove(SCPINode *node) {return removeInternal(node, 0);} + bool add(SCPICommand *cmd) {return addInternal(cmd, 0);} + bool remove(SCPICommand *cmd) {return removeInternal(cmd, 0);} bool addDoubleParameter(QString name, double ¶m, bool gettable = true, bool settable = true, std::function setCallback = nullptr); bool addUnsignedIntParameter(QString name, unsigned int ¶m, bool gettable = true, bool settable = true, std::function setCallback = nullptr); bool addBoolParameter(QString name, bool ¶m, bool gettable = true, bool settable = true, std::function setCallback = nullptr); bool changeName(QString newname); + QString leafName() {return name.split(":").back();} void setOperationPending(bool pending); @@ -54,6 +57,11 @@ class SCPINode { QString parse(QString cmd, SCPINode* &lastNode); bool nameCollision(QString name); void createCommandList(QString prefix, QString &list); + SCPINode *findSubnode(QString name); + bool addInternal(SCPINode *node, int depth); + bool removeInternal(SCPINode *node, int depth); + bool addInternal(SCPICommand *cmd, int depth); + bool removeInternal(SCPICommand *cmd, int depth); QString name; std::vector subnodes; std::vector commands;