Merge branch 'master' of github.com:jankae/LibreVNA

This commit is contained in:
Jan Käberich 2023-07-24 10:29:20 +02:00
commit 175cade3da
9 changed files with 114 additions and 14 deletions

View File

@ -212,7 +212,9 @@ Some commands are both events and queries, depending on whether the question mar
\section{Commands} \section{Commands}
\subsection{General Commands} \subsection{General Commands}
\subsubsection{*IDN} \subsubsection{*IDN}
\query{Returns the identifications string}{*IDN?}{None}{LibreVNA-GUI} \query{Returns the identifications string}{*IDN?}{None}{LibreVNA,LibreVNA-GUI,dummy\_serial,<software version>}
\subsubsection{*OPC}
\query{Returns a 1 after every previous command has been handled}{*OPC?}{None}{1}
\subsubsection{*LST} \subsubsection{*LST}
\query{Lists all available commands}{*LST?}{None}{List of commands, separated by newline} \query{Lists all available commands}{*LST?}{None}{List of commands, separated by newline}
\subsection{Device Commands} \subsection{Device Commands}
@ -249,6 +251,22 @@ This section contains general device commands, available regardless of the curre
VNA VNA
\end{example} \end{example}
\subsubsection{DEVice:SETUP:SAVE}
\event{Saves the GUI setup to a file}{DEVice:SETUP:SAVE}{<filename>}
Important points when saving/loading setup 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 setup files will be saved/loaded from the machine that runs the GUI.
\item If no (or a wrong) file ending is specified, ``.setup'' is automatically added to the filename.
\end{itemize}
\subsubsection{DEVice:SETUP:LOAD}
\query{Loads a setup file}{DEVice:SETUP:LOAD?}{<filename>}{TRUE or FALSE}
\begin{itemize}
\item Filenames must be either absolute or relative to the location of the GUI application.
\item The filename must include the file ending ``.setup''.
\end{itemize}
\subsubsection{DEVice:REFerence:OUT} \subsubsection{DEVice:REFerence:OUT}
\event{Sets the reference output frequency}{DEVice:REFerence:OUT <freq>}{<freq> in MHz, either 0 (disabled), 10 or 100} \event{Sets the reference output frequency}{DEVice:REFerence:OUT <freq>}{<freq> in MHz, either 0 (disabled), 10 or 100}
\query{Queries the reference output frequency}{DEVice:REFerence:OUT?}{None}{Output frequency in MHz} \query{Queries the reference output frequency}{DEVice:REFerence:OUT?}{None}{Output frequency in MHz}
@ -561,7 +579,6 @@ Any number of measurements can be specified (by their number). These measurement
Important points when saving/loading calibration files through SCPI commands: Important points when saving/loading calibration files through SCPI commands:
\begin{itemize} \begin{itemize}
\item Filenames must be either absolute or relative to the location of the GUI application. \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. \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} \end{itemize}

View File

@ -0,0 +1,50 @@
#!/usr/bin/env python3
import time
from libreVNA import libreVNA
# Create the control instance
vna = libreVNA('localhost', 19542)
# Quick connection check (should print "LibreVNA-GUI")
print(vna.query("*IDN?"))
vna.cmd(":VNA:DEEMB:CLEAR")
vna.cmd(":VNA:DEEMB:NEW PORT_EXTENSION")
vna.cmd(":VNA:DEEMB:NEW 2XTHRU")
vna.cmd(":VNA:DEEMB:NEW MATCHING_NETWORK")
vna.cmd(":VNA:DEEMB:NEW IMPEDANCE_RENORMALIZATION")
print("Number of total deembedding options:")
num_options = int(vna.query(":VNA:DEEMB:NUM?"))
print(num_options)
for i in range(1, num_options+1):
option_name = vna.query(":VNA:DEEMB:TYPE? "+str(i))
print("Option "+str(i)+": "+option_name)
# edit port extension
vna.cmd(":VNA:DEEMB:1:PORT 2")
vna.cmd(":VNA:DEEMB:1:DELAY 0.00002")
vna.cmd(":VNA:DEEMB:1:DCLOSS 1")
vna.cmd(":VNA:DEEMB:1:LOSS 3")
vna.cmd(":VNA:DEEMB:1:FREQUENCY 5000000000")
vna.cmd(":VNA:DEEMB:3:PORT 3")
vna.cmd(":VNA:DEEMB:3:CLEAR")
vna.cmd(":VNA:DEEMB:3:ADD FALSE")
vna.cmd(":VNA:DEEMB:3:NEW ParallelC")
vna.cmd(":VNA:DEEMB:3:NEW SeriesR")
vna.cmd(":VNA:DEEMB:3:NEW ParallelL")
vna.cmd(":VNA:DEEMB:3:NEW SeriesL")
vna.cmd(":VNA:DEEMB:3:NEW touchstone_shunt")
vna.cmd(":VNA:DEEMB:3:1:VALUE 0.0001")
vna.cmd(":VNA:DEEMB:3:2:VALUE 0.00002")
vna.cmd(":VNA:DEEMB:3:3:VALUE 0.000003")
vna.cmd(":VNA:DEEMB:3:4:VALUE 0.000004")
vna.cmd(":VNA:DEEMB:3:5:FILE TEST.S2P")
vna.cmd(":VNA:DEEMB:4:IMPedance 75")

View File

@ -14,6 +14,7 @@ using USBID = struct {
static constexpr USBID IDs[] = { static constexpr USBID IDs[] = {
{0x0483, 0x564e}, {0x0483, 0x564e},
{0x0483, 0x4121}, {0x0483, 0x4121},
{0x1209, 0x4121},
}; };
USBInBuffer::USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size) : USBInBuffer::USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size) :

View File

@ -477,7 +477,10 @@ void AppWindow::CreateToolbars()
void AppWindow::SetupSCPI() void AppWindow::SetupSCPI()
{ {
scpi.add(new SCPICommand("*IDN", nullptr, [=](QStringList){ scpi.add(new SCPICommand("*IDN", nullptr, [=](QStringList){
return "LibreVNA-GUI"; return "LibreVNA,LibreVNA-GUI,dummy_serial,"+appVersion;
}));
scpi.add(new SCPICommand("*OPC", nullptr, [=](QStringList){
return "1";
})); }));
auto scpi_dev = new SCPINode("DEVice"); auto scpi_dev = new SCPINode("DEVice");
scpi.add(scpi_dev); scpi.add(scpi_dev);
@ -518,6 +521,27 @@ void AppWindow::SetupSCPI()
ret.chop(1); ret.chop(1);
return ret; return ret;
})); }));
auto scpi_setup = new SCPINode("SETUP");
scpi_dev->add(scpi_setup);
scpi_setup->add(new SCPICommand("SAVE", [=](QStringList params) -> QString {
if(params.size() != 1) {
// no filename given
return SCPI::getResultName(SCPI::Result::Error);
}
SaveSetup(params[0]);
return SCPI::getResultName(SCPI::Result::Empty);
}, nullptr, false));
scpi_setup->add(new SCPICommand("LOAD", nullptr, [=](QStringList params) -> QString {
if(params.size() != 1) {
// no filename given
return SCPI::getResultName(SCPI::Result::False);
}
if(!LoadSetup(params[0])) {
// some error when loading the setup file
return SCPI::getResultName(SCPI::Result::False);
}
return SCPI::getResultName(SCPI::Result::True);
}, false));
auto scpi_ref = new SCPINode("REFerence"); auto scpi_ref = new SCPINode("REFerence");
scpi_dev->add(scpi_ref); scpi_dev->add(scpi_ref);
scpi_ref->add(new SCPICommand("OUT", [=](QStringList params) -> QString { scpi_ref->add(new SCPICommand("OUT", [=](QStringList params) -> QString {
@ -1182,13 +1206,13 @@ nlohmann::json AppWindow::SaveSetup()
return j; return j;
} }
void AppWindow::LoadSetup(QString filename) bool AppWindow::LoadSetup(QString filename)
{ {
ifstream file; ifstream file;
file.open(filename.toStdString()); file.open(filename.toStdString());
if(!file.is_open()) { if(!file.is_open()) {
qWarning() << "Unable to open file:" << filename; qWarning() << "Unable to open file:" << filename;
return; return false;
} }
nlohmann::json j; nlohmann::json j;
try { try {
@ -1197,12 +1221,13 @@ void AppWindow::LoadSetup(QString filename)
InformationBox::ShowError("Error", "Failed to parse the setup file (" + QString(e.what()) + ")"); InformationBox::ShowError("Error", "Failed to parse the setup file (" + QString(e.what()) + ")");
qWarning() << "Parsing of setup file failed: " << e.what(); qWarning() << "Parsing of setup file failed: " << e.what();
file.close(); file.close();
return; return false;
} }
file.close(); file.close();
LoadSetup(j); LoadSetup(j);
QFileInfo fi(filename); QFileInfo fi(filename);
lSetupName.setText("Setup: "+fi.fileName()); lSetupName.setText("Setup: "+fi.fileName());
return true;
} }
void AppWindow::LoadSetup(nlohmann::json j) void AppWindow::LoadSetup(nlohmann::json j)

View File

@ -68,7 +68,7 @@ private slots:
void DeviceFlagsUpdated(); void DeviceFlagsUpdated();
void DeviceInfoUpdated(); void DeviceInfoUpdated();
void SaveSetup(QString filename); void SaveSetup(QString filename);
void LoadSetup(QString filename); bool LoadSetup(QString filename);
private: private:
nlohmann::json SaveSetup(); nlohmann::json SaveSetup();
void LoadSetup(nlohmann::json j); void LoadSetup(nlohmann::json j);

View File

@ -105,7 +105,6 @@ void SCPI::input(QString line)
if(cmd[0] == ':') { if(cmd[0] == ':') {
cmd.remove(0, 1); cmd.remove(0, 1);
} }
cmd = cmd.toUpper();
auto response = lastNode->parse(cmd, lastNode); auto response = lastNode->parse(cmd, lastNode);
emit output(response); emit output(response);
} }
@ -274,7 +273,7 @@ QString SCPINode::parse(QString cmd, SCPINode* &lastNode)
// have not reached a leaf, find next subnode // have not reached a leaf, find next subnode
auto subnode = cmd.left(splitPos); auto subnode = cmd.left(splitPos);
for(auto n : subnodes) { for(auto n : subnodes) {
if(SCPI::match(n->name, subnode)) { if(SCPI::match(n->name, subnode.toUpper())) {
// pass on to next level // pass on to next level
return n->parse(cmd.right(cmd.size() - splitPos - 1), lastNode); return n->parse(cmd.right(cmd.size() - splitPos - 1), lastNode);
} }
@ -292,9 +291,14 @@ QString SCPINode::parse(QString cmd, SCPINode* &lastNode)
cmd.chop(1); cmd.chop(1);
} }
for(auto c : commands) { for(auto c : commands) {
if(SCPI::match(c->name(), cmd)) { if(SCPI::match(c->name(), cmd.toUpper())) {
// save current node in case of non-root for the next command // save current node in case of non-root for the next command
lastNode = this; lastNode = this;
if(c->convertToUppercase()) {
for(auto &p : params) {
p = p.toUpper();
}
}
if(isQuery) { if(isQuery) {
return c->query(params); return c->query(params);
} else { } else {

View File

@ -8,20 +8,23 @@
class SCPICommand { class SCPICommand {
public: public:
SCPICommand(QString name, std::function<QString(QStringList)> cmd, std::function<QString(QStringList)> query) : SCPICommand(QString name, std::function<QString(QStringList)> cmd, std::function<QString(QStringList)> query, bool convertToUppercase = true) :
_name(name), _name(name),
fn_cmd(cmd), fn_cmd(cmd),
fn_query(query){} fn_query(query),
argAlwaysUppercase(convertToUppercase){}
QString execute(QStringList params); QString execute(QStringList params);
QString query(QStringList params); QString query(QStringList params);
QString name() {return _name;} QString name() {return _name;}
bool queryable() { return fn_query != nullptr;} bool queryable() { return fn_query != nullptr;}
bool executable() { return fn_cmd != nullptr;} bool executable() { return fn_cmd != nullptr;}
bool convertToUppercase() { return argAlwaysUppercase;}
private: private:
const QString _name; const QString _name;
std::function<QString(QStringList)> fn_cmd; std::function<QString(QStringList)> fn_cmd;
std::function<QString(QStringList)> fn_query; std::function<QString(QStringList)> fn_query;
bool argAlwaysUppercase;
}; };
class SCPINode { class SCPINode {

View File

@ -63,10 +63,10 @@
* @{ * @{
*/ */
#define USBD_VID 0x0483 #define USBD_VID 0x1209
#define USBD_PID_FS 0x4121 #define USBD_PID_FS 0x4121
#define USBD_LANGID_STRING 0x0409 #define USBD_LANGID_STRING 0x0409
#define USBD_MANUFACTURER_STRING "STMicroelectronics" #define USBD_MANUFACTURER_STRING "LibreVNA"
#define USBD_PRODUCT_STRING_FS "VNA" #define USBD_PRODUCT_STRING_FS "VNA"
#define USBD_CONFIGURATION_STRING_FS "CustomUSBDevice Config" #define USBD_CONFIGURATION_STRING_FS "CustomUSBDevice Config"
#define USBD_INTERFACE_STRING_FS "CustomUSBDevice Interface" #define USBD_INTERFACE_STRING_FS "CustomUSBDevice Interface"