From 9ad8def2eacbbed47ff5fb99b980fc2a00f96845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Fri, 4 Dec 2020 23:49:52 +0100 Subject: [PATCH] Save/load trace and graph setup --- Software/PC_Application/Application.pro | 3 +- .../Calibration/calibration.cpp | 2 +- .../CustomWidgets/tilewidget.cpp | 67 +++++++++++ .../PC_Application/CustomWidgets/tilewidget.h | 9 +- Software/PC_Application/Generator/generator.h | 4 + .../SpectrumAnalyzer/spectrumanalyzer.cpp | 18 +++ .../SpectrumAnalyzer/spectrumanalyzer.h | 5 + Software/PC_Application/TraceSetup | 0 .../Traces/Math/medianfilter.cpp | 14 +++ .../PC_Application/Traces/Math/medianfilter.h | 4 + Software/PC_Application/Traces/Math/tdr.cpp | 38 ++++++ Software/PC_Application/Traces/Math/tdr.h | 4 + .../PC_Application/Traces/Math/tracemath.h | 6 +- .../Traces/Math/windowfunction.cpp | 40 +++++++ .../Traces/Math/windowfunction.h | 6 +- Software/PC_Application/Traces/trace.cpp | 108 ++++++++++++++++++ Software/PC_Application/Traces/trace.h | 11 ++ Software/PC_Application/Traces/tracemodel.cpp | 27 +++++ Software/PC_Application/Traces/tracemodel.h | 6 +- Software/PC_Application/Traces/traceplot.h | 9 +- .../PC_Application/Traces/tracesmithchart.cpp | 38 +++++- .../PC_Application/Traces/tracesmithchart.h | 16 +-- .../PC_Application/Traces/tracexyplot.cpp | 70 ++++++++++++ Software/PC_Application/Traces/tracexyplot.h | 4 + Software/PC_Application/VNA/vna.cpp | 18 +++ Software/PC_Application/VNA/vna.h | 4 + Software/PC_Application/appwindow.cpp | 54 ++++++++- Software/PC_Application/appwindow.h | 11 ++ .../PC_Application/{Calibration => }/json.hpp | 0 Software/PC_Application/main.ui | 20 ++++ Software/PC_Application/mode.h | 3 +- Software/PC_Application/savable.h | 12 ++ Software/PC_Application/touchstone.cpp | 2 +- 33 files changed, 605 insertions(+), 28 deletions(-) create mode 100644 Software/PC_Application/TraceSetup rename Software/PC_Application/{Calibration => }/json.hpp (100%) create mode 100644 Software/PC_Application/savable.h diff --git a/Software/PC_Application/Application.pro b/Software/PC_Application/Application.pro index 110195a..60a7d7c 100644 --- a/Software/PC_Application/Application.pro +++ b/Software/PC_Application/Application.pro @@ -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 diff --git a/Software/PC_Application/Calibration/calibration.cpp b/Software/PC_Application/Calibration/calibration.cpp index 3b27a6f..310f850 100644 --- a/Software/PC_Application/Calibration/calibration.cpp +++ b/Software/PC_Application/Calibration/calibration.cpp @@ -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(); diff --git a/Software/PC_Application/CustomWidgets/tilewidget.cpp b/Software/PC_Application/CustomWidgets/tilewidget.cpp index 0f1dc12..bb4c02f 100644 --- a/Software/PC_Application/CustomWidgets/tilewidget.cpp +++ b/Software/PC_Application/CustomWidgets/tilewidget.cpp @@ -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) { diff --git a/Software/PC_Application/CustomWidgets/tilewidget.h b/Software/PC_Application/CustomWidgets/tilewidget.h index 8dde7be..357974e 100644 --- a/Software/PC_Application/CustomWidgets/tilewidget.h +++ b/Software/PC_Application/CustomWidgets/tilewidget.h @@ -5,12 +5,13 @@ #include "Traces/traceplot.h" #include #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(); diff --git a/Software/PC_Application/Generator/generator.h b/Software/PC_Application/Generator/generator.h index b884b89..2c41fce 100644 --- a/Software/PC_Application/Generator/generator.h +++ b/Software/PC_Application/Generator/generator.h @@ -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: diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp index c401a22..775d312 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -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) diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h index c43fdfa..406e011 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.h @@ -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(); diff --git a/Software/PC_Application/TraceSetup b/Software/PC_Application/TraceSetup new file mode 100644 index 0000000..e69de29 diff --git a/Software/PC_Application/Traces/Math/medianfilter.cpp b/Software/PC_Application/Traces/Math/medianfilter.cpp index 0b21a7d..5f225f2 100644 --- a/Software/PC_Application/Traces/Math/medianfilter.cpp +++ b/Software/PC_Application/Traces/Math/medianfilter.cpp @@ -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()); diff --git a/Software/PC_Application/Traces/Math/medianfilter.h b/Software/PC_Application/Traces/Math/medianfilter.h index 2cbba2f..ddd6d16 100644 --- a/Software/PC_Application/Traces/Math/medianfilter.h +++ b/Software/PC_Application/Traces/Math/medianfilter.h @@ -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; diff --git a/Software/PC_Application/Traces/Math/tdr.cpp b/Software/PC_Application/Traces/Math/tdr.cpp index 006fb86..3fadf63 100644 --- a/Software/PC_Application/Traces/Math/tdr.cpp +++ b/Software/PC_Application/Traces/Math/tdr.cpp @@ -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(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); diff --git a/Software/PC_Application/Traces/Math/tdr.h b/Software/PC_Application/Traces/Math/tdr.h index 5420c3f..2b19715 100644 --- a/Software/PC_Application/Traces/Math/tdr.h +++ b/Software/PC_Application/Traces/Math/tdr.h @@ -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; diff --git a/Software/PC_Application/Traces/Math/tracemath.h b/Software/PC_Application/Traces/Math/tracemath.h index 476b773..a5cb613 100644 --- a/Software/PC_Application/Traces/Math/tracemath.h +++ b/Software/PC_Application/Traces/Math/tracemath.h @@ -4,6 +4,7 @@ #include #include #include +#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& rData() { return data;}; Status getStatus() const; QString getStatusDescription() const; + virtual Type getType() = 0; // returns the trace this math operation is attached to Trace* root(); diff --git a/Software/PC_Application/Traces/Math/windowfunction.cpp b/Software/PC_Application/Traces/Math/windowfunction.cpp index dbcc083..8ae0444 100644 --- a/Software/PC_Application/Traces/Math/windowfunction.cpp +++ b/Software/PC_Application/Traces/Math/windowfunction.cpp @@ -5,6 +5,7 @@ #include #include #include "CustomWidgets/siunitedit.h" +#include 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 diff --git a/Software/PC_Application/Traces/Math/windowfunction.h b/Software/PC_Application/Traces/Math/windowfunction.h index fabec7f..c190922 100644 --- a/Software/PC_Application/Traces/Math/windowfunction.h +++ b/Software/PC_Application/Traces/Math/windowfunction.h @@ -4,8 +4,9 @@ #include #include #include +#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(); diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index 11feb31..8e1c9a2 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -1,6 +1,8 @@ #include "trace.h" #include #include "fftcomplex.h" +#include +#include 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{}(json_string); +} + void Trace::updateLastMath(vector::reverse_iterator start) { TraceMath *newLast = nullptr; diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index 25218b3..d1015a7 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -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); diff --git a/Software/PC_Application/Traces/tracemodel.cpp b/Software/PC_Application/Traces/tracemodel.cpp index 21c8e1f..2e9443b 100644 --- a/Software/PC_Application/Traces/tracemodel.cpp +++ b/Software/PC_Application/Traces/tracemodel.cpp @@ -1,5 +1,6 @@ #include "tracemodel.h" #include +#include 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) { diff --git a/Software/PC_Application/Traces/tracemodel.h b/Software/PC_Application/Traces/tracemodel.h index a25bfe3..f154e5e 100644 --- a/Software/PC_Application/Traces/tracemodel.h +++ b/Software/PC_Application/Traces/tracemodel.h @@ -5,8 +5,9 @@ #include "trace.h" #include #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); diff --git a/Software/PC_Application/Traces/traceplot.h b/Software/PC_Application/Traces/traceplot.h index 3f87962..caa1d63 100644 --- a/Software/PC_Application/Traces/traceplot.h +++ b/Software/PC_Application/Traces/traceplot.h @@ -7,17 +7,24 @@ #include #include #include +#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 getPlots(); diff --git a/Software/PC_Application/Traces/tracesmithchart.cpp b/Software/PC_Application/Traces/tracesmithchart.cpp index 5b79f18..7e04091 100644 --- a/Software/PC_Application/Traces/tracesmithchart.cpp +++ b/Software/PC_Application/Traces/tracesmithchart.cpp @@ -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(); diff --git a/Software/PC_Application/Traces/tracesmithchart.h b/Software/PC_Application/Traces/tracesmithchart.h index 5d96935..e2c01f9 100644 --- a/Software/PC_Application/Traces/tracesmithchart.h +++ b/Software/PC_Application/Traces/tracesmithchart.h @@ -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; }; diff --git a/Software/PC_Application/Traces/tracexyplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp index 5e3ccb1..a3d92d3 100644 --- a/Software/PC_Application/Traces/tracexyplot.cpp +++ b/Software/PC_Application/Traces/tracexyplot.cpp @@ -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) { diff --git a/Software/PC_Application/Traces/tracexyplot.h b/Software/PC_Application/Traces/tracexyplot.h index 027b915..91287e5 100644 --- a/Software/PC_Application/Traces/tracexyplot.h +++ b/Software/PC_Application/Traces/tracexyplot.h @@ -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: diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index 96ab4fb..efeceb7 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -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) diff --git a/Software/PC_Application/VNA/vna.h b/Software/PC_Application/VNA/vna.h index e78d755..f9db043 100644 --- a/Software/PC_Application/VNA/vna.h +++ b/Software/PC_Application/VNA/vna.h @@ -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(); diff --git a/Software/PC_Application/appwindow.cpp b/Software/PC_Application/appwindow.cpp index a5bc610..8d0a635 100644 --- a/Software/PC_Application/appwindow.cpp +++ b/Software/PC_Application/appwindow.cpp @@ -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; diff --git a/Software/PC_Application/appwindow.h b/Software/PC_Application/appwindow.h index d6b9ef3..4d4beba 100644 --- a/Software/PC_Application/appwindow.h +++ b/Software/PC_Application/appwindow.h @@ -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; diff --git a/Software/PC_Application/Calibration/json.hpp b/Software/PC_Application/json.hpp similarity index 100% rename from Software/PC_Application/Calibration/json.hpp rename to Software/PC_Application/json.hpp diff --git a/Software/PC_Application/main.ui b/Software/PC_Application/main.ui index 91de82d..9ccf3ba 100644 --- a/Software/PC_Application/main.ui +++ b/Software/PC_Application/main.ui @@ -27,6 +27,8 @@ File + + @@ -170,6 +172,24 @@ Receiver Calibration + + + + :/icons/save.png:/icons/save.png + + + Save setup + + + + + + :/icons/open.png:/icons/open.png + + + Load setup + + diff --git a/Software/PC_Application/mode.h b/Software/PC_Application/mode.h index 9119af7..0aa4f08 100644 --- a/Software/PC_Application/mode.h +++ b/Software/PC_Application/mode.h @@ -8,8 +8,9 @@ #include #include #include "appwindow.h" +#include "savable.h" -class Mode : public QObject +class Mode : public QObject, public Savable { public: Mode(AppWindow *window, QString name); diff --git a/Software/PC_Application/savable.h b/Software/PC_Application/savable.h new file mode 100644 index 0000000..75845bd --- /dev/null +++ b/Software/PC_Application/savable.h @@ -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 diff --git a/Software/PC_Application/touchstone.cpp b/Software/PC_Application/touchstone.cpp index d77f839..de4ea4a 100644 --- a/Software/PC_Application/touchstone.cpp +++ b/Software/PC_Application/touchstone.cpp @@ -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