Save/load trace and graph setup

This commit is contained in:
Jan Käberich 2020-12-04 23:49:52 +01:00
parent b91f431473
commit 9ad8def2ea
33 changed files with 605 additions and 28 deletions

View File

@ -5,7 +5,6 @@ HEADERS += \
Calibration/calibrationtracedialog.h \
Calibration/calkit.h \
Calibration/calkitdialog.h \
Calibration/json.hpp \
Calibration/measurementmodel.h \
Calibration/receivercaldialog.h \
Calibration/sourcecaldialog.h \
@ -51,8 +50,10 @@ HEADERS += \
VNA/vna.h \
appwindow.h \
averaging.h \
json.hpp \
mode.h \
preferences.h \
savable.h \
touchstone.h \
unit.h

View File

@ -679,7 +679,7 @@ bool Calibration::openFromFile(QString filename)
return false;
}
}
qDebug() << "Attempting to open calibration from file" << filename;
qDebug() << "Attempting to open calibration from file" << filename;
// reset all data before loading new calibration
clearMeasurements();

View File

@ -28,6 +28,73 @@ TileWidget::~TileWidget()
delete ui;
}
void TileWidget::clear()
{
if(hasContent) {
delete content;
hasContent = false;
}
if(isSplit) {
delete child1;
delete child2;
isSplit = false;
delete splitter;
}
}
nlohmann::json TileWidget::toJSON()
{
nlohmann::json j;
j["split"] = isSplit;
if(isSplit) {
j["orientation"] = splitter->orientation() == Qt::Horizontal ? "horizontal" : "vertical";
j["sizes"] = splitter->sizes();
j["tile1"] = child1->toJSON();
j["tile2"] = child2->toJSON();
}
if(hasContent) {
std::string plotname;
switch(content->getType()) {
case TracePlot::Type::SmithChart:
plotname = "smithchart";
break;
case TracePlot::Type::XYPlot:
plotname = "XY-plot";
break;
}
j["plot"] = plotname;
j["plotsettings"] = content->toJSON();
}
return j;
}
void TileWidget::fromJSON(nlohmann::json j)
{
// delete all childs before parsing json
clear();
bool split = j.value("split", false);
if(split) {
if(j["orientation"] == "horizontal") {
splitHorizontally();
} else {
splitVertically();
}
splitter->setSizes(j["sizes"]);
child1->fromJSON(j["tile1"]);
child2->fromJSON(j["tile2"]);
} else if(j.contains("plot")) {
// has a plot enabled
auto plotname = j["plot"];
if(plotname == "smithchart") {
content = new TraceSmithChart(model);
} else {
content = new TraceXYPlot(model);
}
setContent(content);
content->fromJSON(j["plotsettings"]);
}
}
void TileWidget::splitVertically()
{
if(isSplit) {

View File

@ -5,12 +5,13 @@
#include "Traces/traceplot.h"
#include <QSplitter>
#include "Traces/tracemodel.h"
#include "savable.h"
namespace Ui {
class TileWidget;
}
class TileWidget : public QWidget
class TileWidget : public QWidget, public Savable
{
Q_OBJECT
@ -20,6 +21,12 @@ public:
TileWidget *Child1() { return child1; };
TileWidget *Child2() { return child2; };
// closes all plots/childs, leaving only the tilewidget at the top
void clear();
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
public slots:
void splitVertically();
void splitHorizontally();

View File

@ -10,6 +10,10 @@ public:
Generator(AppWindow *window);
void deactivate() override;
void initializeDevice() override;
// Nothing to do for now
virtual nlohmann::json toJSON() override {return nlohmann::json();};
virtual void fromJSON(nlohmann::json j) override {Q_UNUSED(j)};
private slots:
void updateDevice();
private:

View File

@ -293,6 +293,24 @@ void SpectrumAnalyzer::initializeDevice()
window->getDevice()->Configure(settings);
}
nlohmann::json SpectrumAnalyzer::toJSON()
{
nlohmann::json j;
j["traces"] = traceModel.toJSON();
j["tiles"] = central->toJSON();
return j;
}
void SpectrumAnalyzer::fromJSON(nlohmann::json j)
{
if(j.contains("traces")) {
traceModel.fromJSON(j["traces"]);
}
if(j.contains("tiles")) {
central->fromJSON(j["tiles"]);
}
}
using namespace std;
void SpectrumAnalyzer::NewDatapoint(Protocol::SpectrumAnalyzerResult d)

View File

@ -17,6 +17,11 @@ public:
void deactivate() override;
void initializeDevice() override;
// Only save/load user changeable stuff, no need to save the widgets/mode name etc.
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
private slots:
void NewDatapoint(Protocol::SpectrumAnalyzerResult d);
void StartImpedanceMatching();

View File

View File

@ -55,6 +55,20 @@ QWidget *MedianFilter::createExplanationWidget()
return w;
}
nlohmann::json MedianFilter::toJSON()
{
nlohmann::json j;
j["kernel"] = kernelSize;
j["order"] = order;
return j;
}
void MedianFilter::fromJSON(nlohmann::json j)
{
kernelSize = j.value("kernel", 3);
order = j.value("order", Order::AbsoluteValue);
}
void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
if(data.size() != input->rData().size()) {
data.resize(input->rData().size());

View File

@ -17,6 +17,10 @@ public:
static QWidget *createExplanationWidget();
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
Type getType() override {return Type::MedianFilter;};
public slots:
// a single value of the input data has changed, index determines which sample has changed
virtual void inputSamplesChanged(unsigned int begin, unsigned int end) override;

View File

@ -111,6 +111,44 @@ QWidget *TDR::createExplanationWidget()
return new QLabel("Test");
}
nlohmann::json TDR::toJSON()
{
nlohmann::json j;
j["bandpass_mode"] = mode == Mode::Bandpass;
j["window"] = window.toJSON();
if(mode == Mode::Lowpass) {
j["step_response"] = stepResponse;
if(stepResponse) {
j["automatic_DC"] = automaticDC;
if(!automaticDC) {
j["manual_DC_real"] = manualDC.real();
j["manual_DC_imag"] = manualDC.imag();
}
}
}
return j;
}
void TDR::fromJSON(nlohmann::json j)
{
if(j.value("bandpass_mode", true)) {
mode = Mode::Bandpass;
} else {
mode = Mode::Lowpass;
if(j.value("step_response", true)) {
stepResponse = true;
if(j.value("automatic_DC", true)) {
automaticDC = true;
} else {
automaticDC = false;
manualDC = complex<double>(j.value("manual_DC_real", 1.0), j.value("manual_DC_imag", 0.0));
}
} else {
stepResponse = false;
}
}
}
void TDR::inputSamplesChanged(unsigned int begin, unsigned int end)
{
Q_UNUSED(begin);

View File

@ -17,6 +17,10 @@ public:
static QWidget* createExplanationWidget();
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
Type getType() override {return Type::TDR;};
public slots:
void inputSamplesChanged(unsigned int begin, unsigned int end) override;

View File

@ -4,6 +4,7 @@
#include <QObject>
#include <vector>
#include <complex>
#include "savable.h"
/*
* How to implement a new type of math operation:
@ -34,6 +35,8 @@
* Provide a hint by passing a short description string
* error(): something went wrong (called with wrong type of data, mathematical error, ...).
* Provide a hint by passing a short description string
* e. getType(): return the type of the operation
* f. toJSON() and fromJSON(). Save/load all internal parameters
* 3. Add a new type to the Type enum for your operation
* 4. Extend the createMath(Type type) factory function to create an instance of your operation
* 5. Add a static function "createExplanationWidget" which returns a QWidget explaining what your operation does.
@ -43,7 +46,7 @@
class Trace;
class TraceMath : public QObject {
class TraceMath : public QObject, public Savable {
Q_OBJECT
public:
TraceMath();
@ -98,6 +101,7 @@ public:
std::vector<Data>& rData() { return data;};
Status getStatus() const;
QString getStatusDescription() const;
virtual Type getType() = 0;
// returns the trace this math operation is attached to
Trace* root();

View File

@ -5,6 +5,7 @@
#include <QLabel>
#include <QFormLayout>
#include "CustomWidgets/siunitedit.h"
#include <QDebug>
QString WindowFunction::typeToName(WindowFunction::Type type)
{
@ -108,6 +109,45 @@ QString WindowFunction::getDescription()
return ret;
}
nlohmann::json WindowFunction::toJSON()
{
nlohmann::json j;
j["type"] = typeToName(type).toStdString();
// add additional parameter if type has one
switch(type) {
case Type::Gaussian:
j["sigma"] = gaussian_sigma;
break;
default:
break;
}
return j;
}
void WindowFunction::fromJSON(nlohmann::json j)
{
qDebug() << "Setting window function from json";
QString typeName = QString::fromStdString(j["type"]);
unsigned int i=0;
for(;i<(int) Type::Last;i++) {
if(typeToName((Type) i) == typeName) {
type = Type(i);
break;
}
}
if(i>=(int) Type::Last) {
qWarning() << "Invalid window type specified, defaulting to hamming";
type = Type::Hamming;
}
switch(type) {
case Type::Gaussian:
gaussian_sigma = j.value("sigma", 0.4);
break;
default:
break;
}
}
double WindowFunction::getFactor(unsigned int n, unsigned int N)
{
// all formulas from https://en.wikipedia.org/wiki/Window_function

View File

@ -4,8 +4,9 @@
#include <QWidget>
#include <complex>
#include <vector>
#include "savable.h"
class WindowFunction : public QObject
class WindowFunction : public QObject, public Savable
{
Q_OBJECT;
public:
@ -31,6 +32,9 @@ public:
Type getType() const;
QString getDescription();
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
signals:
void changed();

View File

@ -1,6 +1,8 @@
#include "trace.h"
#include <math.h>
#include "fftcomplex.h"
#include <QDebug>
#include <functional>
using namespace std;
@ -193,6 +195,112 @@ double Trace::distanceToTime(double distance)
return time;
}
nlohmann::json Trace::toJSON()
{
nlohmann::json j;
if(isCalibration()) {
// calibration traces can't be saved
return j;
}
j["name"] = _name.toStdString();
j["color"] = _color.name().toStdString();
j["visible"] = visible;
if(isLive()) {
j["type"] = "Live";
j["parameter"] = _liveParam;
j["livetype"] = _liveType;
j["paused"] = paused;
} else if(isTouchstone()) {
j["type"] = "Touchstone";
j["filename"] = touchstoneFilename.toStdString();
j["parameter"] = touchstoneParameter;
}
j["reflection"] = reflection;
// TODO how to save assigned markers?
nlohmann::json mathList;
for(auto m : mathOps) {
if(m.math->getType() == Type::Last) {
// this is an invalid type reserved for the trace itself, skip
continue;
}
nlohmann::json jm;
auto info = TraceMath::getInfo(m.math->getType());
jm["operation"] = info.name.toStdString();
jm["enabled"] = m.enabled;
jm["settings"] = m.math->toJSON();
mathList.push_back(jm);
}
j["math"] = mathList;
j["math_enabled"] = mathEnabled();
return j;
}
void Trace::fromJSON(nlohmann::json j)
{
touchstone = false;
calibration = false;
_name = QString::fromStdString(j.value("name", "Missing name"));
_color = QColor(QString::fromStdString(j.value("color", "yellow")));
visible = j.value("visible", true);
auto type = QString::fromStdString(j.value("type", "Live"));
if(type == "Live") {
_liveParam = j.value("parameter", LiveParameter::S11);
_liveType = j.value("livetype", LivedataType::Overwrite);
paused = j.value("paused", false);
} else if(type == "Touchstone") {
std::string filename = j.value("filename", "");
touchstoneParameter = j.value("parameter", 0);
try {
Touchstone t = Touchstone::fromFile(filename);
fillFromTouchstone(t, touchstoneParameter, QString::fromStdString(filename));
} catch (const exception &e) {
std::string what = e.what();
throw runtime_error("Failed to create touchstone:" + what);
}
}
reflection = j.value("reflection", false);
for(auto jm : j["math"]) {
QString operation = QString::fromStdString(jm.value("operation", ""));
if(operation.isEmpty()) {
qWarning() << "Skipping empty math operation";
continue;
}
// attempt to find the type of operation
TraceMath::Type type = Type::Last;
for(unsigned int i=0;i<(int) Type::Last;i++) {
auto info = TraceMath::getInfo((Type) i);
if(info.name == operation) {
// found the correct operation
type = (Type) i;
break;
}
}
if(type == Type::Last) {
// unable to find this operation
qWarning() << "Unable to create math operation:" << operation;
continue;
}
qDebug() << "Creating math operation of type:" << operation;
auto op = TraceMath::createMath(type);
MathInfo info;
info.enabled = jm.value("enabled", true);
info.math = op;
op->assignInput(lastMath);
mathOps.push_back(info);
updateLastMath(mathOps.rbegin());
}
enableMath(j.value("math_enabled", true));
}
unsigned int Trace::toHash()
{
// taking the easy way: create the json string and hash it (already contains all necessary information)
// This is slower than it could be, but this function is only used when loading setups, so this isn't a big problem
std::string json_string = toJSON().dump();
return hash<std::string>{}(json_string);
}
void Trace::updateLastMath(vector<MathInfo>::reverse_iterator start)
{
TraceMath *newLast = nullptr;

View File

@ -108,6 +108,17 @@ public:
double timeToDistance(double time);
double distanceToTime(double distance);
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
Type getType() override {return Type::Last;}; // can return invalid type, this will never be called
// Traces are referenced by pointers throughout this project (e.g. when added to a graph)
// When saving the current graph configuration, the pointer is not useful. Instead a trace
// hash is saved to identify the correct trace. The hash should be influenced by every setting
// the trace can have (and its math function). It should not depend on the acquired trace samples
unsigned int toHash();
public slots:
void setTouchstoneParameter(int value);
void setTouchstoneFilename(const QString &value);

View File

@ -1,5 +1,6 @@
#include "tracemodel.h"
#include <QIcon>
#include <QDebug>
using namespace std;
@ -164,6 +165,32 @@ bool TraceModel::PortExcitationRequired(int port)
return false;
}
nlohmann::json TraceModel::toJSON()
{
nlohmann::json j;
for(auto t : traces) {
j.push_back(t->toJSON());
}
return j;
}
void TraceModel::fromJSON(nlohmann::json j)
{
// clear old traces
while(traces.size()) {
removeTrace(0);
}
for(auto jt : j) {
auto trace = new Trace();
try {
trace->fromJSON(jt);
addTrace(trace);
} catch (const exception &e) {
qWarning() << "Failed to create trace:" << e.what();
}
}
}
void TraceModel::clearVNAData()
{
for(auto t : traces) {

View File

@ -5,8 +5,9 @@
#include "trace.h"
#include <vector>
#include "Device/device.h"
#include "savable.h"
class TraceModel : public QAbstractTableModel
class TraceModel : public QAbstractTableModel, public Savable
{
Q_OBJECT
public:
@ -36,6 +37,9 @@ public:
bool PortExcitationRequired(int port);
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
signals:
void SpanChanged(double fmin, double fmax);
void traceAdded(Trace *t);

View File

@ -7,17 +7,24 @@
#include <QContextMenuEvent>
#include <QTime>
#include <QLabel>
#include "savable.h"
class TracePlot : public QWidget
class TracePlot : public QWidget, public Savable
{
Q_OBJECT
public:
enum class Type {
SmithChart,
XYPlot,
};
TracePlot(TraceModel &model, QWidget *parent = nullptr);
~TracePlot();
virtual void enableTrace(Trace *t, bool enabled);
void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void updateSpan(double min, double max);
virtual Type getType() = 0;
static std::set<TracePlot *> getPlots();

View File

@ -13,15 +13,43 @@ using namespace std;
TraceSmithChart::TraceSmithChart(TraceModel &model, QWidget *parent)
: TracePlot(model, parent)
{
chartLinesPen = QPen(palette().windowText(), 0.75);
thinPen = QPen(palette().windowText(), 0.25);
textPen = QPen(palette().windowText(), 0.25);
pointDataPen = QPen(QColor("red"), 4.0, Qt::SolidLine, Qt::RoundCap);
lineDataPen = QPen(QColor("blue"), 1.0);
limitToSpan = true;
initializeTraceInfo();
}
nlohmann::json TraceSmithChart::toJSON()
{
nlohmann::json j;
j["limit_to_span"] = limitToSpan;
nlohmann::json jtraces;
for(auto t : traces) {
if(t.second) {
jtraces.push_back(t.first->toHash());
}
}
j["traces"] = jtraces;
return j;
}
void TraceSmithChart::fromJSON(nlohmann::json j)
{
limitToSpan = j.value("limit_to_span", true);
for(unsigned int hash : j["traces"]) {
// attempt to find the traces with this hash
bool found = false;
for(auto t : model.getTraces()) {
if(t->toHash() == hash) {
enableTrace(t, true);
found = true;
break;
}
}
if(!found) {
qWarning() << "Unable to find trace with hash" << hash;
}
}
}
void TraceSmithChart::axisSetupDialog()
{
auto dialog = new QDialog();

View File

@ -11,6 +11,11 @@ class TraceSmithChart : public TracePlot
Q_OBJECT
public:
TraceSmithChart(TraceModel &model, QWidget *parent = 0);
virtual Type getType() override { return Type::SmithChart;};
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
public slots:
void axisSetupDialog();
protected:
@ -29,18 +34,7 @@ protected:
virtual void draw(QPainter& painter) override;
virtual void traceDropped(Trace *t, QPoint position) override;
QString mouseText(QPoint pos) override;
QPen textPen;
QPen chartLinesPen;
QPen thinPen;
QPen pointDataPen;
QPen lineDataPen;
bool limitToSpan;
/// Path for the thin arcs
QPainterPath thinArcsPath;
/// Path for the thick arcs
QPainterPath thickArcsPath;
QTransform transform;
};

View File

@ -112,6 +112,76 @@ void TraceXYPlot::replot()
TracePlot::replot();
}
nlohmann::json TraceXYPlot::toJSON()
{
nlohmann::json j;
nlohmann::json jX;
jX["type"] = XAxis.type;
jX["mode"] = XAxis.mode;
jX["log"] = XAxis.log;
jX["min"] = XAxis.rangeMin;
jX["max"] = XAxis.rangeMax;
jX["div"] = XAxis.rangeDiv;
j["XAxis"] = jX;
for(unsigned int i=0;i<2;i++) {
nlohmann::json jY;
jY["type"] = YAxis[i].type;
jY["log"] = YAxis[i].log;
jY["autorange"] = YAxis[i].autorange;
jY["min"] = YAxis[i].rangeMin;
jY["max"] = YAxis[i].rangeMax;
jY["div"] = YAxis[i].rangeDiv;
nlohmann::json jtraces;
for(auto t : tracesAxis[i]) {
jtraces.push_back(t->toHash());
}
jY["traces"] = jtraces;
if(i==0) {
j["YPrimary"] = jY;
} else {
j["YSecondary"] = jY;
}
}
return j;
}
void TraceXYPlot::fromJSON(nlohmann::json j)
{
auto jX = j["XAxis"];
auto xtype = jX.value("type", XAxisType::Frequency);
auto xmode = jX.value("mode", XAxisMode::UseSpan);
// auto xlog = jX.value("log", false);
auto xmin = jX.value("min", 0);
auto xmax = jX.value("max", 6000000000);
auto xdiv = jX.value("div", 600000000);
setXAxis(xtype, xmode, xmin, xmax, xdiv);
nlohmann::json jY[2] = {j["YPrimary"], j["YSecondary"]};
for(unsigned int i=0;i<2;i++) {
auto ytype = jY[i].value("type", YAxisType::Disabled);
auto yauto = jY[i].value("autorange", true);
auto ylog = jY[i].value("log", false);
auto ymin = jY[i].value("min", -120);
auto ymax = jY[i].value("max", 20);
auto ydiv = jY[i].value("div", 10);
setYAxis(i, ytype, ylog, yauto, ymin, ymax, ydiv);
for(unsigned int hash : jY[i]["traces"]) {
// attempt to find the traces with this hash
bool found = false;
for(auto t : model.getTraces()) {
if(t->toHash() == hash) {
enableTraceAxis(t, i, true);
found = true;
break;
}
}
if(!found) {
qWarning() << "Unable to find trace with hash" << hash;
}
}
}
}
bool TraceXYPlot::isTDRtype(TraceXYPlot::YAxisType type)
{
switch(type) {

View File

@ -41,6 +41,10 @@ public:
void updateSpan(double min, double max) override;
void replot() override;
virtual Type getType() override { return Type::XYPlot;};
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
bool isTDRtype(YAxisType type);
public slots:

View File

@ -443,6 +443,24 @@ void VNA::deviceDisconnected()
defaultCalMenu->setEnabled(false);
}
nlohmann::json VNA::toJSON()
{
nlohmann::json j;
j["traces"] = traceModel.toJSON();
j["tiles"] = central->toJSON();
return j;
}
void VNA::fromJSON(nlohmann::json j)
{
if(j.contains("traces")) {
traceModel.fromJSON(j["traces"]);
}
if(j.contains("tiles")) {
central->fromJSON(j["tiles"]);
}
}
using namespace std;
void VNA::NewDatapoint(Protocol::Datapoint d)

View File

@ -19,6 +19,10 @@ public:
void deactivate() override;
void initializeDevice() override;
void deviceDisconnected() override;
// Only save/load user changeable stuff, no need to save the widgets/mode name etc.
virtual nlohmann::json toJSON() override;
virtual void fromJSON(nlohmann::json j) override;
private slots:
void NewDatapoint(Protocol::Datapoint d);
void StartImpedanceMatching();

View File

@ -93,14 +93,46 @@ AppWindow::AppWindow(QWidget *parent)
// Create GUI modes
central = new QStackedWidget;
setCentralWidget(central);
auto vna = new VNA(this);
new Generator(this);
new SpectrumAnalyzer(this);
vna = new VNA(this);
generator = new Generator(this);
spectrumAnalyzer = new SpectrumAnalyzer(this);
// UI connections
connect(ui->actionUpdate_Device_List, &QAction::triggered, this, &AppWindow::UpdateDeviceList);
connect(ui->actionDisconnect, &QAction::triggered, this, &AppWindow::DisconnectDevice);
connect(ui->actionQuit, &QAction::triggered, this, &AppWindow::close);
connect(ui->actionSave_setup, &QAction::triggered, [=](){
auto filename = QFileDialog::getSaveFileName(nullptr, "Save setup data", "", "Setup files (*.setup)", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.isEmpty()) {
// aborted selection
return;
}
if(!filename.endsWith(".setup")) {
filename.append(".setup");
}
ofstream file;
file.open(filename.toStdString());
file << setw(4) << SaveSetup() << endl;
file.close();
});
connect(ui->actionLoad_setup, &QAction::triggered, [=](){
auto filename = QFileDialog::getOpenFileName(nullptr, "Load setup data", "", "Setup files (*.setup)", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.isEmpty()) {
// aborted selection
return;
}
ifstream file;
file.open(filename.toStdString());
if(!file.is_open()) {
qWarning() << "Unable to open file:" << filename;
return;
}
nlohmann::json j;
file >> j;
file.close();
LoadSetup(j);
});
connect(ui->actionManual_Control, &QAction::triggered, this, &AppWindow::StartManualControl);
connect(ui->actionFirmware_Update, &QAction::triggered, this, &AppWindow::StartFirmwareUpdateDialog);
connect(ui->actionSource_Calibration, &QAction::triggered, this, &AppWindow::SourceCalibrationDialog);
@ -373,6 +405,22 @@ void AppWindow::ReceiverCalibrationDialog()
d->exec();
}
nlohmann::json AppWindow::SaveSetup()
{
nlohmann::json j;
j["VNA"] = vna->toJSON();
j["Generator"] = generator->toJSON();
j["SpectrumAnalyzer"] = spectrumAnalyzer->toJSON();
return j;
}
void AppWindow::LoadSetup(nlohmann::json j)
{
vna->fromJSON(j["VNA"]);
generator->fromJSON(j["Generator"]);
spectrumAnalyzer->fromJSON(j["SpectrumAnalyzer"]);
}
Device *AppWindow::getDevice() const
{
return device;

View File

@ -23,6 +23,10 @@ namespace Ui {
class MainWindow;
}
class VNA;
class Generator;
class SpectrumAnalyzer;
class AppWindow : public QMainWindow
{
Q_OBJECT
@ -46,6 +50,8 @@ private slots:
void DeviceNeedsUpdate(int reported, int expected);
void SourceCalibrationDialog();
void ReceiverCalibrationDialog();
nlohmann::json SaveSetup();
void LoadSetup(nlohmann::json j);
private:
void DeviceConnectionLost();
void CreateToolbars();
@ -66,6 +72,11 @@ private:
QString deviceSerial;
QActionGroup *deviceActionGroup;
// Modes
VNA *vna;
Generator *generator;
SpectrumAnalyzer *spectrumAnalyzer;
// Status bar widgets
QLabel lConnectionStatus;
QLabel lDeviceInfo;

View File

@ -27,6 +27,8 @@
<property name="title">
<string>File</string>
</property>
<addaction name="actionSave_setup"/>
<addaction name="actionLoad_setup"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuDevice">
@ -170,6 +172,24 @@
<string>Receiver Calibration</string>
</property>
</action>
<action name="actionSave_setup">
<property name="icon">
<iconset theme="document-save" resource="icons.qrc">
<normaloff>:/icons/save.png</normaloff>:/icons/save.png</iconset>
</property>
<property name="text">
<string>Save setup</string>
</property>
</action>
<action name="actionLoad_setup">
<property name="icon">
<iconset theme="document-open" resource="icons.qrc">
<normaloff>:/icons/open.png</normaloff>:/icons/open.png</iconset>
</property>
<property name="text">
<string>Load setup</string>
</property>
</action>
</widget>
<resources>
<include location="icons.qrc"/>

View File

@ -8,8 +8,9 @@
#include <QDockWidget>
#include <set>
#include "appwindow.h"
#include "savable.h"
class Mode : public QObject
class Mode : public QObject, public Savable
{
public:
Mode(AppWindow *window, QString name);

View File

@ -0,0 +1,12 @@
#ifndef SAVABLE_H
#define SAVABLE_H
#include "json.hpp"
class Savable {
public:
virtual nlohmann::json toJSON() = 0;
virtual void fromJSON(nlohmann::json j) = 0;
};
#endif // SAVABLE_H

View File

@ -126,7 +126,7 @@ Touchstone Touchstone::fromFile(string filename)
file.open(filename);
if(!file.is_open()) {
throw runtime_error("Unable to open file");
throw runtime_error("Unable to open file:" + filename);
}
// extract number of ports from filename