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) :
|
||||
json(json)
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
setupJsonInfo(json);
|
||||
}
|
||||
|
||||
@ -97,6 +98,7 @@ int JSONModel::rowCount(const QModelIndex &parent) const
|
||||
|
||||
int JSONModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
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
|
||||
{
|
||||
Q_UNUSED(section)
|
||||
Q_UNUSED(orientation)
|
||||
Q_UNUSED(role)
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool JSONModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
Q_UNUSED(value)
|
||||
nlohmann::json *item = static_cast<nlohmann::json*>(index.internalPointer());
|
||||
auto info = jsonInfo.at(item);
|
||||
if(role == Qt::CheckStateRole)
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
Generator::Generator(AppWindow *window)
|
||||
: Mode(window, "Signal Generator")
|
||||
, SCPINode("GENerator")
|
||||
{
|
||||
central = new SignalgeneratorWidget(window);
|
||||
|
||||
@ -18,6 +19,8 @@ Generator::Generator(AppWindow *window)
|
||||
central->setLevel(pref.Startup.Generator.level);
|
||||
}
|
||||
|
||||
setupSCPI();
|
||||
|
||||
finalize(central);
|
||||
connect(central, &SignalgeneratorWidget::SettingsChanged, this, &Generator::updateDevice);
|
||||
}
|
||||
@ -48,3 +51,52 @@ void Generator::updateDevice()
|
||||
p.generator = central->getDeviceStatus();
|
||||
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 "signalgenwidget.h"
|
||||
#include "scpi.h"
|
||||
|
||||
class Generator : public Mode
|
||||
class Generator : public Mode, public SCPINode
|
||||
{
|
||||
public:
|
||||
Generator(AppWindow *window);
|
||||
@ -19,6 +20,7 @@ private slots:
|
||||
void updateDevice();
|
||||
|
||||
private:
|
||||
void setupSCPI();
|
||||
SignalgeneratorWidget *central;
|
||||
};
|
||||
|
||||
|
@ -92,13 +92,13 @@ SignalgeneratorWidget::SignalgeneratorWidget(QWidget *parent) :
|
||||
connect(ui->levelSlider, &QSlider::valueChanged, [=](int value) {
|
||||
setLevel((double) value / 100.0);
|
||||
});
|
||||
connect(ui->EnablePort1, &QCheckBox::clicked, [=](){
|
||||
connect(ui->EnablePort1, &QCheckBox::toggled, [=](){
|
||||
if(ui->EnablePort1->isChecked() && ui->EnablePort2->isChecked()) {
|
||||
ui->EnablePort2->setCheckState(Qt::CheckState::Unchecked);
|
||||
}
|
||||
emit SettingsChanged();
|
||||
});
|
||||
connect(ui->EnablePort2, &QCheckBox::clicked, [=](){
|
||||
connect(ui->EnablePort2, &QCheckBox::toggled, [=](){
|
||||
if(ui->EnablePort1->isChecked() && ui->EnablePort2->isChecked()) {
|
||||
ui->EnablePort1->setCheckState(Qt::CheckState::Unchecked);
|
||||
}
|
||||
@ -174,3 +174,24 @@ void SignalgeneratorWidget::setFrequency(double 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:
|
||||
void setLevel(double level);
|
||||
void setFrequency(double frequency);
|
||||
void setPort(int port);
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *) override;
|
||||
|
@ -115,6 +115,8 @@ HEADERS += \
|
||||
mode.h \
|
||||
preferences.h \
|
||||
savable.h \
|
||||
scpi.h \
|
||||
tcpserver.h \
|
||||
touchstone.h \
|
||||
unit.h
|
||||
|
||||
@ -221,6 +223,8 @@ SOURCES += \
|
||||
main.cpp \
|
||||
mode.cpp \
|
||||
preferences.cpp \
|
||||
scpi.cpp \
|
||||
tcpserver.cpp \
|
||||
touchstone.cpp \
|
||||
unit.cpp
|
||||
|
||||
@ -230,7 +234,7 @@ win32:LIBS += -L"$$_PRO_FILE_PWD_" # Github actions placed libusb here
|
||||
osx:INCPATH += /usr/local/include
|
||||
osx:LIBS += $(shell pkg-config --libs libusb-1.0)
|
||||
|
||||
QT += widgets
|
||||
QT += widgets network
|
||||
|
||||
FORMS += \
|
||||
Calibration/addamplitudepointsdialog.ui \
|
||||
|
@ -49,6 +49,7 @@
|
||||
|
||||
VNA::VNA(AppWindow *window)
|
||||
: Mode(window, "Vector Network Analyzer"),
|
||||
SCPINode("VNA"),
|
||||
deembedding(traceModel),
|
||||
central(new TileWidget(traceModel))
|
||||
{
|
||||
@ -58,6 +59,8 @@ VNA::VNA(AppWindow *window)
|
||||
calDialog.reset();
|
||||
calEdited = false;
|
||||
|
||||
SetupSCPI();
|
||||
|
||||
// Create default traces
|
||||
auto tS11 = new Trace("S11", Qt::yellow);
|
||||
tS11->fromLivedata(Trace::LivedataType::Overwrite, Trace::LiveParameter::S11);
|
||||
@ -875,6 +878,124 @@ void VNA::StartCalibrationMeasurement(Calibration::Measurement m)
|
||||
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()
|
||||
{
|
||||
auto pref = Preferences::getInstance();
|
||||
|
@ -9,8 +9,9 @@
|
||||
#include "Device/device.h"
|
||||
#include <functional>
|
||||
#include "Deembedding/deembedding.h"
|
||||
#include "scpi.h"
|
||||
|
||||
class VNA : public Mode
|
||||
class VNA : public Mode, public SCPINode
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@ -50,6 +51,7 @@ signals:
|
||||
void CalibrationMeasurementComplete(Calibration::Measurement m);
|
||||
|
||||
private:
|
||||
void SetupSCPI();
|
||||
void UpdateAverageCount();
|
||||
void SettingsChanged(std::function<void (Device::TransmissionResult)> cb = nullptr);
|
||||
void ConstrainAndUpdateFrequencies();
|
||||
|
@ -48,23 +48,106 @@
|
||||
#include "Calibration/receivercaldialog.h"
|
||||
#include <QDebug>
|
||||
#include "CustomWidgets/jsonpickerdialog.h"
|
||||
#include <QCommandLineParser>
|
||||
|
||||
using namespace std;
|
||||
|
||||
AppWindow::AppWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, deviceActionGroup(new QActionGroup(this))
|
||||
, ui(new Ui::MainWindow)
|
||||
, server(nullptr)
|
||||
{
|
||||
QCoreApplication::setOrganizationName("LibreVNA");
|
||||
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}");
|
||||
|
||||
// qDebug().setVerbosity(0);
|
||||
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();
|
||||
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->statusbar->addWidget(&lConnectionStatus);
|
||||
auto div1 = new QFrame;
|
||||
@ -112,6 +195,9 @@ AppWindow::AppWindow(QWidget *parent)
|
||||
generator = new Generator(this);
|
||||
spectrumAnalyzer = new SpectrumAnalyzer(this);
|
||||
|
||||
scpi.add(vna);
|
||||
scpi.add(generator);
|
||||
|
||||
// UI connections
|
||||
connect(ui->actionUpdate_Device_List, &QAction::triggered, this, &AppWindow::UpdateDeviceList);
|
||||
connect(ui->actionDisconnect, &QAction::triggered, this, &AppWindow::DisconnectDevice);
|
||||
@ -158,11 +244,8 @@ AppWindow::AppWindow(QWidget *parent)
|
||||
// TraceXYPlot::updateGraphColors();
|
||||
});
|
||||
connect(ui->actionAbout, &QAction::triggered, [=](){
|
||||
auto commit = QString(GITHASH);
|
||||
commit.truncate(7);
|
||||
QMessageBox::about(this, "About", "More information: github.com/jankae/LibreVNA\n"
|
||||
"\nVersion: " + QString::number(FW_MAJOR) + "." + QString::number(FW_MINOR)
|
||||
+ "." + QString::number(FW_PATCH) + FW_SUFFIX + " ("+ commit+")");
|
||||
"\nVersion: " + QCoreApplication::applicationVersion());
|
||||
});
|
||||
|
||||
setWindowTitle("LibreVNA-GUI");
|
||||
@ -190,11 +273,16 @@ AppWindow::AppWindow(QWidget *parent)
|
||||
// at least one device available
|
||||
ConnectToDevice();
|
||||
}
|
||||
if(!parser.isSet("no-gui")) {
|
||||
resize(1280, 800);
|
||||
show();
|
||||
}
|
||||
}
|
||||
|
||||
AppWindow::~AppWindow()
|
||||
{
|
||||
delete ui;
|
||||
delete server;
|
||||
}
|
||||
|
||||
void AppWindow::closeEvent(QCloseEvent *event)
|
||||
@ -213,7 +301,7 @@ void AppWindow::closeEvent(QCloseEvent *event)
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
|
||||
void AppWindow::ConnectToDevice(QString serial)
|
||||
bool AppWindow::ConnectToDevice(QString serial)
|
||||
{
|
||||
if(serial.isEmpty()) {
|
||||
qDebug() << "Trying to connect to any device";
|
||||
@ -256,10 +344,12 @@ void AppWindow::ConnectToDevice(QString serial)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (const runtime_error &e) {
|
||||
qWarning() << "Failed to connect:" << e.what();
|
||||
DisconnectDevice();
|
||||
UpdateDeviceList();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,8 +412,13 @@ int AppWindow::UpdateDeviceList()
|
||||
if(device) {
|
||||
devices.insert(device->serial());
|
||||
}
|
||||
int available = 0;
|
||||
if(devices.size()) {
|
||||
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);
|
||||
connectAction->setCheckable(true);
|
||||
connectAction->setActionGroup(deviceActionGroup);
|
||||
@ -333,14 +428,15 @@ int AppWindow::UpdateDeviceList()
|
||||
connect(connectAction, &QAction::triggered, [this, d]() {
|
||||
ConnectToDevice(d);
|
||||
});
|
||||
ui->menuConnect_to->setEnabled(true);
|
||||
available++;
|
||||
}
|
||||
ui->menuConnect_to->setEnabled(true);
|
||||
} else {
|
||||
// no devices available, disable connection option
|
||||
ui->menuConnect_to->setEnabled(false);
|
||||
}
|
||||
qDebug() << "Updated device list, found" << devices.size();
|
||||
return devices.size();
|
||||
qDebug() << "Updated device list, found" << available;
|
||||
return available;
|
||||
}
|
||||
|
||||
void AppWindow::StartManualControl()
|
||||
|
@ -18,6 +18,9 @@
|
||||
#include <QButtonGroup>
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QCommandLineParser>
|
||||
#include "scpi.h"
|
||||
#include "tcpserver.h"
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
@ -41,7 +44,7 @@ public:
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
private slots:
|
||||
void ConnectToDevice(QString serial = QString());
|
||||
bool ConnectToDevice(QString serial = QString());
|
||||
void DisconnectDevice();
|
||||
int UpdateDeviceList();
|
||||
void StartManualControl();
|
||||
@ -84,6 +87,10 @@ private:
|
||||
QLabel lUnlock;
|
||||
|
||||
Ui::MainWindow *ui;
|
||||
QCommandLineParser parser;
|
||||
|
||||
SCPI scpi;
|
||||
TCPServer *server;
|
||||
};
|
||||
|
||||
#endif // VNA_H
|
||||
|
@ -7,12 +7,22 @@
|
||||
#include "Calibration/calkit.h"
|
||||
#include "touchstone.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <complex>
|
||||
|
||||
static QApplication *app;
|
||||
static AppWindow *window;
|
||||
|
||||
void sig_handler(int s) {
|
||||
Q_UNUSED(s)
|
||||
window->close();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QApplication a(argc, argv);
|
||||
AppWindow vna;
|
||||
vna.resize(1280, 800);
|
||||
vna.show();
|
||||
a.exec();
|
||||
app = new QApplication(argc, argv);
|
||||
window = new AppWindow;
|
||||
signal(SIGINT, sig_handler);
|
||||
app->exec();
|
||||
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