Implementation of some SCPI commands via TCP
This commit is contained in:
parent
f1d52f159b
commit
a15d02f217
@ -22,6 +22,7 @@ JSONPickerDialog::~JSONPickerDialog()
|
|||||||
JSONModel::JSONModel(const nlohmann::json &json, QObject *parent) :
|
JSONModel::JSONModel(const nlohmann::json &json, QObject *parent) :
|
||||||
json(json)
|
json(json)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
setupJsonInfo(json);
|
setupJsonInfo(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +98,7 @@ int JSONModel::rowCount(const QModelIndex &parent) const
|
|||||||
|
|
||||||
int JSONModel::columnCount(const QModelIndex &parent) const
|
int JSONModel::columnCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,11 +135,15 @@ QVariant JSONModel::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
QVariant JSONModel::headerData(int section, Qt::Orientation orientation, int role) const
|
QVariant JSONModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(section)
|
||||||
|
Q_UNUSED(orientation)
|
||||||
|
Q_UNUSED(role)
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JSONModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
bool JSONModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(value)
|
||||||
nlohmann::json *item = static_cast<nlohmann::json*>(index.internalPointer());
|
nlohmann::json *item = static_cast<nlohmann::json*>(index.internalPointer());
|
||||||
auto info = jsonInfo.at(item);
|
auto info = jsonInfo.at(item);
|
||||||
if(role == Qt::CheckStateRole)
|
if(role == Qt::CheckStateRole)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
Generator::Generator(AppWindow *window)
|
Generator::Generator(AppWindow *window)
|
||||||
: Mode(window, "Signal Generator")
|
: Mode(window, "Signal Generator")
|
||||||
|
, SCPINode("GENerator")
|
||||||
{
|
{
|
||||||
central = new SignalgeneratorWidget(window);
|
central = new SignalgeneratorWidget(window);
|
||||||
|
|
||||||
@ -18,6 +19,8 @@ Generator::Generator(AppWindow *window)
|
|||||||
central->setLevel(pref.Startup.Generator.level);
|
central->setLevel(pref.Startup.Generator.level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupSCPI();
|
||||||
|
|
||||||
finalize(central);
|
finalize(central);
|
||||||
connect(central, &SignalgeneratorWidget::SettingsChanged, this, &Generator::updateDevice);
|
connect(central, &SignalgeneratorWidget::SettingsChanged, this, &Generator::updateDevice);
|
||||||
}
|
}
|
||||||
@ -48,3 +51,52 @@ void Generator::updateDevice()
|
|||||||
p.generator = central->getDeviceStatus();
|
p.generator = central->getDeviceStatus();
|
||||||
window->getDevice()->SendPacket(p);
|
window->getDevice()->SendPacket(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Generator::setupSCPI()
|
||||||
|
{
|
||||||
|
add(new SCPICommand("FREQuency", [=](QStringList params) -> QString {
|
||||||
|
bool ok;
|
||||||
|
if(params.size() != 1) {
|
||||||
|
return "ERROR";
|
||||||
|
}
|
||||||
|
auto newval = params[0].toUInt(&ok);
|
||||||
|
if(!ok) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
central->setFrequency(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(central->getDeviceStatus().frequency);
|
||||||
|
}));
|
||||||
|
add(new SCPICommand("LVL", [=](QStringList params) -> QString {
|
||||||
|
bool ok;
|
||||||
|
if(params.size() != 1) {
|
||||||
|
return "ERROR";
|
||||||
|
}
|
||||||
|
auto newval = params[0].toDouble(&ok);
|
||||||
|
if(!ok) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
central->setLevel(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(central->getDeviceStatus().cdbm_level / 100.0);
|
||||||
|
}));
|
||||||
|
add(new SCPICommand("PORT", [=](QStringList params) -> QString {
|
||||||
|
bool ok;
|
||||||
|
if(params.size() != 1) {
|
||||||
|
return "ERROR";
|
||||||
|
}
|
||||||
|
auto newval = params[0].toUInt(&ok);
|
||||||
|
if(!ok || newval > 2) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
central->setPort(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(central->getDeviceStatus().activePort);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
#include "mode.h"
|
#include "mode.h"
|
||||||
#include "signalgenwidget.h"
|
#include "signalgenwidget.h"
|
||||||
|
#include "scpi.h"
|
||||||
|
|
||||||
class Generator : public Mode
|
class Generator : public Mode, public SCPINode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Generator(AppWindow *window);
|
Generator(AppWindow *window);
|
||||||
@ -19,6 +20,7 @@ private slots:
|
|||||||
void updateDevice();
|
void updateDevice();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void setupSCPI();
|
||||||
SignalgeneratorWidget *central;
|
SignalgeneratorWidget *central;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,13 +92,13 @@ SignalgeneratorWidget::SignalgeneratorWidget(QWidget *parent) :
|
|||||||
connect(ui->levelSlider, &QSlider::valueChanged, [=](int value) {
|
connect(ui->levelSlider, &QSlider::valueChanged, [=](int value) {
|
||||||
setLevel((double) value / 100.0);
|
setLevel((double) value / 100.0);
|
||||||
});
|
});
|
||||||
connect(ui->EnablePort1, &QCheckBox::clicked, [=](){
|
connect(ui->EnablePort1, &QCheckBox::toggled, [=](){
|
||||||
if(ui->EnablePort1->isChecked() && ui->EnablePort2->isChecked()) {
|
if(ui->EnablePort1->isChecked() && ui->EnablePort2->isChecked()) {
|
||||||
ui->EnablePort2->setCheckState(Qt::CheckState::Unchecked);
|
ui->EnablePort2->setCheckState(Qt::CheckState::Unchecked);
|
||||||
}
|
}
|
||||||
emit SettingsChanged();
|
emit SettingsChanged();
|
||||||
});
|
});
|
||||||
connect(ui->EnablePort2, &QCheckBox::clicked, [=](){
|
connect(ui->EnablePort2, &QCheckBox::toggled, [=](){
|
||||||
if(ui->EnablePort1->isChecked() && ui->EnablePort2->isChecked()) {
|
if(ui->EnablePort1->isChecked() && ui->EnablePort2->isChecked()) {
|
||||||
ui->EnablePort1->setCheckState(Qt::CheckState::Unchecked);
|
ui->EnablePort1->setCheckState(Qt::CheckState::Unchecked);
|
||||||
}
|
}
|
||||||
@ -174,3 +174,24 @@ void SignalgeneratorWidget::setFrequency(double frequency)
|
|||||||
ui->frequency->setValue(frequency);
|
ui->frequency->setValue(frequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SignalgeneratorWidget::setPort(int port)
|
||||||
|
{
|
||||||
|
if(port < 0 || port > 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch(port) {
|
||||||
|
case 0:
|
||||||
|
ui->EnablePort1->setChecked(false);
|
||||||
|
ui->EnablePort2->setChecked(false);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
ui->EnablePort1->setChecked(true);
|
||||||
|
ui->EnablePort2->setChecked(false);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ui->EnablePort1->setChecked(false);
|
||||||
|
ui->EnablePort2->setChecked(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ signals:
|
|||||||
public slots:
|
public slots:
|
||||||
void setLevel(double level);
|
void setLevel(double level);
|
||||||
void setFrequency(double frequency);
|
void setFrequency(double frequency);
|
||||||
|
void setPort(int port);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void timerEvent(QTimerEvent *) override;
|
void timerEvent(QTimerEvent *) override;
|
||||||
|
@ -115,6 +115,8 @@ HEADERS += \
|
|||||||
mode.h \
|
mode.h \
|
||||||
preferences.h \
|
preferences.h \
|
||||||
savable.h \
|
savable.h \
|
||||||
|
scpi.h \
|
||||||
|
tcpserver.h \
|
||||||
touchstone.h \
|
touchstone.h \
|
||||||
unit.h
|
unit.h
|
||||||
|
|
||||||
@ -221,6 +223,8 @@ SOURCES += \
|
|||||||
main.cpp \
|
main.cpp \
|
||||||
mode.cpp \
|
mode.cpp \
|
||||||
preferences.cpp \
|
preferences.cpp \
|
||||||
|
scpi.cpp \
|
||||||
|
tcpserver.cpp \
|
||||||
touchstone.cpp \
|
touchstone.cpp \
|
||||||
unit.cpp
|
unit.cpp
|
||||||
|
|
||||||
@ -230,7 +234,7 @@ win32:LIBS += -L"$$_PRO_FILE_PWD_" # Github actions placed libusb here
|
|||||||
osx:INCPATH += /usr/local/include
|
osx:INCPATH += /usr/local/include
|
||||||
osx:LIBS += $(shell pkg-config --libs libusb-1.0)
|
osx:LIBS += $(shell pkg-config --libs libusb-1.0)
|
||||||
|
|
||||||
QT += widgets
|
QT += widgets network
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
Calibration/addamplitudepointsdialog.ui \
|
Calibration/addamplitudepointsdialog.ui \
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
VNA::VNA(AppWindow *window)
|
VNA::VNA(AppWindow *window)
|
||||||
: Mode(window, "Vector Network Analyzer"),
|
: Mode(window, "Vector Network Analyzer"),
|
||||||
|
SCPINode("VNA"),
|
||||||
deembedding(traceModel),
|
deembedding(traceModel),
|
||||||
central(new TileWidget(traceModel))
|
central(new TileWidget(traceModel))
|
||||||
{
|
{
|
||||||
@ -58,6 +59,8 @@ VNA::VNA(AppWindow *window)
|
|||||||
calDialog.reset();
|
calDialog.reset();
|
||||||
calEdited = false;
|
calEdited = false;
|
||||||
|
|
||||||
|
SetupSCPI();
|
||||||
|
|
||||||
// Create default traces
|
// Create default traces
|
||||||
auto tS11 = new Trace("S11", Qt::yellow);
|
auto tS11 = new Trace("S11", Qt::yellow);
|
||||||
tS11->fromLivedata(Trace::LivedataType::Overwrite, Trace::LiveParameter::S11);
|
tS11->fromLivedata(Trace::LivedataType::Overwrite, Trace::LiveParameter::S11);
|
||||||
@ -875,6 +878,124 @@ void VNA::StartCalibrationMeasurement(Calibration::Measurement m)
|
|||||||
calEdited = true;
|
calEdited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VNA::SetupSCPI()
|
||||||
|
{
|
||||||
|
auto scpi_freq = new SCPINode("FREQuency");
|
||||||
|
SCPINode::add(scpi_freq);
|
||||||
|
auto toULong = [](QStringList params) {
|
||||||
|
bool ok;
|
||||||
|
if(params.size() != 1) {
|
||||||
|
return std::numeric_limits<unsigned long>::max();
|
||||||
|
}
|
||||||
|
auto newval = params[0].toULong(&ok);
|
||||||
|
if(!ok) {
|
||||||
|
return std::numeric_limits<unsigned long>::max();
|
||||||
|
} else {
|
||||||
|
return newval;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
scpi_freq->add(new SCPICommand("SPAN", [=](QStringList params) -> QString {
|
||||||
|
auto newval = toULong(params);
|
||||||
|
if(newval == std::numeric_limits<unsigned long>::max()) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
SetSpan(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(settings.f_stop - settings.f_start);
|
||||||
|
}));
|
||||||
|
scpi_freq->add(new SCPICommand("START", [=](QStringList params) -> QString {
|
||||||
|
auto newval = toULong(params);
|
||||||
|
if(newval == std::numeric_limits<unsigned long>::max()) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
SetStartFreq(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(settings.f_start);
|
||||||
|
}));
|
||||||
|
scpi_freq->add(new SCPICommand("CENTER", [=](QStringList params) -> QString {
|
||||||
|
auto newval = toULong(params);
|
||||||
|
if(newval == std::numeric_limits<unsigned long>::max()) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
SetCenterFreq(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number((settings.f_start + settings.f_stop)/2);
|
||||||
|
}));
|
||||||
|
scpi_freq->add(new SCPICommand("STOP", [=](QStringList params) -> QString {
|
||||||
|
auto newval = toULong(params);
|
||||||
|
if(newval == std::numeric_limits<unsigned long>::max()) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
SetStopFreq(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(settings.f_stop);
|
||||||
|
}));
|
||||||
|
scpi_freq->add(new SCPICommand("FULL", [=](QStringList params) -> QString {
|
||||||
|
SetFullSpan();
|
||||||
|
return "";
|
||||||
|
}, nullptr));
|
||||||
|
auto scpi_acq = new SCPINode("ACQuisition");
|
||||||
|
SCPINode::add(scpi_acq);
|
||||||
|
scpi_acq->add(new SCPICommand("IFBW", [=](QStringList params) -> QString {
|
||||||
|
auto newval = toULong(params);
|
||||||
|
if(newval == std::numeric_limits<unsigned long>::max()) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
SetIFBandwidth(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(settings.if_bandwidth);
|
||||||
|
}));
|
||||||
|
scpi_acq->add(new SCPICommand("POINTS", [=](QStringList params) -> QString {
|
||||||
|
auto newval = toULong(params);
|
||||||
|
if(newval == std::numeric_limits<unsigned long>::max()) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
SetPoints(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(settings.points);
|
||||||
|
}));
|
||||||
|
scpi_acq->add(new SCPICommand("AVG", [=](QStringList params) -> QString {
|
||||||
|
auto newval = toULong(params);
|
||||||
|
if(newval == std::numeric_limits<unsigned long>::max()) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
SetAveraging(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(averages);
|
||||||
|
}));
|
||||||
|
auto scpi_stim = new SCPINode("STIMulus");
|
||||||
|
SCPINode::add(scpi_stim);
|
||||||
|
scpi_stim->add(new SCPICommand("LVL", [=](QStringList params) -> QString {
|
||||||
|
bool ok;
|
||||||
|
if(params.size() != 1) {
|
||||||
|
return "ERROR";
|
||||||
|
}
|
||||||
|
auto newval = params[0].toDouble(&ok);
|
||||||
|
if(!ok) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
SetSourceLevel(newval);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
return QString::number(settings.cdbm_excitation / 100.0);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
void VNA::ConstrainAndUpdateFrequencies()
|
void VNA::ConstrainAndUpdateFrequencies()
|
||||||
{
|
{
|
||||||
auto pref = Preferences::getInstance();
|
auto pref = Preferences::getInstance();
|
||||||
|
@ -9,8 +9,9 @@
|
|||||||
#include "Device/device.h"
|
#include "Device/device.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include "Deembedding/deembedding.h"
|
#include "Deembedding/deembedding.h"
|
||||||
|
#include "scpi.h"
|
||||||
|
|
||||||
class VNA : public Mode
|
class VNA : public Mode, public SCPINode
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@ -50,6 +51,7 @@ signals:
|
|||||||
void CalibrationMeasurementComplete(Calibration::Measurement m);
|
void CalibrationMeasurementComplete(Calibration::Measurement m);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void SetupSCPI();
|
||||||
void UpdateAverageCount();
|
void UpdateAverageCount();
|
||||||
void SettingsChanged(std::function<void (Device::TransmissionResult)> cb = nullptr);
|
void SettingsChanged(std::function<void (Device::TransmissionResult)> cb = nullptr);
|
||||||
void ConstrainAndUpdateFrequencies();
|
void ConstrainAndUpdateFrequencies();
|
||||||
|
@ -48,23 +48,106 @@
|
|||||||
#include "Calibration/receivercaldialog.h"
|
#include "Calibration/receivercaldialog.h"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include "CustomWidgets/jsonpickerdialog.h"
|
#include "CustomWidgets/jsonpickerdialog.h"
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
AppWindow::AppWindow(QWidget *parent)
|
AppWindow::AppWindow(QWidget *parent)
|
||||||
: QMainWindow(parent)
|
: QMainWindow(parent)
|
||||||
, deviceActionGroup(new QActionGroup(this))
|
, deviceActionGroup(new QActionGroup(this))
|
||||||
, ui(new Ui::MainWindow)
|
, ui(new Ui::MainWindow)
|
||||||
|
, server(nullptr)
|
||||||
{
|
{
|
||||||
QCoreApplication::setOrganizationName("LibreVNA");
|
QCoreApplication::setOrganizationName("LibreVNA");
|
||||||
QCoreApplication::setApplicationName("LibreVNA-GUI");
|
QCoreApplication::setApplicationName("LibreVNA-GUI");
|
||||||
|
auto commit = QString(GITHASH);
|
||||||
|
commit.truncate(7);
|
||||||
|
QCoreApplication::setApplicationVersion(QString::number(FW_MAJOR) + "." + QString::number(FW_MINOR)
|
||||||
|
+ "." + QString::number(FW_PATCH) + FW_SUFFIX + " ("+ commit+")");
|
||||||
|
|
||||||
qSetMessagePattern("%{time process}: [%{type}] %{message}");
|
qSetMessagePattern("%{time process}: [%{type}] %{message}");
|
||||||
|
|
||||||
|
// qDebug().setVerbosity(0);
|
||||||
qDebug() << "Application start";
|
qDebug() << "Application start";
|
||||||
|
|
||||||
|
parser.setApplicationDescription("LibreVNA-GUI");
|
||||||
|
parser.addHelpOption();
|
||||||
|
parser.addVersionOption();
|
||||||
|
parser.addOption(QCommandLineOption({"p","port"}, "Specify port to listen for SCPY commands", "port"));
|
||||||
|
parser.addOption(QCommandLineOption({"d","device"}, "Only allow connections to the specified device", "device"));
|
||||||
|
parser.addOption(QCommandLineOption("no-gui", "Disables the graphical interface"));
|
||||||
|
|
||||||
|
parser.process(QCoreApplication::arguments());
|
||||||
|
|
||||||
Preferences::getInstance().load();
|
Preferences::getInstance().load();
|
||||||
device = nullptr;
|
device = nullptr;
|
||||||
|
|
||||||
|
if(parser.isSet("port")) {
|
||||||
|
bool OK;
|
||||||
|
auto port = parser.value("port").toUInt(&OK);
|
||||||
|
if(!OK) {
|
||||||
|
// set default port
|
||||||
|
port = 19542;
|
||||||
|
}
|
||||||
|
server = new TCPServer(port);
|
||||||
|
connect(server, &TCPServer::received, &scpi, &SCPI::input);
|
||||||
|
connect(&scpi, &SCPI::output, server, &TCPServer::send);
|
||||||
|
}
|
||||||
|
|
||||||
|
scpi.add(new SCPICommand("*IDN", nullptr, [=](){
|
||||||
|
return "LibreVNA-GUI";
|
||||||
|
}));
|
||||||
|
auto scpi_dev = new SCPINode("DEVice");
|
||||||
|
scpi.add(scpi_dev);
|
||||||
|
scpi_dev->add(new SCPICommand("DISConnect", [=](QStringList params) -> QString {
|
||||||
|
Q_UNUSED(params)
|
||||||
|
DisconnectDevice();
|
||||||
|
return "";
|
||||||
|
}, nullptr));
|
||||||
|
scpi_dev->add(new SCPICommand("CONNect", [=](QStringList params) -> QString {
|
||||||
|
QString serial;
|
||||||
|
if(params.size() > 0) {
|
||||||
|
serial = params[0];
|
||||||
|
}
|
||||||
|
if(!ConnectToDevice(serial)) {
|
||||||
|
return "Device not found";
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [=]() -> QString {
|
||||||
|
if(device) {
|
||||||
|
return device->serial();
|
||||||
|
} else {
|
||||||
|
return "Not connected";
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
scpi.add(new SCPICommand("MODE", [=](QStringList params) -> QString {
|
||||||
|
if (params.size() != 1) {
|
||||||
|
return "ERROR";
|
||||||
|
}
|
||||||
|
if (params[0] == "VNA") {
|
||||||
|
vna->activate();
|
||||||
|
} else if(params[0] == "GEN") {
|
||||||
|
generator->activate();
|
||||||
|
} else if(params[0] == "SA") {
|
||||||
|
spectrumAnalyzer->activate();
|
||||||
|
} else {
|
||||||
|
return "INVALID MDOE";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}, [=]() -> QString {
|
||||||
|
auto active = Mode::getActiveMode();
|
||||||
|
if(active == vna) {
|
||||||
|
return "VNA";
|
||||||
|
} else if(active == generator) {
|
||||||
|
return "GEN";
|
||||||
|
} else if(active == spectrumAnalyzer) {
|
||||||
|
return "SA";
|
||||||
|
} else {
|
||||||
|
return "ERROR";
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->statusbar->addWidget(&lConnectionStatus);
|
ui->statusbar->addWidget(&lConnectionStatus);
|
||||||
auto div1 = new QFrame;
|
auto div1 = new QFrame;
|
||||||
@ -112,6 +195,9 @@ AppWindow::AppWindow(QWidget *parent)
|
|||||||
generator = new Generator(this);
|
generator = new Generator(this);
|
||||||
spectrumAnalyzer = new SpectrumAnalyzer(this);
|
spectrumAnalyzer = new SpectrumAnalyzer(this);
|
||||||
|
|
||||||
|
scpi.add(vna);
|
||||||
|
scpi.add(generator);
|
||||||
|
|
||||||
// UI connections
|
// UI connections
|
||||||
connect(ui->actionUpdate_Device_List, &QAction::triggered, this, &AppWindow::UpdateDeviceList);
|
connect(ui->actionUpdate_Device_List, &QAction::triggered, this, &AppWindow::UpdateDeviceList);
|
||||||
connect(ui->actionDisconnect, &QAction::triggered, this, &AppWindow::DisconnectDevice);
|
connect(ui->actionDisconnect, &QAction::triggered, this, &AppWindow::DisconnectDevice);
|
||||||
@ -158,11 +244,8 @@ AppWindow::AppWindow(QWidget *parent)
|
|||||||
// TraceXYPlot::updateGraphColors();
|
// TraceXYPlot::updateGraphColors();
|
||||||
});
|
});
|
||||||
connect(ui->actionAbout, &QAction::triggered, [=](){
|
connect(ui->actionAbout, &QAction::triggered, [=](){
|
||||||
auto commit = QString(GITHASH);
|
|
||||||
commit.truncate(7);
|
|
||||||
QMessageBox::about(this, "About", "More information: github.com/jankae/LibreVNA\n"
|
QMessageBox::about(this, "About", "More information: github.com/jankae/LibreVNA\n"
|
||||||
"\nVersion: " + QString::number(FW_MAJOR) + "." + QString::number(FW_MINOR)
|
"\nVersion: " + QCoreApplication::applicationVersion());
|
||||||
+ "." + QString::number(FW_PATCH) + FW_SUFFIX + " ("+ commit+")");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setWindowTitle("LibreVNA-GUI");
|
setWindowTitle("LibreVNA-GUI");
|
||||||
@ -190,11 +273,16 @@ AppWindow::AppWindow(QWidget *parent)
|
|||||||
// at least one device available
|
// at least one device available
|
||||||
ConnectToDevice();
|
ConnectToDevice();
|
||||||
}
|
}
|
||||||
|
if(!parser.isSet("no-gui")) {
|
||||||
|
resize(1280, 800);
|
||||||
|
show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AppWindow::~AppWindow()
|
AppWindow::~AppWindow()
|
||||||
{
|
{
|
||||||
delete ui;
|
delete ui;
|
||||||
|
delete server;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppWindow::closeEvent(QCloseEvent *event)
|
void AppWindow::closeEvent(QCloseEvent *event)
|
||||||
@ -213,7 +301,7 @@ void AppWindow::closeEvent(QCloseEvent *event)
|
|||||||
QMainWindow::closeEvent(event);
|
QMainWindow::closeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppWindow::ConnectToDevice(QString serial)
|
bool AppWindow::ConnectToDevice(QString serial)
|
||||||
{
|
{
|
||||||
if(serial.isEmpty()) {
|
if(serial.isEmpty()) {
|
||||||
qDebug() << "Trying to connect to any device";
|
qDebug() << "Trying to connect to any device";
|
||||||
@ -256,10 +344,12 @@ void AppWindow::ConnectToDevice(QString serial)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
} catch (const runtime_error &e) {
|
} catch (const runtime_error &e) {
|
||||||
qWarning() << "Failed to connect:" << e.what();
|
qWarning() << "Failed to connect:" << e.what();
|
||||||
DisconnectDevice();
|
DisconnectDevice();
|
||||||
UpdateDeviceList();
|
UpdateDeviceList();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,8 +412,13 @@ int AppWindow::UpdateDeviceList()
|
|||||||
if(device) {
|
if(device) {
|
||||||
devices.insert(device->serial());
|
devices.insert(device->serial());
|
||||||
}
|
}
|
||||||
|
int available = 0;
|
||||||
if(devices.size()) {
|
if(devices.size()) {
|
||||||
for(auto d : devices) {
|
for(auto d : devices) {
|
||||||
|
if(!parser.value("device").isEmpty() && parser.value("device") != d) {
|
||||||
|
// specified device does not match, ignore
|
||||||
|
continue;
|
||||||
|
}
|
||||||
auto connectAction = ui->menuConnect_to->addAction(d);
|
auto connectAction = ui->menuConnect_to->addAction(d);
|
||||||
connectAction->setCheckable(true);
|
connectAction->setCheckable(true);
|
||||||
connectAction->setActionGroup(deviceActionGroup);
|
connectAction->setActionGroup(deviceActionGroup);
|
||||||
@ -333,14 +428,15 @@ int AppWindow::UpdateDeviceList()
|
|||||||
connect(connectAction, &QAction::triggered, [this, d]() {
|
connect(connectAction, &QAction::triggered, [this, d]() {
|
||||||
ConnectToDevice(d);
|
ConnectToDevice(d);
|
||||||
});
|
});
|
||||||
|
ui->menuConnect_to->setEnabled(true);
|
||||||
|
available++;
|
||||||
}
|
}
|
||||||
ui->menuConnect_to->setEnabled(true);
|
|
||||||
} else {
|
} else {
|
||||||
// no devices available, disable connection option
|
// no devices available, disable connection option
|
||||||
ui->menuConnect_to->setEnabled(false);
|
ui->menuConnect_to->setEnabled(false);
|
||||||
}
|
}
|
||||||
qDebug() << "Updated device list, found" << devices.size();
|
qDebug() << "Updated device list, found" << available;
|
||||||
return devices.size();
|
return available;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppWindow::StartManualControl()
|
void AppWindow::StartManualControl()
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
#include <QButtonGroup>
|
#include <QButtonGroup>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
#include "scpi.h"
|
||||||
|
#include "tcpserver.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
@ -41,7 +44,7 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *event) override;
|
||||||
private slots:
|
private slots:
|
||||||
void ConnectToDevice(QString serial = QString());
|
bool ConnectToDevice(QString serial = QString());
|
||||||
void DisconnectDevice();
|
void DisconnectDevice();
|
||||||
int UpdateDeviceList();
|
int UpdateDeviceList();
|
||||||
void StartManualControl();
|
void StartManualControl();
|
||||||
@ -84,6 +87,10 @@ private:
|
|||||||
QLabel lUnlock;
|
QLabel lUnlock;
|
||||||
|
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
|
QCommandLineParser parser;
|
||||||
|
|
||||||
|
SCPI scpi;
|
||||||
|
TCPServer *server;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VNA_H
|
#endif // VNA_H
|
||||||
|
@ -7,12 +7,22 @@
|
|||||||
#include "Calibration/calkit.h"
|
#include "Calibration/calkit.h"
|
||||||
#include "touchstone.h"
|
#include "touchstone.h"
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
#include <complex>
|
#include <complex>
|
||||||
|
|
||||||
|
static QApplication *app;
|
||||||
|
static AppWindow *window;
|
||||||
|
|
||||||
|
void sig_handler(int s) {
|
||||||
|
Q_UNUSED(s)
|
||||||
|
window->close();
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QApplication a(argc, argv);
|
app = new QApplication(argc, argv);
|
||||||
AppWindow vna;
|
window = new AppWindow;
|
||||||
vna.resize(1280, 800);
|
signal(SIGINT, sig_handler);
|
||||||
vna.show();
|
app->exec();
|
||||||
a.exec();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
160
Software/PC_Application/scpi.cpp
Normal file
160
Software/PC_Application/scpi.cpp
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
#include "scpi.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
SCPI::SCPI() :
|
||||||
|
SCPINode("")
|
||||||
|
{
|
||||||
|
lastNode = this;
|
||||||
|
add(new SCPICommand("*LST", nullptr, [=](){
|
||||||
|
QString list;
|
||||||
|
createCommandList("", list);
|
||||||
|
return list.trimmed();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SCPI::match(QString s1, QString s2)
|
||||||
|
{
|
||||||
|
if (s1.compare(s2, Qt::CaseInsensitive) == 0
|
||||||
|
|| s1.compare(alternateName(s2), Qt::CaseInsensitive) == 0
|
||||||
|
|| alternateName(s1).compare(alternateName(s2), Qt::CaseInsensitive) == 0
|
||||||
|
|| alternateName(s1).compare(alternateName(s2), Qt::CaseInsensitive) == 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SCPI::alternateName(QString name)
|
||||||
|
{
|
||||||
|
while(name[name.size()-1].isLower()) {
|
||||||
|
name.chop(1);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCPI::input(QString line)
|
||||||
|
{
|
||||||
|
auto cmds = line.split(";");
|
||||||
|
for(auto cmd : cmds) {
|
||||||
|
if(cmd[0] == ':' || cmd[0] == '*') {
|
||||||
|
// reset to root node
|
||||||
|
lastNode = this;
|
||||||
|
}
|
||||||
|
if(cmd[0] == ':') {
|
||||||
|
cmd.remove(0, 1);
|
||||||
|
}
|
||||||
|
cmd = cmd.toUpper();
|
||||||
|
auto response = lastNode->parse(cmd, lastNode);
|
||||||
|
if(!response.isEmpty()) {
|
||||||
|
emit output(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SCPINode::add(SCPICommand *cmd)
|
||||||
|
{
|
||||||
|
if(nameCollision(cmd->name())) {
|
||||||
|
qWarning() << "Unable to add SCPI node, name collision: " << cmd->name();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
commands.push_back(cmd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SCPINode::nameCollision(QString name)
|
||||||
|
{
|
||||||
|
for(auto n : subnodes) {
|
||||||
|
if(SCPI::match(n->name, name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(auto c : commands) {
|
||||||
|
if(SCPI::match(c->name(), name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCPINode::createCommandList(QString prefix, QString &list)
|
||||||
|
{
|
||||||
|
for(auto c : commands) {
|
||||||
|
if(c->queryable()) {
|
||||||
|
list += prefix + c->name() + "?\n";
|
||||||
|
}
|
||||||
|
if(c->executable()) {
|
||||||
|
list += prefix + c->name() + '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(auto n : subnodes) {
|
||||||
|
n->createCommandList(prefix + n->name + ":", list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SCPINode::parse(QString cmd, SCPINode* &lastNode)
|
||||||
|
{
|
||||||
|
auto splitPos = cmd.indexOf(':');
|
||||||
|
if(splitPos > 0) {
|
||||||
|
// have not reached a leaf, find next subnode
|
||||||
|
auto subnode = cmd.left(splitPos);
|
||||||
|
for(auto n : subnodes) {
|
||||||
|
if(SCPI::match(n->name, subnode)) {
|
||||||
|
// pass on to next level
|
||||||
|
return n->parse(cmd.right(cmd.size() - splitPos - 1), lastNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unable to find subnode
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
// no more levels, search for command
|
||||||
|
auto params = cmd.split(" ");
|
||||||
|
auto cmd = params.front();
|
||||||
|
params.pop_front();
|
||||||
|
bool isQuery = false;
|
||||||
|
if (cmd[cmd.size()-1]=='?') {
|
||||||
|
isQuery = true;
|
||||||
|
cmd.chop(1);
|
||||||
|
}
|
||||||
|
for(auto c : commands) {
|
||||||
|
if(SCPI::match(c->name(), cmd)) {
|
||||||
|
// save current node in case of non-root for the next command
|
||||||
|
lastNode = this;
|
||||||
|
if(isQuery) {
|
||||||
|
return c->query();
|
||||||
|
} else {
|
||||||
|
return c->execute(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// couldn't find command
|
||||||
|
return "ERROR";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SCPICommand::execute(QStringList params)
|
||||||
|
{
|
||||||
|
if(fn_cmd == nullptr) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
return fn_cmd(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SCPICommand::query()
|
||||||
|
{
|
||||||
|
if(fn_query == nullptr) {
|
||||||
|
return "ERROR";
|
||||||
|
} else {
|
||||||
|
return fn_query();
|
||||||
|
}
|
||||||
|
}
|
63
Software/PC_Application/scpi.h
Normal file
63
Software/PC_Application/scpi.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#ifndef SCPI_H
|
||||||
|
#define SCPI_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QObject>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class SCPICommand {
|
||||||
|
public:
|
||||||
|
SCPICommand(QString name, std::function<QString(QStringList)> cmd, std::function<QString()> query) :
|
||||||
|
_name(name),
|
||||||
|
fn_cmd(cmd),
|
||||||
|
fn_query(query){}
|
||||||
|
|
||||||
|
QString execute(QStringList params);
|
||||||
|
QString query();
|
||||||
|
QString name() {return _name;}
|
||||||
|
bool queryable() { return fn_query != nullptr;};
|
||||||
|
bool executable() { return fn_cmd != nullptr;};
|
||||||
|
private:
|
||||||
|
const QString _name;
|
||||||
|
std::function<QString(QStringList)> fn_cmd;
|
||||||
|
std::function<QString()> fn_query;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SCPINode {
|
||||||
|
friend class SCPI;
|
||||||
|
public:
|
||||||
|
SCPINode(QString name) :
|
||||||
|
name(name){}
|
||||||
|
|
||||||
|
bool add(SCPINode *node);
|
||||||
|
bool add(SCPICommand *cmd);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString parse(QString cmd, SCPINode* &lastNode);
|
||||||
|
bool nameCollision(QString name);
|
||||||
|
void createCommandList(QString prefix, QString &list);
|
||||||
|
const QString name;
|
||||||
|
std::vector<SCPINode*> subnodes;
|
||||||
|
std::vector<SCPICommand*> commands;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SCPI : public QObject, public SCPINode
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
SCPI();
|
||||||
|
|
||||||
|
static bool match(QString s1, QString s2);
|
||||||
|
static QString alternateName(QString name);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void input(QString line);
|
||||||
|
signals:
|
||||||
|
void output(QString line);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SCPINode *lastNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SCPI_H
|
40
Software/PC_Application/tcpserver.cpp
Normal file
40
Software/PC_Application/tcpserver.cpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#include "tcpserver.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
TCPServer::TCPServer(int port)
|
||||||
|
{
|
||||||
|
qInfo() << "Listening on port" << port;
|
||||||
|
socket = nullptr;
|
||||||
|
server.listen(QHostAddress::Any, port);
|
||||||
|
connect(&server, &QTcpServer::newConnection, [&](){
|
||||||
|
// only one connection at a time
|
||||||
|
delete socket;
|
||||||
|
socket = server.nextPendingConnection();
|
||||||
|
connect(socket, &QTcpSocket::readyRead, [=](){
|
||||||
|
if(socket->canReadLine()) {
|
||||||
|
auto available = socket->bytesAvailable();
|
||||||
|
char data[available+1];
|
||||||
|
socket->readLine(data, sizeof(data));
|
||||||
|
auto line = QString(data);
|
||||||
|
emit received(line.trimmed());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(socket, &QTcpSocket::stateChanged, [&](QAbstractSocket::SocketState state){
|
||||||
|
if (state == QAbstractSocket::UnconnectedState)
|
||||||
|
{
|
||||||
|
socket->deleteLater();
|
||||||
|
socket = nullptr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TCPServer::send(QString line)
|
||||||
|
{
|
||||||
|
if (socket) {
|
||||||
|
socket->write(QByteArray::fromStdString(line.toStdString()+'\n'));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
23
Software/PC_Application/tcpserver.h
Normal file
23
Software/PC_Application/tcpserver.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef TCPSERVER_H
|
||||||
|
#define TCPSERVER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTcpServer>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
|
||||||
|
class TCPServer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
TCPServer(int port);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool send(QString line);
|
||||||
|
signals:
|
||||||
|
void received(QString line);
|
||||||
|
private:
|
||||||
|
QTcpServer server;
|
||||||
|
QTcpSocket *socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TCPSERVER_H
|
Loading…
Reference in New Issue
Block a user