PC Application: partial firmware update dialog

This commit is contained in:
Jan Käberich 2020-08-30 22:03:41 +02:00
parent 8c8749accd
commit 07ba714f1f
134 changed files with 13954 additions and 7 deletions

57
AssembleFirmware.py Executable file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
import os
import binascii
FPGA_BITSTREAM = "FPGA/VNA/top.bin"
MCU_FW = "Software/VNA_embedded/Debug/VNA_embedded.bin"
HEADER_SIZE = 24
f = open("test.vnafw", "wb")
f.write(bytes("VNA!", 'utf-8'))
bitstream = open(FPGA_BITSTREAM, "rb")
firmware = open(MCU_FW, "rb")
size_FPGA = os.path.getsize(FPGA_BITSTREAM)
size_MCU = os.path.getsize(MCU_FW)
print("Got FPGA bitstream of size "+str(size_FPGA))
print("Got MCU firmware of size "+str(size_MCU))
#Create header
# Start address of FPGA bitstream
f.write((HEADER_SIZE).to_bytes(4, byteorder='little'))
# Size of FPGA bitstream
f.write(size_FPGA.to_bytes(4, byteorder='little'))
# Start address of MCU firmware
f.write((HEADER_SIZE + size_FPGA).to_bytes(4, byteorder='little'))
# Size of MCU firmware
f.write(size_MCU.to_bytes(4, byteorder='little'))
# Calculate CRC
def CRC32_from_file(filename, initial_CRC):
buf = open(filename,'rb').read()
buf = (binascii.crc32(buf, initial_CRC) & 0xFFFFFFFF)
return buf
print("Calculating CRC...", end="")
CRC = CRC32_from_file(FPGA_BITSTREAM, 0xFFFFFFFF)
CRC = CRC32_from_file(MCU_FW, CRC)
print(":"+hex(CRC))
f.write(CRC.to_bytes(4, byteorder='little'))
# Check that we used the correct header size
if f.tell() != HEADER_SIZE:
print("Incorrect header size (defined as "+str(HEADER_SIZE)+" but actual header is of size "+str(f.tell())+")")
exit(-1)
f.write(bitstream.read())
f.write(firmware.read())
if f.tell() % 256 != 0:
padding = 256 - f.tell() % 256
f.write(bytearray(padding))
print("Created combined firmware file of size "+str(f.tell()))

View File

@ -254,6 +254,8 @@
<status xil_pn:value="SuccessfullyRun"/>
<status xil_pn:value="WarningsGenerated"/>
<status xil_pn:value="ReadyToRun"/>
<status xil_pn:value="OutOfDateForOutputs"/>
<status xil_pn:value="OutputChanged"/>
<outfile xil_pn:name="_xmsgs/map.xmsgs"/>
<outfile xil_pn:name="top.pcf"/>
<outfile xil_pn:name="top_map.map"/>

74
Software/PC_Application/.gitignore vendored Normal file
View File

@ -0,0 +1,74 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
moc_*.h
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe

View File

@ -0,0 +1 @@
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="564e", MODE:="0666"

Binary file not shown.

View File

@ -0,0 +1,116 @@
HEADERS += \
../VNA_embedded/Application/Communication/Protocol.hpp \
Calibration/calibration.h \
Calibration/calibrationtracedialog.h \
Calibration/calkit.h \
Calibration/calkitdialog.h \
Calibration/measurementmodel.h \
CustomWidgets/siunitedit.h \
CustomWidgets/tilewidget.h \
CustomWidgets/toggleswitch.h \
CustomWidgets/touchstoneimport.h \
Device/device.h \
Device/devicelog.h \
Device/firmwareupdatedialog.h \
Device/manualcontroldialog.h \
Menu/menu.h \
Menu/menuaction.h \
Menu/menubool.h \
Menu/menuitem.h \
Menu/menuvalue.h \
Tools/eseries.h \
Tools/impedancematchdialog.h \
Traces/bodeplotaxisdialog.h \
Traces/markerwidget.h \
Traces/trace.h \
Traces/tracebodeplot.h \
Traces/traceeditdialog.h \
Traces/traceexportdialog.h \
Traces/traceimportdialog.h \
Traces/tracemarker.h \
Traces/tracemarkermodel.h \
Traces/tracemodel.h \
Traces/traceplot.h \
Traces/tracesmithchart.h \
Traces/tracewidget.h \
averaging.h \
qwtplotpiecewisecurve.h \
touchstone.h \
unit.h \
valueinput.h \
vna.h
SOURCES += \
../VNA_embedded/Application/Communication/Protocol.cpp \
Calibration/calibration.cpp \
Calibration/calibrationtracedialog.cpp \
Calibration/calkit.cpp \
Calibration/calkitdialog.cpp \
Calibration/measurementmodel.cpp \
CustomWidgets/siunitedit.cpp \
CustomWidgets/tilewidget.cpp \
CustomWidgets/toggleswitch.cpp \
CustomWidgets/touchstoneimport.cpp \
Device/device.cpp \
Device/devicelog.cpp \
Device/firmwareupdatedialog.cpp \
Device/manualcontroldialog.cpp \
Menu/menu.cpp \
Menu/menuaction.cpp \
Menu/menubool.cpp \
Menu/menuitem.cpp \
Menu/menuvalue.cpp \
Tools/eseries.cpp \
Tools/impedancematchdialog.cpp \
Traces/bodeplotaxisdialog.cpp \
Traces/markerwidget.cpp \
Traces/trace.cpp \
Traces/tracebodeplot.cpp \
Traces/traceeditdialog.cpp \
Traces/traceexportdialog.cpp \
Traces/traceimportdialog.cpp \
Traces/tracemarker.cpp \
Traces/tracemarkermodel.cpp \
Traces/tracemodel.cpp \
Traces/traceplot.cpp \
Traces/tracesmithchart.cpp \
Traces/tracewidget.cpp \
averaging.cpp \
main.cpp \
qwtplotpiecewisecurve.cpp \
touchstone.cpp \
unit.cpp \
valueinput.cpp \
vna.cpp
LIBS += -lusb-1.0
unix:INCLUDEPATH += /usr/include/qwt
unix:LIBS += -L/usr/lib/ -lqwt-qt5
win32:INCLUDEPATH += C:\Qwt-6.1.4\include
win32:LIBS += -LC:\Qwt-6.1.4\lib -lqwt
QT += widgets
FORMS += \
Calibration/calibrationtracedialog.ui \
Calibration/calkitdialog.ui \
CustomWidgets/tilewidget.ui \
CustomWidgets/touchstoneimport.ui \
Device/devicelog.ui \
Device/firmwareupdatedialog.ui \
Device/manualcontroldialog.ui \
Tools/impedancematchdialog.ui \
Traces/bodeplotaxisdialog.ui \
Traces/markerwidget.ui \
Traces/traceeditdialog.ui \
Traces/traceexportdialog.ui \
Traces/traceimportdialog.ui \
Traces/tracewidget.ui \
main.ui
DISTFILES +=
RESOURCES += \
icons.qrc
CONFIG += c++14

View File

@ -0,0 +1,666 @@
#include "calibration.h"
#include <algorithm>
#include <QMessageBox>
#include <QFileDialog>
#include <fstream>
using namespace std;
Calibration::Calibration()
{
// Creator vectors for measurements
measurements[Measurement::Port1Open].datapoints = vector<Protocol::Datapoint>();
measurements[Measurement::Port1Short].datapoints = vector<Protocol::Datapoint>();
measurements[Measurement::Port1Load].datapoints = vector<Protocol::Datapoint>();
measurements[Measurement::Port2Open].datapoints = vector<Protocol::Datapoint>();
measurements[Measurement::Port2Short].datapoints = vector<Protocol::Datapoint>();
measurements[Measurement::Port2Load].datapoints = vector<Protocol::Datapoint>();
measurements[Measurement::Isolation].datapoints = vector<Protocol::Datapoint>();
measurements[Measurement::Through].datapoints = vector<Protocol::Datapoint>();
type = Type::None;
}
void Calibration::clearMeasurements()
{
for(auto m : measurements) {
m.second.datapoints.clear();
}
}
void Calibration::clearMeasurement(Calibration::Measurement type)
{
measurements[type].datapoints.clear();
measurements[type].timestamp = QDateTime();
}
void Calibration::addMeasurement(Calibration::Measurement type, Protocol::Datapoint &d)
{
measurements[type].datapoints.push_back(d);
measurements[type].timestamp = QDateTime::currentDateTime();
}
bool Calibration::calculationPossible(Calibration::Type type)
{
std::vector<Measurement> requiredMeasurements;
switch(type) {
case Type::Port1SOL:
requiredMeasurements.push_back(Measurement::Port1Open);
requiredMeasurements.push_back(Measurement::Port1Short);
requiredMeasurements.push_back(Measurement::Port1Load);
break;
case Type::Port2SOL:
requiredMeasurements.push_back(Measurement::Port2Open);
requiredMeasurements.push_back(Measurement::Port2Short);
requiredMeasurements.push_back(Measurement::Port2Load);
break;
case Type::FullSOLT:
requiredMeasurements.push_back(Measurement::Port1Open);
requiredMeasurements.push_back(Measurement::Port1Short);
requiredMeasurements.push_back(Measurement::Port1Load);
requiredMeasurements.push_back(Measurement::Port2Open);
requiredMeasurements.push_back(Measurement::Port2Short);
requiredMeasurements.push_back(Measurement::Port2Load);
requiredMeasurements.push_back(Measurement::Through);
break;
case Type::None:
return false;
}
return SanityCheckSamples(requiredMeasurements);
}
bool Calibration::constructErrorTerms(Calibration::Type type)
{
if(!calculationPossible(type)) {
return false;
}
if(minFreq < kit.minFreq() || maxFreq > kit.maxFreq()) {
// Calkit does not support complete calibration range
QMessageBox::critical(nullptr, "Unable to perform calibration", "The calibration kit does not support the complete span. Please choose a different calibration kit or a narrower span.");
return false;
}
switch(type) {
case Type::Port1SOL:
constructPort1SOL();
break;
case Type::Port2SOL:
constructPort2SOL();
break;
case Type::FullSOLT:
construct12TermPoints();
break;
case Type::None:
break;
}
this->type = type;
return true;
}
void Calibration::resetErrorTerms()
{
type = Type::None;
points.clear();
}
void Calibration::construct12TermPoints()
{
std::vector<Measurement> requiredMeasurements;
requiredMeasurements.push_back(Measurement::Port1Open);
requiredMeasurements.push_back(Measurement::Port1Short);
requiredMeasurements.push_back(Measurement::Port1Load);
requiredMeasurements.push_back(Measurement::Port2Open);
requiredMeasurements.push_back(Measurement::Port2Short);
requiredMeasurements.push_back(Measurement::Port2Load);
requiredMeasurements.push_back(Measurement::Through);
if(!SanityCheckSamples(requiredMeasurements)) {
throw runtime_error("Missing/wrong calibration measurement");
}
requiredMeasurements.push_back(Measurement::Isolation);
bool isolation_measured = SanityCheckSamples(requiredMeasurements);
// If we get here the calibration measurements are all okay
points.clear();
for(unsigned int i = 0;i<measurements[Measurement::Port1Open].datapoints.size();i++) {
Point p;
p.frequency = measurements[Measurement::Port1Open].datapoints[i].frequency;
// extract required complex reflection/transmission factors from datapoints
auto S11_open = complex<double>(measurements[Measurement::Port1Open].datapoints[i].real_S11, measurements[Measurement::Port1Open].datapoints[i].imag_S11);
auto S11_short = complex<double>(measurements[Measurement::Port1Short].datapoints[i].real_S11, measurements[Measurement::Port1Short].datapoints[i].imag_S11);
auto S11_load = complex<double>(measurements[Measurement::Port1Load].datapoints[i].real_S11, measurements[Measurement::Port1Load].datapoints[i].imag_S11);
auto S22_open = complex<double>(measurements[Measurement::Port2Open].datapoints[i].real_S22, measurements[Measurement::Port2Open].datapoints[i].imag_S22);
auto S22_short = complex<double>(measurements[Measurement::Port2Short].datapoints[i].real_S22, measurements[Measurement::Port2Short].datapoints[i].imag_S22);
auto S22_load = complex<double>(measurements[Measurement::Port2Load].datapoints[i].real_S22, measurements[Measurement::Port2Load].datapoints[i].imag_S22);
auto S21_isolation = complex<double>(0,0);
auto S12_isolation = complex<double>(0,0);
if(isolation_measured) {
S21_isolation = complex<double>(measurements[Measurement::Isolation].datapoints[i].real_S21, measurements[Measurement::Isolation].datapoints[i].imag_S21);
S12_isolation = complex<double>(measurements[Measurement::Isolation].datapoints[i].real_S12, measurements[Measurement::Isolation].datapoints[i].imag_S12);
}
auto S11_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S11, measurements[Measurement::Through].datapoints[i].imag_S11);
auto S21_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S21, measurements[Measurement::Through].datapoints[i].imag_S21);
auto S22_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S22, measurements[Measurement::Through].datapoints[i].imag_S22);
auto S12_through = complex<double>(measurements[Measurement::Through].datapoints[i].real_S12, measurements[Measurement::Through].datapoints[i].imag_S12);
auto actual = kit.toReflection(p.frequency);
// Forward calibration
computeSOL(S11_short, S11_open, S11_load, p.fe00, p.fe11, p.fe10e01, actual.Open, actual.Short, actual.Load);
p.fe30 = S21_isolation;
// See page 17 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
// Formulas for S11M and S21M solved for e22 and e10e32
auto deltaS = actual.ThroughS11*actual.ThroughS22 - actual.ThroughS21 * actual.ThroughS12;
p.fe22 = ((S11_through - p.fe00)*(1.0 - p.fe11 * actual.ThroughS11)-actual.ThroughS11*p.fe10e01)
/ ((S11_through - p.fe00)*(actual.ThroughS22-p.fe11*deltaS)-deltaS*p.fe10e01);
p.fe10e32 = (S21_through - p.fe30)*(1.0 - p.fe11*actual.ThroughS11 - p.fe22*actual.ThroughS22 + p.fe11*p.fe22*deltaS) / actual.ThroughS21;
// Reverse calibration
computeSOL(S22_short, S22_open, S22_load, p.re33, p.re22, p.re23e32, actual.Open, actual.Short, actual.Load);
p.re03 = S12_isolation;
p.re11 = ((S22_through - p.re33)*(1.0 - p.re22 * actual.ThroughS22)-actual.ThroughS22*p.re23e32)
/ ((S22_through - p.re33)*(actual.ThroughS11-p.re22*deltaS)-deltaS*p.re23e32);
p.re23e01 = (S12_through - p.re03)*(1.0 - p.re11*actual.ThroughS11 - p.re22*actual.ThroughS22 + p.re11*p.re22*deltaS) / actual.ThroughS12;
points.push_back(p);
}
}
void Calibration::constructPort1SOL()
{
std::vector<Measurement> requiredMeasurements;
requiredMeasurements.push_back(Measurement::Port1Open);
requiredMeasurements.push_back(Measurement::Port1Short);
requiredMeasurements.push_back(Measurement::Port1Load);
if(!SanityCheckSamples(requiredMeasurements)) {
throw runtime_error("Missing/wrong calibration measurement");
}
// If we get here the calibration measurements are all okay
points.clear();
for(unsigned int i = 0;i<measurements[Measurement::Port1Open].datapoints.size();i++) {
Point p;
p.frequency = measurements[Measurement::Port1Open].datapoints[i].frequency;
// extract required complex reflection/transmission factors from datapoints
auto S11_open = complex<double>(measurements[Measurement::Port1Open].datapoints[i].real_S11, measurements[Measurement::Port1Open].datapoints[i].imag_S11);
auto S11_short = complex<double>(measurements[Measurement::Port1Short].datapoints[i].real_S11, measurements[Measurement::Port1Short].datapoints[i].imag_S11);
auto S11_load = complex<double>(measurements[Measurement::Port1Load].datapoints[i].real_S11, measurements[Measurement::Port1Load].datapoints[i].imag_S11);
// OSL port1
auto actual = kit.toReflection(p.frequency);
// See page 19 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
computeSOL(S11_short, S11_open, S11_load, p.fe00, p.fe11, p.fe10e01, actual.Open, actual.Short, actual.Load);
// All other calibration coefficients to ideal values
p.fe30 = 0.0;
p.fe22 = 0.0;
p.fe10e32 = 1.0;
p.re33 = 0.0;
p.re22 = 0.0;
p.re23e32 = 1.0;
p.re03 = 0.0;
p.re11 = 0.0;
p.re23e01 = 1.0;
points.push_back(p);
}
}
void Calibration::constructPort2SOL()
{
std::vector<Measurement> requiredMeasurements;
requiredMeasurements.push_back(Measurement::Port2Open);
requiredMeasurements.push_back(Measurement::Port2Short);
requiredMeasurements.push_back(Measurement::Port2Load);
if(!SanityCheckSamples(requiredMeasurements)) {
throw runtime_error("Missing/wrong calibration measurement");
}
// If we get here the calibration measurements are all okay
points.clear();
for(unsigned int i = 0;i<measurements[Measurement::Port2Open].datapoints.size();i++) {
Point p;
p.frequency = measurements[Measurement::Port2Open].datapoints[i].frequency;
// extract required complex reflection/transmission factors from datapoints
auto S22_open = complex<double>(measurements[Measurement::Port2Open].datapoints[i].real_S22, measurements[Measurement::Port2Open].datapoints[i].imag_S22);
auto S22_short = complex<double>(measurements[Measurement::Port2Short].datapoints[i].real_S22, measurements[Measurement::Port2Short].datapoints[i].imag_S22);
auto S22_load = complex<double>(measurements[Measurement::Port2Load].datapoints[i].real_S22, measurements[Measurement::Port2Load].datapoints[i].imag_S22);
// OSL port2
auto actual = kit.toReflection(p.frequency);
// See page 19 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
computeSOL(S22_short, S22_open, S22_load, p.re33, p.re22, p.re23e32, actual.Open, actual.Short, actual.Load);
// All other calibration coefficients to ideal values
p.fe30 = 0.0;
p.fe22 = 0.0;
p.fe10e32 = 1.0;
p.fe00 = 0.0;
p.fe11 = 0.0;
p.fe10e01 = 1.0;
p.re03 = 0.0;
p.re11 = 0.0;
p.re23e01 = 1.0;
points.push_back(p);
}
}
void Calibration::correctMeasurement(Protocol::Datapoint &d)
{
if(type == Type::None) {
// No calibration data, do nothing
return;
}
// Convert measurements to complex variables
auto S11m = complex<double>(d.real_S11, d.imag_S11);
auto S21m = complex<double>(d.real_S21, d.imag_S21);
auto S22m = complex<double>(d.real_S22, d.imag_S22);
auto S12m = complex<double>(d.real_S12, d.imag_S12);
// find correct entry
auto p = getCalibrationPoint(d);
// equations from page 20 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
auto denom = (1.0 + (S11m - p.fe00) / p.fe10e01 * p.fe11) * (1.0 + (S22m - p.re33) / p.re23e32 * p.re22)
- (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01 * p.fe22 * p.re11;
auto S11 = ((S11m - p.fe00) / p.fe10e01 * (1.0 + (S22m - p.re33) / p.re23e32 * p.re22)
- p.fe22 * (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01) / denom;
auto S21 = ((S21m - p.fe30) / p.fe10e32 * (1.0 + (S22m - p.re33) / p.re23e32 * (p.re22 - p.fe22))) / denom;
auto S22 = ((S22m - p.re33) / p.re23e32 * (1.0 + (S11m - p.fe00) / p.fe10e01 * p.fe11)
- p.re11 * (S21m - p.fe30) / p.fe10e32 * (S12m - p.re03) / p.re23e01) / denom;
auto S12 = ((S12m - p.re03) / p.re23e01 * (1.0 + (S11m - p.fe00) / p.fe10e01 * (p.fe11 - p.re11))) / denom;
d.real_S11 = S11.real();
d.imag_S11 = S11.imag();
d.real_S12 = S12.real();
d.imag_S12 = S12.imag();
d.real_S21 = S21.real();
d.imag_S21 = S21.imag();
d.real_S22 = S22.real();
d.imag_S22 = S22.imag();
}
Calibration::InterpolationType Calibration::getInterpolation(Protocol::SweepSettings settings)
{
if(!points.size()) {
return InterpolationType::NoCalibration;
}
if(settings.f_start < points.front().frequency || settings.f_stop > points.back().frequency) {
return InterpolationType::Extrapolate;
}
// Either exact or interpolation, check individual frequencies
uint32_t f_step = (settings.f_stop - settings.f_start) / (settings.points - 1);
for(uint64_t f = settings.f_start; f <= settings.f_stop; f += f_step) {
if(find_if(points.begin(), points.end(), [&f](const Point& p){
return abs(f - p.frequency) < 100;
}) == points.end()) {
return InterpolationType::Interpolate;
}
}
// if we get here all frequency points were matched
if(points.front().frequency == settings.f_start && points.back().frequency == settings.f_stop) {
return InterpolationType::Unchanged;
} else {
return InterpolationType::Exact;
}
}
QString Calibration::MeasurementToString(Calibration::Measurement m)
{
switch(m) {
case Measurement::Port1Open:
return "Port 1 Open";
case Measurement::Port1Short:
return "Port 1 Short";
case Measurement::Port1Load:
return "Port 1 Load";
case Measurement::Port2Open:
return "Port 2 Open";
case Measurement::Port2Short:
return "Port 2 Short";
case Measurement::Port2Load:
return "Port 2 Load";
case Measurement::Through:
return "Through";
case Measurement::Isolation:
return "Isolation";
default:
return "Unknown";
}
}
QString Calibration::TypeToString(Calibration::Type t)
{
switch(t) {
case Type::Port1SOL: return "Port 1"; break;
case Type::Port2SOL: return "Port 2"; break;
case Type::FullSOLT: return "SOLT"; break;
default: return "None"; break;
}
}
const std::vector<Calibration::Type> Calibration::Types()
{
const std::vector<Calibration::Type> ret = {Type::Port1SOL, Type::Port2SOL, Type::FullSOLT};
return ret;
}
const std::vector<Calibration::Measurement> Calibration::Measurements(Calibration::Type type)
{
switch(type) {
case Type::FullSOLT:
case Type::None:
return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load, Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load, Measurement::Through, Measurement::Isolation};
break;
case Type::Port1SOL:
return {Measurement::Port1Short, Measurement::Port1Open, Measurement::Port1Load};
break;
case Type::Port2SOL:
return {Measurement::Port2Short, Measurement::Port2Open, Measurement::Port2Load};
break;
default:
return {};
break;
}
}
Calibration::MeasurementInfo Calibration::getMeasurementInfo(Calibration::Measurement m)
{
MeasurementInfo info;
switch(m) {
case Measurement::Port1Short:
info.name = "Port 1 short";
info.prerequisites = "Short standard connected to port 1, port 2 open";
break;
case Measurement::Port1Open:
info.name = "Port 1 open";
info.prerequisites = "Open standard connected to port 1, port 2 open";
break;
case Measurement::Port1Load:
info.name = "Port 1 load";
info.prerequisites = "Load standard connected to port 1, port 2 open";
break;
case Measurement::Port2Short:
info.name = "Port 2 short";
info.prerequisites = "Port 1 open, short standard connected to port 2";
break;
case Measurement::Port2Open:
info.name = "Port 2 open";
info.prerequisites = "Port 1 open, open standard connected to port 2";
break;
case Measurement::Port2Load:
info.name = "Port 2 load";
info.prerequisites = "Port 1 open, load standard connected to port 2";
break;
case Measurement::Through:
info.name = "Through";
info.prerequisites = "Port 1 connected to port 2 via through standard";
break;
case Measurement::Isolation:
info.name = "Isolation";
info.prerequisites = "Both ports terminated into 50 ohm";
}
info.points = measurements[m].datapoints.size();
if(info.points > 0) {
info.fmin = measurements[m].datapoints.front().frequency;
info.fmax = measurements[m].datapoints.back().frequency;
info.points = measurements[m].datapoints.size();
}
info.timestamp = measurements[m].timestamp;
return info;
}
std::vector<Trace *> Calibration::getErrorTermTraces()
{
std::vector<Trace*> traces;
const QString traceNames[12] = {"e00", "F_e11", "e10e01", "e10e32", "F_e22", "e30", "e33", "R_e11", "e23e32", "e23e01", "R_e22", "e03"};
constexpr bool reflection[12] = {true, true, false, false, true, false, true, true, false, false, true, false};
for(int i=0;i<12;i++) {
auto t = new Trace(traceNames[i], Qt::red);
t->setCalibration(true);
t->setReflection(reflection[i]);
traces.push_back(t);
}
for(auto p : points) {
Trace::Data d;
d.frequency = p.frequency;
for(int i=0;i<12;i++) {
switch(i) {
case 0: d.S = p.fe00; break;
case 1: d.S = p.fe11; break;
case 2: d.S = p.fe10e01; break;
case 3: d.S = p.fe10e32; break;
case 4: d.S = p.fe22; break;
case 5: d.S = p.fe30; break;
case 6: d.S = p.re33; break;
case 7: d.S = p.re11; break;
case 8: d.S = p.re23e32; break;
case 9: d.S = p.re23e01; break;
case 10: d.S = p.re22; break;
case 11: d.S = p.re03; break;
}
traces[i]->addData(d);
}
}
return traces;
}
bool Calibration::openFromFile(QString filename)
{
if(filename.isEmpty()) {
filename = QFileDialog::getOpenFileName(nullptr, "Load calibration data", "", "Calibration files (*.cal)", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.isEmpty()) {
// aborted selection
return false;
}
}
// attempt to load associated calibration kit first (needs to be available when performing calibration)
auto calkit_file = filename;
auto dotPos = calkit_file.lastIndexOf('.');
if(dotPos >= 0) {
calkit_file.truncate(dotPos);
}
calkit_file.append(".calkit");
try {
kit = Calkit::fromFile(calkit_file.toStdString());
} catch (runtime_error e) {
QMessageBox::warning(nullptr, "Missing calibration kit", "The calibration kit file associated with the selected calibration could not be parsed. The calibration might not be accurate. (" + QString(e.what()) + ")");
}
ifstream file;
file.open(filename.toStdString());
try {
file >> *this;
} catch(runtime_error e) {
QMessageBox::warning(nullptr, "File parsing error", e.what());
return false;
}
return true;
}
bool Calibration::saveToFile(QString filename)
{
if(filename.isEmpty()) {
filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", "", "Calibration files (*.cal)", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.isEmpty()) {
// aborted selection
return false;
}
}
// strip any potential file name extension and set default
auto dotPos = filename.lastIndexOf('.');
if(dotPos >= 0) {
filename.truncate(dotPos);
}
auto calibration_file = filename;
calibration_file.append(".cal");
ofstream file;
file.open(calibration_file.toStdString());
file << *this;
auto calkit_file = filename;
calkit_file.append(".calkit");
kit.toFile(calkit_file.toStdString());
return true;
}
ostream& operator<<(ostream &os, const Calibration &c)
{
for(auto m : c.measurements) {
if(m.second.datapoints.size() > 0) {
os << c.MeasurementToString(m.first).toStdString() << endl;
os << m.second.timestamp.toSecsSinceEpoch() << endl;
os << m.second.datapoints.size() << endl;
for(auto p : m.second.datapoints) {
os << p.pointNum << " " << p.frequency << " ";
os << p.imag_S11 << " " << p.real_S11 << " " << p.imag_S21 << " " << p.real_S21 << " " << p.imag_S12 << " " << p.real_S12 << " " << p.imag_S22 << " " << p.real_S22;
os << endl;
}
}
}
os << Calibration::TypeToString(c.getType()).toStdString() << endl;
return os;
}
istream& operator >>(istream &in, Calibration &c)
{
std::string line;
while(getline(in, line)) {
for(auto m : Calibration::Measurements()) {
if(Calibration::MeasurementToString(m) == QString::fromStdString(line)) {
// this is the correct measurement
c.clearMeasurement(m);
uint timestamp;
in >> timestamp;
c.measurements[m].timestamp = QDateTime::fromSecsSinceEpoch(timestamp);
unsigned int points;
in >> points;
for(unsigned int i=0;i<points;i++) {
Protocol::Datapoint p;
in >> p.pointNum >> p.frequency;
in >> p.imag_S11 >> p.real_S11 >> p.imag_S21 >> p.real_S21 >> p.imag_S12 >> p.real_S12 >> p.imag_S22 >> p.real_S22;
c.measurements[m].datapoints.push_back(p);
if(in.eof() || in.bad() || in.fail()) {
c.clearMeasurement(m);
throw runtime_error("Failed to parse measurement \"" + line + "\", aborting calibration data import.");
}
}
break;
}
}
for(auto t : Calibration::Types()) {
if(Calibration::TypeToString(t) == QString::fromStdString(line)) {
// try to apply this calibration type
if(c.calculationPossible(t)) {
c.constructErrorTerms(t);
} else {
throw runtime_error("Incomplete calibration data, the requested \"" + line + "\"-Calibration could not be performed.");
}
}
break;
}
}
return in;
}
bool Calibration::SanityCheckSamples(std::vector<Calibration::Measurement> &requiredMeasurements)
{
// sanity check measurements, all need to be of the same size with the same frequencies (except for isolation which may be empty)
vector<uint64_t> freqs;
for(auto type : requiredMeasurements) {
auto m = measurements[type];
if(m.datapoints.size() == 0) {
// empty required measurement
return false;
}
if(freqs.size() == 0) {
// this is the first measurement, create frequency vector
for(auto p : m.datapoints) {
freqs.push_back(p.frequency);
}
} else {
// compare with already assembled frequency vector
if(m.datapoints.size() != freqs.size()) {
return false;
}
for(unsigned int i=0;i<freqs.size();i++) {
if(m.datapoints[i].frequency != freqs[i]) {
return false;
}
}
}
}
minFreq = freqs.front();
maxFreq = freqs.back();
return true;
}
Calibration::Point Calibration::getCalibrationPoint(Protocol::Datapoint &d)
{
if(!points.size()) {
throw runtime_error("No calibration points available");
}
if(d.frequency <= points.front().frequency) {
// use first point even for lower frequencies
return points.front();
}
if(d.frequency >= points.back().frequency) {
// use last point even for higher frequencies
return points.back();
}
auto p = lower_bound(points.begin(), points.end(), d.frequency, [](Point p, uint64_t freq) -> bool {
return p.frequency < freq;
});
if(p->frequency == d.frequency) {
// Exact match, return point
return *p;
}
// need to interpolate
auto high = p;
p--;
auto low = p;
double alpha = (d.frequency - low->frequency) / (high->frequency - low->frequency);
Point ret;
ret.frequency = d.frequency;
ret.fe00 = low->fe00 * (1 - alpha) + high->fe00 * alpha;
ret.fe11 = low->fe11 * (1 - alpha) + high->fe11 * alpha;
ret.fe22 = low->fe22 * (1 - alpha) + high->fe22 * alpha;
ret.fe30 = low->fe30 * (1 - alpha) + high->fe30 * alpha;
ret.re03 = low->re03 * (1 - alpha) + high->re03 * alpha;
ret.re11 = low->re11 * (1 - alpha) + high->re11 * alpha;
ret.re22 = low->re22 * (1 - alpha) + high->re22 * alpha;
ret.re33 = low->re33 * (1 - alpha) + high->re33 * alpha;
ret.fe10e01 = low->fe10e01 * (1 - alpha) + high->fe10e01 * alpha;
ret.fe10e32 = low->fe10e32 * (1 - alpha) + high->fe10e32 * alpha;
ret.re23e01 = low->re23e01 * (1 - alpha) + high->re23e01 * alpha;
ret.re23e32 = low->re23e32 * (1 - alpha) + high->re23e32 * alpha;
return ret;
}
void Calibration::computeSOL(std::complex<double> s_m, std::complex<double> o_m, std::complex<double> l_m,
std::complex<double> &directivity, std::complex<double> &match, std::complex<double> &tracking,
std::complex<double> o_c, std::complex<double> s_c, std::complex<double> l_c)
{
// equations from page 13 of http://www2.electron.frba.utn.edu.ar/~jcecconi/Bibliografia/04%20-%20Param_S_y_VNA/Network_Analyzer_Error_Models_and_Calibration_Methods.pdf
// solved while taking non ideal o/s/l standards into account
auto denom = l_c * o_c * (o_m - l_m) + l_c * s_c * (l_m - s_m) + o_c * s_c * (s_m - o_m);
directivity = (l_c * o_m * (s_m * (o_c - s_c) + l_m * s_c) - l_c * o_c * l_m * s_m + o_c * l_m * s_c * (s_m - o_m)) / denom;
match = (l_c * (o_m - s_m) + o_c * (s_m - l_m) + s_c * (l_m - o_m)) / denom;
auto delta = (l_c * l_m * (o_m - s_m) + o_c * o_m * (s_m - l_m) + s_c * s_m * (l_m - o_m)) / denom;
tracking = directivity * match - delta;
}
std::complex<double> Calibration::correctSOL(std::complex<double> measured, std::complex<double> directivity, std::complex<double> match, std::complex<double> tracking)
{
return (measured - directivity) / (measured * match - directivity * match + tracking);
}
Calkit &Calibration::getCalibrationKit()
{
return kit;
}
void Calibration::setCalibrationKit(const Calkit &value)
{
kit = value;
}
Calibration::Type Calibration::getType() const
{
return type;
}

View File

@ -0,0 +1,136 @@
#ifndef CALIBRATION_H
#define CALIBRATION_H
#include "Device/device.h"
#include <complex>
#include <vector>
#include <map>
#include <iostream>
#include <iomanip>
#include "calkit.h"
#include "Traces/tracemodel.h"
#include <QDateTime>
#include "calkit.h"
class Calibration
{
public:
Calibration();
enum class Measurement {
Port1Open,
Port1Short,
Port1Load,
Port2Open,
Port2Short,
Port2Load,
Isolation,
Through,
};
void clearMeasurements();
void clearMeasurement(Measurement type);
void addMeasurement(Measurement type, Protocol::Datapoint &d);
enum class Type {
Port1SOL,
Port2SOL,
FullSOLT,
None,
};
bool calculationPossible(Type type);
bool constructErrorTerms(Type type);
void resetErrorTerms();
void correctMeasurement(Protocol::Datapoint &d);
enum class InterpolationType {
Unchanged, // Nothing has changed, settings and calibration points match
Exact, // Every frequency point in settings has an exact calibration point (but there are more calibration points outside of the sweep)
Interpolate, // Every point in the sweep can be interpolated between two calibration points
Extrapolate, // At least one point in sweep is outside of the calibration and has to be extrapolated
NoCalibration, // No calibration available
};
InterpolationType getInterpolation(Protocol::SweepSettings settings);
static QString MeasurementToString(Measurement m);
static QString TypeToString(Type t);
class MeasurementInfo {
public:
QString name, prerequisites;
double fmin, fmax;
unsigned int points;
QDateTime timestamp;
};
static const std::vector<Type> Types();
static const std::vector<Measurement> Measurements(Type type = Type::None);
MeasurementInfo getMeasurementInfo(Measurement m);
friend std::ostream& operator<<(std::ostream& os, const Calibration& c);
friend std::istream& operator >> (std::istream &in, Calibration& c);
int nPoints() {
return points.size();
}
std::vector<Trace*> getErrorTermTraces();
bool openFromFile(QString filename = QString());
bool saveToFile(QString filename = QString());
Type getType() const;
Calkit& getCalibrationKit();
void setCalibrationKit(const Calkit &value);
private:
void construct12TermPoints();
void constructPort1SOL();
void constructPort2SOL();
bool SanityCheckSamples(std::vector<Measurement> &requiredMeasurements);
class Point
{
public:
double frequency;
// Forward error terms
std::complex<double> fe00, fe11, fe10e01, fe10e32, fe22, fe30;
// Reverse error terms
std::complex<double> re33, re11, re23e32, re23e01, re22, re03;
};
Point getCalibrationPoint(Protocol::Datapoint &d);
/*
* Constructs directivity, match and tracking correction factors from measurements of three distinct impedances
* Normally, an open, short and load are used (with ideal reflection coefficients of 1, -1 and 0 respectively).
* The actual reflection coefficients can be passed on as optional arguments to take into account the non-ideal
* calibration kit.
*/
void computeSOL(std::complex<double> s_m,
std::complex<double> o_m,
std::complex<double> l_m,
std::complex<double> &directivity,
std::complex<double> &match,
std::complex<double> &tracking,
std::complex<double> o_c = std::complex<double>(1.0, 0),
std::complex<double> s_c = std::complex<double>(-1.0, 0),
std::complex<double> l_c = std::complex<double>(0, 0));
std::complex<double> correctSOL(std::complex<double> measured,
std::complex<double> directivity,
std::complex<double> match,
std::complex<double> tracking);
class MeasurementData {
public:
QDateTime timestamp;
std::vector<Protocol::Datapoint> datapoints;
};
Type type;
std::map<Measurement, MeasurementData> measurements;
double minFreq, maxFreq;
std::vector<Point> points;
Calkit kit;
};
#endif // CALIBRATION_H

View File

@ -0,0 +1,73 @@
#include "calibrationtracedialog.h"
#include "ui_calibrationtracedialog.h"
#include "measurementmodel.h"
#include <QStyle>
CalibrationTraceDialog::CalibrationTraceDialog(Calibration *cal, Calibration::Type type) :
QDialog(nullptr),
ui(new Ui::CalibrationTraceDialog),
cal(cal),
requestedType(type)
{
ui->setupUi(this);
ui->bApply->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton));
measurements = cal->Measurements(type);
if(requestedType == Calibration::Type::None) {
ui->bApply->setVisible(false);
}
model = new MeasurementModel(cal, measurements);
ui->tableView->setModel(model);
ui->tableView->setColumnWidth(0, 100);
ui->tableView->setColumnWidth(1, 350);
ui->tableView->setColumnWidth(2, 320);
ui->tableView->setColumnWidth(3, 160);
UpdateApplyButton();
}
CalibrationTraceDialog::~CalibrationTraceDialog()
{
delete ui;
}
void CalibrationTraceDialog::measurementComplete(Calibration::Measurement m)
{
model->measurementUpdated(m);
UpdateApplyButton();
}
void CalibrationTraceDialog::UpdateApplyButton()
{
ui->bApply->setEnabled(cal->calculationPossible(requestedType));
}
void CalibrationTraceDialog::on_bDelete_clicked()
{
auto measurement = measurements[ui->tableView->currentIndex().row()];
cal->clearMeasurement(measurement);
model->measurementUpdated(measurement);
UpdateApplyButton();
}
void CalibrationTraceDialog::on_bMeasure_clicked()
{
auto measurement = measurements[ui->tableView->currentIndex().row()];
emit triggerMeasurement(measurement);
}
void CalibrationTraceDialog::on_bApply_clicked()
{
emit applyCalibration(requestedType);
accept();
}
void CalibrationTraceDialog::on_bOpen_clicked()
{
cal->openFromFile();
UpdateApplyButton();
emit applyCalibration(cal->getType());
}
void CalibrationTraceDialog::on_bSave_clicked()
{
cal->saveToFile();
}

View File

@ -0,0 +1,44 @@
#ifndef CALIBRATIONTRACEDIALOG_H
#define CALIBRATIONTRACEDIALOG_H
#include <QDialog>
#include "calibration.h"
#include "measurementmodel.h"
namespace Ui {
class CalibrationTraceDialog;
}
class CalibrationTraceDialog : public QDialog
{
Q_OBJECT
public:
explicit CalibrationTraceDialog(Calibration *cal, Calibration::Type type = Calibration::Type::None);
~CalibrationTraceDialog();
public slots:
void measurementComplete(Calibration::Measurement m);
signals:
void triggerMeasurement(Calibration::Measurement m);
void applyCalibration(Calibration::Type type);
private slots:
void on_bDelete_clicked();
void on_bMeasure_clicked();
void on_bApply_clicked();
void on_bOpen_clicked();
void on_bSave_clicked();
private:
void UpdateApplyButton();
Ui::CalibrationTraceDialog *ui;
Calibration *cal;
Calibration::Type requestedType;
std::vector<Calibration::Measurement> measurements;
MeasurementModel *model;
};
#endif // CALIBRATIONTRACEDIALOG_H

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CalibrationTraceDialog</class>
<widget class="QDialog" name="CalibrationTraceDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1066</width>
<height>396</height>
</rect>
</property>
<property name="windowTitle">
<string>Calibration Traces</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableView">
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderHighlightSections">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="bMeasure">
<property name="text">
<string>Measure</string>
</property>
<property name="icon">
<iconset theme="media-playback-start">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bDelete">
<property name="text">
<string>Delete</string>
</property>
<property name="icon">
<iconset theme="edit-delete">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bOpen">
<property name="text">
<string>Open</string>
</property>
<property name="icon">
<iconset theme="document-open"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bSave">
<property name="text">
<string>Save</string>
</property>
<property name="icon">
<iconset theme="document-save"/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="bApply">
<property name="text">
<string>Apply Calibration</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,290 @@
#include "calkit.h"
#include <fstream>
#include <iomanip>
#include "calkitdialog.h"
#include <math.h>
using namespace std;
Calkit::Calkit()
: ts_open(nullptr),
ts_short(nullptr),
ts_load(nullptr),
ts_through(nullptr),
ts_cached(false)
{
open_Z0 = 50.0;
open_delay = 0.0;
open_loss = 0.0;
open_C0 = 0.0;
open_C1 = 0.0;
open_C2 = 0.0;
open_C3 = 0.0;
short_Z0 = 50.0;
short_delay = 0.0;
short_loss = 0.0;
short_L0 = 0.0;
short_L1 = 0.0;
short_L2 = 0.0;
short_L3 = 0.0;
load_Z0 = 50.0;
through_Z0 = 50.0;
through_delay = 0.0;
through_loss = 0.0;
open_measurements = false;
short_measurements = false;
load_measurements = false;
through_measurements = false;
open_file = "";
short_file = "";
load_file = "";
through_file = "";
open_Sparam = 0;
short_Sparam = 0;
load_Sparam = 0;
through_Sparam1 = 0;
through_Sparam2 = 1;
}
void Calkit::toFile(std::string filename)
{
ofstream file;
file.open(filename);
file << std::fixed << std::setprecision(12);
file << open_measurements << "\n" << short_measurements << "\n" << load_measurements << "\n" << through_measurements << "\n";
file << open_Z0 << "\n" << open_delay << "\n" << open_loss << "\n" << open_C0 << "\n" << open_C1 << "\n" << open_C2 << "\n" << open_C3 << "\n";
file << short_Z0 << "\n" << short_delay << "\n" << short_loss << "\n" << short_L0 << "\n" << short_L1 << "\n" << short_L2 << "\n" << short_L3 << "\n";
file << load_Z0 << "\n";
file << through_Z0 << "\n" << through_delay << "\n" << through_loss << "\n";
if(open_measurements) {
file << open_file << "\n" << open_Sparam << "\n";
}
if(short_measurements) {
file << short_file << "\n" << short_Sparam << "\n";
}
if(load_measurements) {
file << load_file << "\n" << load_Sparam << "\n";
}
if(through_measurements) {
file << through_file << "\n" << through_Sparam1 << "\n" << through_Sparam2 << "\n";
}
file.close();
}
Calkit Calkit::fromFile(std::string filename)
{
Calkit c;
ifstream file;
file.open(filename);
if(!file.is_open()) {
throw runtime_error("Unable to open file");
}
file >> c.open_measurements;
file >> c.short_measurements;
file >> c.load_measurements;
file >> c.through_measurements;
file >> c.open_Z0;
file >> c.open_delay;
file >> c.open_loss;
file >> c.open_C0;
file >> c.open_C1;
file >> c.open_C2;
file >> c.open_C3;
file >> c.short_Z0;
file >> c.short_delay;
file >> c.short_loss;
file >> c.short_L0;
file >> c.short_L1;
file >> c.short_L2;
file >> c.short_L3;
file >> c.load_Z0;
file >> c.through_Z0;
file >> c.through_delay;
file >> c.through_loss;
if(c.open_measurements) {
file >> c.open_file;
file >> c.open_Sparam;
}
if(c.short_measurements) {
file >> c.short_file;
file >> c.short_Sparam;
}
if(c.load_measurements) {
file >> c.load_file;
file >> c.load_Sparam;
}
if(c.through_measurements) {
file >> c.through_file;
file >> c.through_Sparam1;
file >> c.through_Sparam2;
}
file.close();
return c;
}
void Calkit::edit()
{
auto dialog = new CalkitDialog(*this);
dialog->show();
}
Calkit::Reflection Calkit::toReflection(double frequency)
{
fillTouchstoneCache();
Reflection ref;
if(load_measurements) {
ref.Load = ts_load->interpolate(frequency).S[0];
} else {
auto imp_load = complex<double>(load_Z0, 0);
ref.Load = (imp_load - complex<double>(50.0)) / (imp_load + complex<double>(50.0));
}
if(open_measurements) {
ref.Open = ts_open->interpolate(frequency).S[0];
} else {
// calculate fringing capacitance for open
double Cfringing = open_C0 * 1e-15 + open_C1 * 1e-27 * frequency + open_C2 * 1e-36 * pow(frequency, 2) + open_C3 * 1e-45 * pow(frequency, 3);
// convert to impedance
if (Cfringing == 0) {
// special case to avoid issues with infinity
ref.Open = complex<double>(1.0, 0);
} else {
auto imp_open = complex<double>(0, -1.0 / (frequency * 2 * M_PI * Cfringing));
ref.Open = (imp_open - complex<double>(50.0)) / (imp_open + complex<double>(50.0));
}
// transform the delay into a phase shift for the given frequency
double open_phaseshift = -2 * M_PI * frequency * open_delay * 1e-12;
double open_att_db = open_loss * 1e9 * 4.3429 * open_delay * 1e-12 / open_Z0 * sqrt(frequency / 1e9);
double open_att = pow(10.0, -open_att_db / 10.0);
auto open_correction = polar<double>(open_att, open_phaseshift);
ref.Open *= open_correction;
}
if(short_measurements) {
ref.Short = ts_short->interpolate(frequency).S[0];
} else {
// calculate inductance for short
double Lseries = short_L0 * 1e-12 + short_L1 * 1e-24 * frequency + short_L2 * 1e-33 * pow(frequency, 2) + short_L3 * 1e-42 * pow(frequency, 3);
// convert to impedance
auto imp_short = complex<double>(0, frequency * 2 * M_PI * Lseries);
ref.Short = (imp_short - complex<double>(50.0)) / (imp_short + complex<double>(50.0));
// transform the delay into a phase shift for the given frequency
double short_phaseshift = -2 * M_PI * frequency * short_delay * 1e-12;
double short_att_db = short_loss * 1e9 * 4.3429 * short_delay * 1e-12 / short_Z0 * sqrt(frequency / 1e9);;
double short_att = pow(10.0, -short_att_db / 10.0);
auto short_correction = polar<double>(short_att, short_phaseshift);
ref.Short *= short_correction;
}
if(through_measurements) {
auto interp = ts_through->interpolate(frequency);
ref.ThroughS11 = interp.S[0];
ref.ThroughS12 = interp.S[1];
ref.ThroughS21 = interp.S[2];
ref.ThroughS22 = interp.S[3];
} else {
// calculate effect of through
double through_phaseshift = -2 * M_PI * frequency * through_delay * 1e-12;
double through_att_db = through_loss * 1e9 * 4.3429 * through_delay * 1e-12 / through_Z0 * sqrt(frequency / 1e9);;
double through_att = pow(10.0, -through_att_db / 10.0);
ref.ThroughS12 = polar<double>(through_att, through_phaseshift);
// Assume symmetric and perfectly matched through for other parameters
ref.ThroughS21 = ref.ThroughS12;
ref.ThroughS11 = 0.0;
ref.ThroughS22 = 0.0;
}
return ref;
}
double Calkit::minFreq()
{
fillTouchstoneCache();
double min = std::numeric_limits<double>::min();
array<Touchstone*, 4> ts_list = {ts_open, ts_short, ts_load, ts_through};
// find the highest minimum frequency in all measurement files
for(auto ts : ts_list) {
if(!ts) {
// this calibration standard is defined by coefficients, no minimum frequency
continue;
}
if(ts->minFreq() > min) {
min = ts->minFreq();
}
}
return min;
}
double Calkit::maxFreq()
{
fillTouchstoneCache();
double max = std::numeric_limits<double>::max();
array<Touchstone*, 4> ts_list = {ts_open, ts_short, ts_load, ts_through};
// find the highest minimum frequency in all measurement files
for(auto ts : ts_list) {
if(!ts) {
// this calibration standard is defined by coefficients, no minimum frequency
continue;
}
if(ts->maxFreq() < max) {
max = ts->maxFreq();
}
}
return max;
}
void Calkit::clearTouchstoneCache()
{
if(ts_open) {
delete ts_open;
ts_open = nullptr;
}
if(ts_short) {
delete ts_short;
ts_short = nullptr;
}
if(ts_load) {
delete ts_load;
ts_load = nullptr;
}
if(ts_through) {
delete ts_through;
ts_through = nullptr;
}
ts_cached = false;
}
void Calkit::fillTouchstoneCache()
{
if(ts_cached) {
return;
}
if(open_measurements) {
ts_open = new Touchstone(1);
*ts_open = Touchstone::fromFile(open_file);
ts_open->reduceTo1Port(open_Sparam);
}
if(short_measurements) {
ts_short = new Touchstone(1);
*ts_short = Touchstone::fromFile(short_file);
ts_open->reduceTo1Port(short_Sparam);
}
if(load_measurements) {
ts_load = new Touchstone(1);
*ts_load = Touchstone::fromFile(load_file);
ts_open->reduceTo1Port(load_Sparam);
}
if(through_measurements) {
ts_through = new Touchstone(2);
*ts_through = Touchstone::fromFile(through_file);
ts_through->reduceTo2Port(through_Sparam1, through_Sparam2);
}
ts_cached = true;
}

View File

@ -0,0 +1,50 @@
#ifndef CALKIT_H
#define CALKIT_H
#include <string>
#include <complex>
#include "touchstone.h"
class Calkit
{
friend class CalkitDialog;
public:
Calkit();
class Reflection {
public:
std::complex<double> Open;
std::complex<double> Short;
std::complex<double> Load;
std::complex<double> ThroughS11, ThroughS12, ThroughS21, ThroughS22;
};
void toFile(std::string filename);
static Calkit fromFile(std::string filename);
void edit();
Reflection toReflection(double frequency);
double minFreq();
double maxFreq();
private:
double open_Z0, open_delay, open_loss, open_C0, open_C1, open_C2, open_C3;
double short_Z0, short_delay, short_loss, short_L0, short_L1, short_L2, short_L3;
double load_Z0;
double through_Z0, through_delay, through_loss;
// coefficients/measurement file switch
bool open_measurements;
bool short_measurements;
bool load_measurements;
bool through_measurements;
std::string open_file, short_file, load_file, through_file;
int open_Sparam, short_Sparam, load_Sparam, through_Sparam1, through_Sparam2;
Touchstone *ts_open, *ts_short, *ts_load, *ts_through;
bool ts_cached;
void clearTouchstoneCache();
void fillTouchstoneCache();
};
#endif // CALKIT_H

View File

@ -0,0 +1,219 @@
#include "calkitdialog.h"
#include "ui_calkitdialog.h"
#include <QPushButton>
#include <QDebug>
#include <QFileDialog>
#include <fstream>
#include <touchstone.h>
#include <QtGlobal>
using namespace std;
CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) :
QDialog(parent),
ui(new Ui::CalkitDialog),
open_ok(true),
short_ok(true),
load_ok(true),
through_ok(true),
editKit(c)
{
ui->setupUi(this);
ui->OpenType->setId(ui->open_coefficients, 0);
ui->OpenType->setId(ui->open_measurement, 1);
ui->ShortType->setId(ui->short_coefficients, 0);
ui->ShortType->setId(ui->short_measurement, 1);
ui->LoadType->setId(ui->load_coefficients, 0);
ui->LoadType->setId(ui->load_measurement, 1);
ui->ThroughType->setId(ui->through_coefficients, 0);
ui->ThroughType->setId(ui->through_measurement, 1);
ui->open_touchstone->setPorts(1);
ui->short_touchstone->setPorts(1);
ui->load_touchstone->setPorts(1);
ui->through_touchstone->setPorts(2);
editKit.clearTouchstoneCache();
ownKit = editKit;
updateEntries();
auto UpdateStatus = [=]() {
bool ok = true;
if(ui->open_measurement->isChecked() && !ui->open_touchstone->getStatus()) {
ok = false;
}
if(ui->short_measurement->isChecked() && !ui->short_touchstone->getStatus()) {
ok = false;
}
if(ui->load_measurement->isChecked() && !ui->load_touchstone->getStatus()) {
ok = false;
}
if(ui->through_measurement->isChecked() && !ui->through_touchstone->getStatus()) {
ok = false;
}
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ok);
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(ok);
};
connect(ui->open_touchstone, &TouchstoneImport::statusChanged, UpdateStatus);
connect(ui->short_touchstone, &TouchstoneImport::statusChanged, UpdateStatus);
connect(ui->load_touchstone, &TouchstoneImport::statusChanged, UpdateStatus);
connect(ui->through_touchstone, &TouchstoneImport::statusChanged, UpdateStatus);
connect(ui->OpenType, qOverload<int>(&QButtonGroup::buttonClicked), [=](int) {
UpdateStatus();
});
connect(ui->ShortType, qOverload<int>(&QButtonGroup::buttonClicked), [=](int) {
UpdateStatus();
});
connect(ui->LoadType, qOverload<int>(&QButtonGroup::buttonClicked), [=](int) {
UpdateStatus();
});
connect(ui->ThroughType, qOverload<int>(&QButtonGroup::buttonClicked), [=](int) {
UpdateStatus();
});
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [this]() {
parseEntries();
editKit = ownKit;
delete this;
});
connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this]() {
delete this;
});
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){
auto filename = QFileDialog::getOpenFileName(this, "Open calibration kit coefficients", "", "Calibration kit files (*.calkit)", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.length() > 0) {
ownKit = Calkit::fromFile(filename.toStdString());
updateEntries();
}
});
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){
auto filename = QFileDialog::getSaveFileName(this, "Save calibration kit coefficients", "", "Calibration kit files (*.calkit)", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.length() > 0) {
parseEntries();
ownKit.toFile(filename.toStdString());
}
});
}
CalkitDialog::~CalkitDialog()
{
delete ui;
}
void CalkitDialog::parseEntries()
{
// type
ownKit.open_measurements = ui->open_measurement->isChecked();
ownKit.short_measurements = ui->short_measurement->isChecked();
ownKit.load_measurements = ui->load_measurement->isChecked();
ownKit.through_measurements = ui->through_measurement->isChecked();
// coefficients
ownKit.open_Z0 = ui->open_Z0->text().toDouble();
ownKit.open_delay = ui->open_delay->text().toDouble();
ownKit.open_loss = ui->open_loss->text().toDouble();
ownKit.open_C0 = ui->open_C0->text().toDouble();
ownKit.open_C1 = ui->open_C1->text().toDouble();
ownKit.open_C2 = ui->open_C2->text().toDouble();
ownKit.open_C3 = ui->open_C3->text().toDouble();
ownKit.short_Z0 = ui->short_Z0->text().toDouble();
ownKit.short_delay = ui->short_delay->text().toDouble();
ownKit.short_loss = ui->short_loss->text().toDouble();
ownKit.short_L0 = ui->short_L0->text().toDouble();
ownKit.short_L1 = ui->short_L1->text().toDouble();
ownKit.short_L2 = ui->short_L2->text().toDouble();
ownKit.short_L3 = ui->short_L3->text().toDouble();
ownKit.load_Z0 = ui->load_Z0->text().toDouble();
ownKit.through_Z0 = ui->through_Z0->text().toDouble();
ownKit.through_delay = ui->through_delay->text().toDouble();
ownKit.through_loss = ui->through_loss->text().toDouble();
// file
ownKit.open_file = ui->open_touchstone->getFilename().toStdString();
ownKit.short_file = ui->short_touchstone->getFilename().toStdString();
ownKit.load_file = ui->load_touchstone->getFilename().toStdString();
ownKit.through_file = ui->through_touchstone->getFilename().toStdString();
ownKit.open_Sparam = ui->open_touchstone->getPorts()[0];
ownKit.short_Sparam = ui->short_touchstone->getPorts()[0];
ownKit.load_Sparam = ui->load_touchstone->getPorts()[0];
ownKit.through_Sparam1 = ui->through_touchstone->getPorts()[0];
ownKit.through_Sparam2 = ui->through_touchstone->getPorts()[1];
}
void CalkitDialog::updateEntries()
{
// Coefficients
ui->open_Z0->setText(QString::number(ownKit.open_Z0));
ui->open_delay->setText(QString::number(ownKit.open_delay));
ui->open_loss->setText(QString::number(ownKit.open_loss));
ui->open_C0->setText(QString::number(ownKit.open_C0));
ui->open_C1->setText(QString::number(ownKit.open_C1));
ui->open_C2->setText(QString::number(ownKit.open_C2));
ui->open_C3->setText(QString::number(ownKit.open_C3));
ui->short_Z0->setText(QString::number(ownKit.short_Z0));
ui->short_delay->setText(QString::number(ownKit.short_delay));
ui->short_loss->setText(QString::number(ownKit.short_loss));
ui->short_L0->setText(QString::number(ownKit.short_L0));
ui->short_L1->setText(QString::number(ownKit.short_L1));
ui->short_L2->setText(QString::number(ownKit.short_L2));
ui->short_L3->setText(QString::number(ownKit.short_L3));
ui->load_Z0->setText(QString::number(ownKit.load_Z0));
ui->through_Z0->setText(QString::number(ownKit.through_Z0));
ui->through_delay->setText(QString::number(ownKit.through_delay));
ui->through_loss->setText(QString::number(ownKit.through_loss));
// Measurements
ui->open_touchstone->setFile(QString::fromStdString(ownKit.open_file));
ui->open_touchstone->selectPort(0, ownKit.open_Sparam);
ui->short_touchstone->setFile(QString::fromStdString(ownKit.short_file));
ui->short_touchstone->selectPort(0, ownKit.short_Sparam);
ui->load_touchstone->setFile(QString::fromStdString(ownKit.load_file));
ui->load_touchstone->selectPort(0, ownKit.load_Sparam);
ui->through_touchstone->setFile(QString::fromStdString(ownKit.through_file));
ui->through_touchstone->selectPort(0, ownKit.through_Sparam1);
ui->through_touchstone->selectPort(1, ownKit.through_Sparam2);
// Type
if (ownKit.open_measurements) {
ui->open_measurement->click();
} else {
ui->open_coefficients->click();
}
if (ownKit.short_measurements) {
ui->short_measurement->click();
} else {
ui->short_coefficients->click();
}
if (ownKit.load_measurements) {
ui->load_measurement->click();
} else {
ui->load_coefficients->click();
}
if (ownKit.through_measurements) {
ui->through_measurement->click();
} else {
ui->through_coefficients->click();
}
}

View File

@ -0,0 +1,33 @@
#ifndef CALKITDIALOG_H
#define CALKITDIALOG_H
#include <QDialog>
#include <QAbstractButton>
#include <iostream>
#include <iomanip>
#include "calkit.h"
namespace Ui {
class CalkitDialog;
}
class CalkitDialog : public QDialog
{
Q_OBJECT
public:
explicit CalkitDialog(Calkit &c, QWidget *parent = nullptr);
~CalkitDialog();
private:
void parseEntries();
void updateEntries();
Ui::CalkitDialog *ui;
bool open_ok, short_ok, load_ok, through_ok;
Calkit ownKit;
Calkit &editKit;
};
#endif // CALKITDIALOG_H

View File

@ -0,0 +1,601 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CalkitDialog</class>
<widget class="QDialog" name="CalkitDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1121</width>
<height>345</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Calibration Kit Coefficients</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_17">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_7">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Open</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="open_coefficients">
<property name="text">
<string>Coefficients</string>
</property>
<attribute name="buttonGroup">
<string notr="true">OpenType</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="open_measurement">
<property name="text">
<string>Measurement file</string>
</property>
<attribute name="buttonGroup">
<string notr="true">OpenType</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="open_stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Offset delay [ps]:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="open_delay"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Offset loss [GΩ/s]: </string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="open_loss"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>C0 [10&lt;sup&gt;-15&lt;/sup&gt;F]:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="open_C0"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>C1 [10&lt;sup&gt;-27&lt;/sup&gt;F/Hz]:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="open_C1"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>C2 [10&lt;sup&gt;-36&lt;/sup&gt;F/Hz&lt;sup&gt;2&lt;/sup&gt;]:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="open_C2"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>C0 [10&lt;sup&gt;-45&lt;/sup&gt;F/Hz&lt;sup&gt;3&lt;/sup&gt;]:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="open_C3"/>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="open_Z0"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Z0 [Ω]:</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0">
<item>
<widget class="TouchstoneImport" name="open_touchstone" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_8">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Short</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QRadioButton" name="short_coefficients">
<property name="text">
<string>Coefficients</string>
</property>
<attribute name="buttonGroup">
<string notr="true">ShortType</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="short_measurement">
<property name="text">
<string>Measurement file</string>
</property>
<attribute name="buttonGroup">
<string notr="true">ShortType</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="short_stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_3">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Offset delay [ps]:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="short_delay"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Offset loss [GΩ/s]: </string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="short_loss"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;L0 [10&lt;span style=&quot; vertical-align:super;&quot;&gt;-12&lt;/span&gt;F]:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="short_L0"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;L1 [10&lt;span style=&quot; vertical-align:super;&quot;&gt;-24&lt;/span&gt;F/Hz]:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="short_L1"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;L2 [10&lt;span style=&quot; vertical-align:super;&quot;&gt;-33&lt;/span&gt;F/Hz&lt;span style=&quot; vertical-align:super;&quot;&gt;2&lt;/span&gt;]:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="short_L2"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;L3 [10&lt;span style=&quot; vertical-align:super;&quot;&gt;-42&lt;/span&gt;F/Hz&lt;span style=&quot; vertical-align:super;&quot;&gt;3&lt;/span&gt;]:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="short_L3"/>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="short_Z0"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Z0 [Ω]:</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_4">
<layout class="QVBoxLayout" name="verticalLayout_5" stretch="0">
<item>
<widget class="TouchstoneImport" name="short_touchstone" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_19">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Load</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QRadioButton" name="load_coefficients">
<property name="text">
<string>Coefficients</string>
</property>
<attribute name="buttonGroup">
<string notr="true">LoadType</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="load_measurement">
<property name="text">
<string>Measurement file</string>
</property>
<attribute name="buttonGroup">
<string notr="true">LoadType</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="load_stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_5">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_30">
<property name="text">
<string>Z0 [Ω]:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="load_Z0"/>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_6">
<layout class="QVBoxLayout" name="verticalLayout_6" stretch="0">
<item>
<widget class="TouchstoneImport" name="load_touchstone" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLabel" name="label_20">
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Through</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QRadioButton" name="through_coefficients">
<property name="text">
<string>Coefficients</string>
</property>
<attribute name="buttonGroup">
<string notr="true">ThroughType</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="through_measurement">
<property name="text">
<string>Measurement file</string>
</property>
<attribute name="buttonGroup">
<string notr="true">ThroughType</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="through_stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_7">
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_35">
<property name="text">
<string>Z0 [Ω]:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="through_Z0"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Offset delay [ps]:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="through_delay"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Offset loss [GΩ/s]: </string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="through_loss"/>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_8">
<layout class="QVBoxLayout" name="verticalLayout_8" stretch="0">
<item>
<widget class="TouchstoneImport" name="through_touchstone" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Open|QDialogButtonBox::Save</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TouchstoneImport</class>
<extends>QWidget</extends>
<header>CustomWidgets/touchstoneimport.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>open_Z0</tabstop>
<tabstop>open_delay</tabstop>
<tabstop>open_loss</tabstop>
<tabstop>open_C0</tabstop>
<tabstop>open_C1</tabstop>
<tabstop>open_C2</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>OpenType</sender>
<signal>buttonClicked(int)</signal>
<receiver>open_stack</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>141</x>
<y>187</y>
</hint>
</hints>
</connection>
<connection>
<sender>ShortType</sender>
<signal>buttonClicked(int)</signal>
<receiver>short_stack</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>420</x>
<y>187</y>
</hint>
</hints>
</connection>
<connection>
<sender>LoadType</sender>
<signal>buttonClicked(int)</signal>
<receiver>load_stack</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>699</x>
<y>187</y>
</hint>
</hints>
</connection>
<connection>
<sender>ThroughType</sender>
<signal>buttonClicked(int)</signal>
<receiver>through_stack</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>978</x>
<y>187</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="LoadType"/>
<buttongroup name="OpenType"/>
<buttongroup name="ShortType"/>
<buttongroup name="ThroughType"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,88 @@
#include "measurementmodel.h"
#include "../unit.h"
#include <algorithm>
MeasurementModel::MeasurementModel(Calibration *cal, std::vector<Calibration::Measurement> measurements) :
QAbstractTableModel(),
cal(cal),
measurements(measurements)
{
}
int MeasurementModel::rowCount(const QModelIndex &) const
{
return measurements.size();
}
int MeasurementModel::columnCount(const QModelIndex &) const
{
return ColIndexLast;
}
QVariant MeasurementModel::data(const QModelIndex &index, int role) const
{
auto info = cal->getMeasurementInfo(measurements[index.row()]);
if(role == Qt::DisplayRole) {
switch(index.column()) {
case ColIndexName:
return info.name;
break;
case ColIndexDescription:
return info.prerequisites;
break;
case ColIndexData:
if(info.points > 0) {
QString data = QString::number(info.points);
data.append(" points from ");
data.append(Unit::ToString(info.fmin, "Hz", " kMG"));
data.append(" to ");
data.append(Unit::ToString(info.fmax, "Hz", " kMG"));
return data;
} else {
return "Not available";
}
break;
case ColIndexDate:
return info.timestamp.toString("dd.MM.yyyy hh:mm:ss");
break;
}
} else if(role == Qt::SizeHintRole) {
switch(index.column()) {
case ColIndexName: return 200; break;
case ColIndexDescription: return 500; break;
case ColIndexData: return 300; break;
case ColIndexDate: return 300; break;
case ColIndexStatusSymbol: return 150; break;
default: return QVariant(); break;
}
}
return QVariant();
}
QVariant MeasurementModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch(section) {
case ColIndexName: return "Type"; break;
case ColIndexDescription: return "Prerequisites"; break;
case ColIndexData: return "Statistics"; break;
case ColIndexDate: return "Timestamp"; break;
case ColIndexStatusSymbol: return "Status"; break;
default: return QVariant(); break;
}
} else {
return QVariant();
}
}
void MeasurementModel::measurementUpdated(Calibration::Measurement m)
{
// find correct index in vector
auto it = std::find(measurements.begin(), measurements.end(), m);
if(it != measurements.end()) {
int row = it - measurements.begin();
emit dataChanged(index(row, 0), index(row, ColIndexLast - 1));
}
}

View File

@ -0,0 +1,36 @@
#ifndef MEASUREMENTMODEL_H
#define MEASUREMENTMODEL_H
#include <QObject>
#include <QWidget>
#include <QAbstractTableModel>
#include "calibration.h"
class MeasurementModel : public QAbstractTableModel
{
Q_OBJECT
public:
MeasurementModel(Calibration *cal, std::vector<Calibration::Measurement> measurements);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
public slots:
void measurementUpdated(Calibration::Measurement m);
private:
enum {
ColIndexName,
ColIndexDescription,
ColIndexData,
ColIndexDate,
ColIndexStatusSymbol,
ColIndexLast
};
Calibration *cal;
std::vector<Calibration::Measurement> measurements;
};
#endif // MEASUREMENTMODEL_H

View File

@ -0,0 +1,79 @@
#include "siunitedit.h"
#include <QDoubleValidator>
#include <unit.h>
#include <QEvent>
#include <QKeyEvent>
SIUnitEdit::SIUnitEdit(QString unit, QString prefixes, int precision, QWidget *parent)
: QLineEdit(parent)
{
this->unit = unit;
this->prefixes = prefixes;
this->precision = precision;
setAlignment(Qt::AlignCenter);
installEventFilter(this);
setValidator(new QDoubleValidator);
connect(this, &QLineEdit::editingFinished, [this]() {
parseNewValue(1.0);
});
}
SIUnitEdit::SIUnitEdit(QWidget *parent)
: SIUnitEdit("", " ", 4, parent)
{
}
void SIUnitEdit::setValue(double value)
{
setValueQuiet(value);
emit valueChanged(value);
}
bool SIUnitEdit::eventFilter(QObject *, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
int key = static_cast<QKeyEvent *>(event)->key();
if(key == Qt::Key_Escape) {
// abort editing process and set old value
setValueQuiet(_value);
return true;
}
if(key == Qt::Key_Return) {
// use new value without prefix
parseNewValue(1.0);
return true;
}
auto mod = static_cast<QKeyEvent *>(event)->modifiers();
if (!(mod & Qt::ShiftModifier)) {
key = tolower(key);
}
if(key <= 255 && prefixes.indexOf(key) >= 0) {
// a valid prefix key was pressed
parseNewValue(Unit::SIPrefixToFactor(key));
return true;
}
} else if(event->type() == QEvent::FocusOut) {
if(!text().isEmpty()) {
parseNewValue(1.0);
} else {
setValueQuiet(_value);
}
}
return false;
}
void SIUnitEdit::setValueQuiet(double value)
{
_value = value;
clear();
setPlaceholderText(Unit::ToString(value, unit, prefixes, precision));
clearFocus();
}
void SIUnitEdit::parseNewValue(double factor)
{
double v = text().toDouble() * factor;
setValue(v);
}

View File

@ -0,0 +1,31 @@
#ifndef SIUNITEDIT_H
#define SIUNITEDIT_H
#include <QLineEdit>
class SIUnitEdit : public QLineEdit
{
Q_OBJECT
public:
SIUnitEdit(QString unit = QString(), QString prefixes = " ", int precision = 0, QWidget *parent = nullptr);
SIUnitEdit(QWidget *parent);
void setUnit(QString unit) { this->unit = unit; setValueQuiet(_value); }
void setPrefixes(QString prefixes) { this->prefixes = prefixes; setValueQuiet(_value); }
void setPrecision(int precision) { this->precision = precision; setValueQuiet(_value); }
double value() { return _value; }
public slots:
void setValue(double value);
void setValueQuiet(double value);
signals:
void valueChanged(double newvalue);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private:
void parseNewValue(double factor);
QString unit, prefixes;
int precision;
double _value;
};
#endif // SIUNITEDIT_H

View File

@ -0,0 +1,141 @@
#include "tilewidget.h"
#include "ui_tilewidget.h"
#include <QDebug>
#include "Traces/tracebodeplot.h"
#include "Traces/tracesmithchart.h"
TileWidget::TileWidget(TraceModel &model, QWidget *parent) :
QWidget(parent),
ui(new Ui::TileWidget),
splitter(0),
isSplit(false),
parent(0),
child1(0),
child2(0),
hasContent(false),
content(0),
model(model)
{
ui->setupUi(this);
auto layout = new QGridLayout;
layout->setContentsMargins(0,0,0,0);
ui->ContentPage->setLayout(layout);
ui->bClose->setVisible(false);
}
TileWidget::~TileWidget()
{
delete ui;
}
void TileWidget::splitVertically()
{
if(isSplit) {
return;
}
isSplit = true;
splitter = new QSplitter(Qt::Vertical);
split();
}
void TileWidget::splitHorizontally()
{
if(isSplit) {
return;
}
isSplit = true;
splitter = new QSplitter(Qt::Horizontal);
split();
}
void TileWidget::closeTile()
{
if(!parent) {
// Unable to close toplevel tile
return;
}
auto pTile = parent;
TileWidget *absorbedTile;
if(this == parent->child1) {
absorbedTile = parent->child2;
} else {
absorbedTile = parent->child1;
}
delete this;
if(absorbedTile->isSplit) {
pTile->isSplit = true;
pTile->child1 = absorbedTile->child1;
pTile->child2 = absorbedTile->child2;
pTile->child1->parent = pTile;
pTile->child2->parent = pTile;
pTile->ui->ContentPage->layout()->addWidget(absorbedTile->splitter);
auto oldsplitter = pTile->splitter;
pTile->splitter = absorbedTile->splitter;
delete absorbedTile;
delete oldsplitter;
} else if(absorbedTile->hasContent) {
pTile->setContent(absorbedTile->content);
delete absorbedTile;
pTile->isSplit = false;
delete pTile->splitter;
pTile->splitter = nullptr;
} else {
delete absorbedTile;
pTile->isSplit = false;
pTile->hasContent = false;
delete pTile->splitter;
pTile->ui->stack->setCurrentWidget(pTile->ui->TilePage);
}
}
void TileWidget::setPlot(TracePlot *plot)
{
if(!isSplit && !hasContent) {
setContent(plot);
}
}
TileWidget::TileWidget(TraceModel &model, TileWidget &parent)
: TileWidget(model)
{
this->parent = &parent;
ui->bClose->setVisible(true);
}
void TileWidget::split()
{
splitter->setHandleWidth(0);
child1 = new TileWidget(model, *this);
child2 = new TileWidget(model, *this);
splitter->addWidget(child1);
splitter->addWidget(child2);
ui->ContentPage->layout()->addWidget(splitter);
ui->stack->setCurrentWidget(ui->ContentPage);
}
void TileWidget::setContent(TracePlot *plot)
{
content = plot;
hasContent = true;
ui->ContentPage->layout()->addWidget(plot);
ui->stack->setCurrentWidget(ui->ContentPage);
connect(content, &TracePlot::deleted, this, &TileWidget::traceDeleted);
}
void TileWidget::on_bSmithchart_clicked()
{
setContent(new TraceSmithChart(model));
}
void TileWidget::on_bBodeplot_clicked()
{
setContent(new TraceBodePlot(model));
}
void TileWidget::traceDeleted(TracePlot *)
{
ui->stack->setCurrentWidget(ui->TilePage);
hasContent = false;
content = nullptr;
}

View File

@ -0,0 +1,49 @@
#ifndef TILEWIDGET_H
#define TILEWIDGET_H
#include <QWidget>
#include "Traces/traceplot.h"
#include <QSplitter>
#include "Traces/tracemodel.h"
namespace Ui {
class TileWidget;
}
class TileWidget : public QWidget
{
Q_OBJECT
public:
explicit TileWidget(TraceModel &model, QWidget *parent = nullptr);
~TileWidget();
TileWidget *Child1() { return child1; };
TileWidget *Child2() { return child2; };
public slots:
void splitVertically();
void splitHorizontally();
void closeTile();
void setPlot(TracePlot *plot);
private slots:
void on_bSmithchart_clicked();
void on_bBodeplot_clicked();
void traceDeleted(TracePlot *t);
private:
TileWidget(TraceModel &model, TileWidget &parent);
void split();
void setContent(TracePlot *plot);
void setChild();
Ui::TileWidget *ui;
QSplitter *splitter;
bool isSplit;
TileWidget *parent;
TileWidget *child1, *child2;
bool hasContent;
TracePlot *content;
TraceModel &model;
};
#endif // TILEWIDGET_H

View File

@ -0,0 +1,266 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TileWidget</class>
<widget class="QWidget" name="TileWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>465</width>
<height>350</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="TilePage">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Plots</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="bSmithchart">
<property name="text">
<string>Smithchart</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bBodeplot">
<property name="text">
<string>Bodeplot</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Split Tile</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPushButton" name="bSplitV">
<property name="text">
<string>Vertical</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/vertical.svg</normaloff>:/icons/vertical.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bSplitH">
<property name="text">
<string>Horizontal</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/horizontal.svg</normaloff>:/icons/horizontal.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="bClose">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Close Tile</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/close.svg</normaloff>:/icons/close.svg</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="ContentPage"/>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../icons.qrc"/>
</resources>
<connections>
<connection>
<sender>bClose</sender>
<signal>clicked()</signal>
<receiver>TileWidget</receiver>
<slot>closeTile()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>74</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>bSplitH</sender>
<signal>clicked()</signal>
<receiver>TileWidget</receiver>
<slot>splitHorizontally()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>224</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>bSplitV</sender>
<signal>clicked()</signal>
<receiver>TileWidget</receiver>
<slot>splitVertically()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>149</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>splitVertically()</slot>
<slot>splitHorizontally()</slot>
<slot>closeTile()</slot>
</slots>
</ui>

View File

@ -0,0 +1,66 @@
#include "toggleswitch.h"
#include <QPainter>
#include <QMouseEvent>
ToggleSwitch::ToggleSwitch(QWidget *parent, bool state) : QAbstractButton(parent),
_height(24),
_width(128),
state(state)
{
}
QSize ToggleSwitch::sizeHint() const
{
return QSize(_width, _height);
}
void ToggleSwitch::toggle()
{
state = !state;
emit toggled(state);
}
void ToggleSwitch::setState(bool state)
{
if(this->state != state) {
this->state = state;
emit toggled(state);
}
}
void ToggleSwitch::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.setPen(Qt::NoPen);
p.setBrush(Qt::black);
p.setOpacity(isEnabled() ? 0.38 : 0.12);
p.setRenderHint(QPainter::Antialiasing, true);
p.drawRoundedRect(QRect(0, 0, width(), height()), 8.0, 8.0);
p.setOpacity(1.0);
QRect rect;
QString statename;
if(state) {
p.setBrush(isEnabled() ? Qt::darkGreen : Qt::gray);
rect = QRect(width()/2, 0, width()/2, height());
statename = "ON";
} else {
p.setBrush(isEnabled() ? QColor("#AA090E") : Qt::lightGray);
rect = QRect(0, 0, width()/2, height());
statename = "OFF";
}
p.drawRoundedRect(rect, 8.0, 8.0);
QFont font = p.font();
p.setPen(Qt::SolidLine);
p.setPen(isEnabled() ? Qt::black : Qt::gray);
p.drawText(rect, Qt::AlignCenter, statename);
}
void ToggleSwitch::mouseReleaseEvent(QMouseEvent *e)
{
if(e->button() & Qt::LeftButton) {
toggle();
}
QAbstractButton::mouseReleaseEvent(e);
}

View File

@ -0,0 +1,28 @@
#ifndef TOGGLESWITCH_H
#define TOGGLESWITCH_H
#include <QAbstractButton>
class ToggleSwitch : public QAbstractButton
{
Q_OBJECT
public:
ToggleSwitch(QWidget* parent = nullptr, bool state = false);
QSize sizeHint() const override;
signals:
void toggled(bool newstate);
public slots:
void toggle();
void setState(bool state);
protected:
void paintEvent(QPaintEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
private:
int _height, _width;
bool state;
};
#endif // TOGGLESWITCH_H

View File

@ -0,0 +1,185 @@
#include "touchstoneimport.h"
#include "ui_touchstoneimport.h"
#include <QFileDialog>
#include <QDebug>
#include <QtGlobal>
using namespace std;
TouchstoneImport::TouchstoneImport(QWidget *parent, int ports) :
QWidget(parent),
ui(new Ui::TouchstoneImport),
touchstone(ports),
status(false)
{
ui->setupUi(this);
connect(ui->browse, &QPushButton::clicked, this, &TouchstoneImport::evaluateFile);
ui->port1Group->setId(ui->port1_1, 0);
ui->port1Group->setId(ui->port1_2, 1);
ui->port1Group->setId(ui->port1_3, 2);
ui->port1Group->setId(ui->port1_4, 3);
ui->port2Group->setId(ui->port2_1, 0);
ui->port2Group->setId(ui->port2_2, 1);
ui->port2Group->setId(ui->port2_3, 2);
ui->port2Group->setId(ui->port2_4, 3);
// prevent selection of same port for port1 and 2
connect(ui->port1Group, qOverload<int>(&QButtonGroup::buttonClicked), [=](int id) {
preventCollisionWithGroup(ui->port2Group, id);
});
connect(ui->port2Group, qOverload<int>(&QButtonGroup::buttonClicked), [=](int id) {
preventCollisionWithGroup(ui->port1Group, id);
});
setPorts(ports);
}
TouchstoneImport::~TouchstoneImport()
{
delete ui;
}
bool TouchstoneImport::getStatus()
{
return status;
}
Touchstone TouchstoneImport::getTouchstone()
{
if(required_ports == 1) {
auto t1 = touchstone;
t1.reduceTo1Port(ui->port1Group->checkedId());
return t1;
} else if(required_ports == 2) {
auto t2 = touchstone;
t2.reduceTo2Port(ui->port1Group->checkedId(), ui->port2Group->checkedId());
return t2;
}
return touchstone;
}
void TouchstoneImport::setPorts(int ports)
{
required_ports = ports;
ui->port1Widget->setVisible(ports >= 1);
ui->port2Widget->setVisible(ports >= 2);
}
QString TouchstoneImport::getFilename()
{
return ui->file->text();
}
void TouchstoneImport::selectPort(int destination, int source)
{
switch(destination) {
case 0:
ui->port1Group->button(source)->setChecked(true);
preventCollisionWithGroup(ui->port2Group, source);
break;
case 1:
ui->port2Group->button(source)->setChecked(true);
preventCollisionWithGroup(ui->port1Group, source);
break;
}
}
std::vector<int> TouchstoneImport::getPorts()
{
vector<int> ret;
if(required_ports >= 1) {
ret.push_back(ui->port1Group->checkedId());
}
if(required_ports >= 2) {
ret.push_back(ui->port2Group->checkedId());
}
return ret;
}
void TouchstoneImport::setFile(QString filename)
{
ui->file->setText(filename);
evaluateFile();
}
void TouchstoneImport::on_browse_clicked()
{
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
if (filename.length() > 0) {
ui->file->setText(filename);
evaluateFile();
}
}
void TouchstoneImport::evaluateFile()
{
bool new_status = false;
ui->port1_1->setEnabled(false);
ui->port1_2->setEnabled(false);
ui->port1_3->setEnabled(false);
ui->port1_4->setEnabled(false);
if (required_ports != 1) {
ui->port2_1->setEnabled(false);
ui->port2_2->setEnabled(false);
ui->port2_3->setEnabled(false);
ui->port2_4->setEnabled(false);
}
ui->points->setText("");
ui->lowerFreq->setText("");
ui->upperFreq->setText("");
ui->status->clear();
try {
touchstone = Touchstone::fromFile(ui->file->text().toStdString());
if (required_ports > 0 && touchstone.ports() < (unsigned int) required_ports) {
throw runtime_error("Not enough ports in file");
}
ui->port1_1->setEnabled(touchstone.ports() >= 1);
ui->port1_2->setEnabled(touchstone.ports() >= 2);
ui->port1_3->setEnabled(touchstone.ports() >= 3);
ui->port1_4->setEnabled(touchstone.ports() >= 4);
if (required_ports != 1) {
ui->port2_1->setEnabled(touchstone.ports() >= 1);
ui->port2_2->setEnabled(touchstone.ports() >= 2);
ui->port2_3->setEnabled(touchstone.ports() >= 3);
ui->port2_4->setEnabled(touchstone.ports() >= 4);
}
ui->points->setText(QString::number(touchstone.points()));
ui->lowerFreq->setText(QString::number(touchstone.minFreq()));
ui->upperFreq->setText(QString::number(touchstone.maxFreq()));
if(ui->port1Group->checkedId() == -1 || !ui->port1Group->checkedButton()->isEnabled()) {
// no or invalid S parameter selected
ui->port1_1->setChecked(true);
}
if (required_ports != 1) {
preventCollisionWithGroup(ui->port2Group, 0);
}
new_status = true;
} catch (const exception &e) {
ui->status->setText(e.what());
}
if (new_status != status) {
status = new_status;
emit statusChanged(status);
}
emit filenameChanged(ui->file->text());
}
void TouchstoneImport::preventCollisionWithGroup(QButtonGroup *group, int id)
{
for(unsigned int i=0;i<touchstone.ports();i++) {
group->button(i)->setEnabled(true);
}
// change selection in second group and mark invalid
group->button(id)->setEnabled(false);
group->button(id)->setChecked(false);
if (group->checkedId() == -1 || group->checkedId() == id) {
for(int i=0;i<4;i++) {
if(i == id) {
continue;
}
if(group->button(i)->isEnabled()) {
group->button(i)->setChecked(true);
break;
}
}
}
}

View File

@ -0,0 +1,46 @@
#ifndef TOUCHSTONEIMPORT_H
#define TOUCHSTONEIMPORT_H
#include <QWidget>
#include "touchstone.h"
#include <QButtonGroup>
namespace Ui {
class TouchstoneImport;
}
class TouchstoneImport : public QWidget
{
Q_OBJECT
public:
explicit TouchstoneImport(QWidget *parent = nullptr, int ports = 0);
~TouchstoneImport();
bool getStatus();
Touchstone getTouchstone();
void setPorts(int ports);
QString getFilename();
void selectPort(int destination, int source);
std::vector<int> getPorts();
signals:
void statusChanged(bool status);
void filenameChanged(QString name);
public slots:
void setFile(QString filename);
private slots:
void on_browse_clicked();
private:
void evaluateFile();
void preventCollisionWithGroup(QButtonGroup *group, int id);
Ui::TouchstoneImport *ui;
int required_ports;
Touchstone touchstone;
bool status;
};
#endif // TOUCHSTONEIMPORT_H

View File

@ -0,0 +1,312 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TouchstoneImport</class>
<widget class="QWidget" name="TouchstoneImport">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>228</width>
<height>227</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="file">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browse">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="status">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>239</red>
<green>41</green>
<blue>41</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>239</red>
<green>41</green>
<blue>41</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>190</red>
<green>190</green>
<blue>190</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="port1Widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Port 1:</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="port1_1">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>1</string>
</property>
<attribute name="buttonGroup">
<string notr="true">port1Group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="port1_2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>2</string>
</property>
<attribute name="buttonGroup">
<string notr="true">port1Group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="port1_3">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>3</string>
</property>
<attribute name="buttonGroup">
<string notr="true">port1Group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="port1_4">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>4</string>
</property>
<attribute name="buttonGroup">
<string notr="true">port1Group</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="port2Widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="port2Selector">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Port 2:</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="port2_1">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>1</string>
</property>
<attribute name="buttonGroup">
<string notr="true">port2Group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="port2_2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>2</string>
</property>
<attribute name="buttonGroup">
<string notr="true">port2Group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="port2_3">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>3</string>
</property>
<attribute name="buttonGroup">
<string notr="true">port2Group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="port2_4">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>4</string>
</property>
<attribute name="buttonGroup">
<string notr="true">port2Group</string>
</attribute>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Points:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="points">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Lower Frequency:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lowerFreq">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Upper Frequency:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="upperFreq">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="port2Group"/>
<buttongroup name="port1Group"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,408 @@
#include "device.h"
#include <signal.h>
#include <QDebug>
#include <QString>
#include <QMessageBox>
#include <mutex>
using namespace std;
Device::Device(QString serial)
{
qDebug() << "Starting device connection...";
m_handle = nullptr;
libusb_init(&m_context);
SearchDevices([=](libusb_device_handle *handle, QString found_serial) -> bool {
if(serial.isEmpty() || serial == found_serial) {
// accept connection to this device
m_serial = found_serial;
m_handle = handle;
// abort device search
return false;
} else {
// not the requested device, continue search
return true;
}
}, m_context);
if(!m_handle) {
QString message = "No device found";
auto msg = new QMessageBox(QMessageBox::Icon::Warning, "Error opening device", message);
msg->exec();
libusb_exit(m_context);
throw std::runtime_error(message.toStdString());
return;
}
// Found the correct device, now connect
/* claim the interfaces */
for (int if_num = 0; if_num < 1; if_num++) {
int ret = libusb_claim_interface(m_handle, if_num);
if (ret < 0) {
libusb_close(m_handle);
/* Failed to open */
QString message = "Failed to claim interface: \"";
message.append(libusb_strerror((libusb_error) ret));
message.append("\" Maybe you are already connected to this device?");
qWarning() << message;
auto msg = new QMessageBox(QMessageBox::Icon::Warning, "Error opening device", message);
msg->exec();
libusb_exit(m_context);
throw std::runtime_error(message.toStdString());
}
}
qInfo() << "USB connection established" << flush;
m_connected = true;
m_receiveThread = new std::thread(&Device::USBHandleThread, this);
dataBuffer = new USBInBuffer(m_handle, EP_Data_In_Addr, 2048);
logBuffer = new USBInBuffer(m_handle, EP_Log_In_Addr, 2048);
connect(dataBuffer, &USBInBuffer::DataReceived, this, &Device::ReceivedData, Qt::DirectConnection);
connect(dataBuffer, &USBInBuffer::TransferError, this, &Device::ConnectionLost);
connect(logBuffer, &USBInBuffer::DataReceived, this, &Device::ReceivedLog, Qt::DirectConnection);
}
Device::~Device()
{
if(m_connected) {
delete dataBuffer;
delete logBuffer;
m_connected = false;
for (int if_num = 0; if_num < 1; if_num++) {
int ret = libusb_release_interface(m_handle, if_num);
if (ret < 0) {
qCritical() << "Error releasing interface" << libusb_error_name(ret);
}
}
libusb_close(m_handle);
m_receiveThread->join();
libusb_exit(m_context);
}
}
bool Device::Configure(Protocol::SweepSettings settings)
{
if(m_connected) {
unsigned char buffer[128];
Protocol::PacketInfo p;
p.type = Protocol::PacketType::SweepSettings;
p.settings = settings;
unsigned int length = Protocol::EncodePacket(p, buffer, sizeof(buffer));
if(!length) {
qCritical() << "Failed to encode packet";
return false;
}
int actual_length;
auto ret = libusb_bulk_transfer(m_handle, EP_Data_Out_Addr, buffer, length, &actual_length, 0);
if(ret < 0) {
qCritical() << "Error sending data: "
<< libusb_strerror((libusb_error) ret);
return false;
}
return true;
} else {
return false;
}
}
bool Device::SetManual(Protocol::ManualControl manual)
{
if(m_connected) {
unsigned char buffer[128];
Protocol::PacketInfo p;
p.type = Protocol::PacketType::ManualControl;
p.manual = manual;
unsigned int length = Protocol::EncodePacket(p, buffer, sizeof(buffer));
if(!length) {
qCritical() << "Failed to encode packet";
return false;
}
int actual_length;
auto ret = libusb_bulk_transfer(m_handle, EP_Data_Out_Addr, buffer, length, &actual_length, 0);
if(ret < 0) {
qCritical() << "Error sending data: "
<< libusb_strerror((libusb_error) ret);
return false;
}
return true;
} else {
return false;
}
}
bool Device::SendFirmwareChunk(Protocol::FirmwarePacket &fw)
{
if(m_connected) {
unsigned char buffer[Protocol::FirmwareChunkSize + 4 + 8];
Protocol::PacketInfo p;
p.type = Protocol::PacketType::FirmwarePacket;
p.firmware = fw;
unsigned int length = Protocol::EncodePacket(p, buffer, sizeof(buffer));
if(!length) {
qCritical() << "Failed to encode packet";
return false;
}
int actual_length;
auto ret = libusb_bulk_transfer(m_handle, EP_Data_Out_Addr, buffer, length, &actual_length, 0);
if(ret < 0) {
qCritical() << "Error sending data: "
<< libusb_strerror((libusb_error) ret);
return false;
}
return true;
} else {
return false;
}
}
std::vector<QString> Device::GetDevices()
{
std::vector<QString> serials;
libusb_context *ctx;
libusb_init(&ctx);
SearchDevices([&serials](libusb_device_handle *, QString serial) -> bool {
serials.push_back(serial);
return true;
}, ctx);
libusb_exit(ctx);
return serials;
}
void Device::USBHandleThread()
{
qInfo() << "Receive thread started" << flush;
while (m_connected) {
libusb_handle_events(m_context);
}
qDebug() << "Disconnected, receive thread exiting";
}
void Device::SearchDevices(std::function<bool (libusb_device_handle *, QString)> foundCallback, libusb_context *context)
{
libusb_device **devList;
auto ndevices = libusb_get_device_list(context, &devList);
for (ssize_t idx = 0; idx < ndevices; idx++) {
int ret;
libusb_device *device = devList[idx];
libusb_device_descriptor desc = {};
ret = libusb_get_device_descriptor(device, &desc);
if (ret) {
/* some error occured */
qCritical() << "Failed to get device descriptor: "
<< libusb_strerror((libusb_error) ret);
continue;
}
if (desc.idVendor != VID || desc.idProduct != PID) {
/* Not the correct IDs */
continue;
}
/* Try to open the device */
libusb_device_handle *handle = nullptr;
ret = libusb_open(device, &handle);
if (ret) {
/* Failed to open */
QString message = "Found potential device but failed to open usb connection: \"";
message.append(libusb_strerror((libusb_error) ret));
message.append("\" On Linux this is most likely caused by a missing udev rule. On Windows it could be a missing driver. Try installing the WinUSB driver using Zadig (https://zadig.akeo.ie/)");
qWarning() << message;
auto msg = new QMessageBox(QMessageBox::Icon::Warning, "Error opening device", message);
msg->exec();
continue;
}
char c_product[256];
char c_serial[256];
libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber,
(unsigned char*) c_serial, sizeof(c_serial));
ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct,
(unsigned char*) c_product, sizeof(c_product));
if (ret > 0) {
/* managed to read the product string */
QString product(c_product);
qDebug() << "Opened device: " << product;
if (product == "VNA") {
// this is a match
if(!foundCallback(handle, QString(c_serial))) {
// abort search
break;
}
}
} else {
qWarning() << "Failed to get product descriptor: "
<< libusb_strerror((libusb_error) ret);
}
libusb_close(handle);
}
libusb_free_device_list(devList, 1);
}
Protocol::DeviceInfo Device::getLastInfo() const
{
return lastInfo;
}
QString Device::getLastDeviceInfoString()
{
QString ret;
if(!lastInfoValid) {
ret.append("No device information available yet");
} else {
ret.append("HW Rev.");
ret.append(lastInfo.HW_Revision);
ret.append(" FW "+QString::number(lastInfo.FW_major)+"."+QString::number(lastInfo.FW_minor).rightJustified(2, '0'));
ret.append(" Temps: "+QString::number(lastInfo.temperatures.source)+"°C/"+QString::number(lastInfo.temperatures.LO1)+"°C/"+QString::number(lastInfo.temperatures.MCU)+"°C");
ret.append(" Reference:");
if(lastInfo.extRefInUse) {
ret.append("External");
} else {
ret.append("Internal");
if(lastInfo.extRefAvailable) {
ret.append(" (External available)");
}
}
}
return ret;
}
void Device::ReceivedData()
{
Protocol::PacketInfo packet;
uint16_t handled_len;
do {
handled_len = Protocol::DecodeBuffer(dataBuffer->getBuffer(), dataBuffer->getReceived(), &packet);
dataBuffer->removeBytes(handled_len);
if(packet.type == Protocol::PacketType::Datapoint) {
emit DatapointReceived(packet.datapoint);
} else if(packet.type == Protocol::PacketType::Status) {
qDebug() << "Got status";
emit ManualStatusReceived(packet.status);
} else if(packet.type == Protocol::PacketType::DeviceInfo) {
lastInfo = packet.info;
lastInfoValid = true;
emit DeviceInfoUpdated();
} else if(packet.type == Protocol::PacketType::Ack) {
emit AckReceived();
}
} while (handled_len > 0);
}
void Device::ReceivedLog()
{
uint16_t handled_len;
do {
handled_len = 0;
auto firstLinebreak = (uint8_t*) memchr(logBuffer->getBuffer(), '\n', logBuffer->getReceived());
if(firstLinebreak) {
handled_len = firstLinebreak - logBuffer->getBuffer();
auto line = QString::fromLatin1((const char*) logBuffer->getBuffer(), handled_len - 1);
emit LogLineReceived(line);
logBuffer->removeBytes(handled_len + 1);
}
} while(handled_len > 0);
}
QString Device::serial() const
{
return m_serial;
}
USBInBuffer::USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size) :
buffer_size(buffer_size),
received_size(0),
inCallback(false)
{
buffer = new unsigned char[buffer_size];
transfer = libusb_alloc_transfer(0);
libusb_fill_bulk_transfer(transfer, handle, endpoint, buffer, 64, CallbackTrampoline, this, 100);
libusb_submit_transfer(transfer);
}
USBInBuffer::~USBInBuffer()
{
if(transfer) {
qDebug() << "Start cancellation";
libusb_cancel_transfer(transfer);
// wait for cancellation to complete
mutex mtx;
unique_lock<mutex> lck(mtx);
cv.wait(lck);
qDebug() << "Cancellation complete";
}
delete buffer;
}
void USBInBuffer::removeBytes(int handled_bytes)
{
if(!inCallback) {
throw runtime_error("Removing of bytes is only allowed from within receive callback");
}
if(handled_bytes >= received_size) {
received_size = 0;
} else {
// not removing all bytes, have to move remaining data to the beginning of the buffer
memmove(buffer, &buffer[handled_bytes], received_size - handled_bytes);
received_size -= handled_bytes;
}
}
int USBInBuffer::getReceived() const
{
return received_size;
}
void USBInBuffer::Callback(libusb_transfer *transfer)
{
switch(transfer->status) {
case LIBUSB_TRANSFER_COMPLETED:
received_size += transfer->actual_length;
inCallback = true;
emit DataReceived();
inCallback = false;
break;
case LIBUSB_TRANSFER_ERROR:
case LIBUSB_TRANSFER_NO_DEVICE:
case LIBUSB_TRANSFER_OVERFLOW:
case LIBUSB_TRANSFER_STALL:
qCritical() << "LIBUSB_TRANSFER_ERROR";
libusb_free_transfer(transfer);
this->transfer = nullptr;
emit TransferError();
return;
break;
case LIBUSB_TRANSFER_TIMED_OUT:
// nothing to do
break;
case LIBUSB_TRANSFER_CANCELLED:
// destructor called, do not resubmit
libusb_free_transfer(transfer);
this->transfer = nullptr;
cv.notify_all();
return;
break;
}
// Resubmit the transfer
transfer->buffer = &buffer[received_size];
libusb_submit_transfer(transfer);
}
void USBInBuffer::CallbackTrampoline(libusb_transfer *transfer)
{
auto usb = (USBInBuffer*) transfer->user_data;
usb->Callback(transfer);
}
uint8_t *USBInBuffer::getBuffer() const
{
return buffer;
}

View File

@ -0,0 +1,93 @@
#ifndef DEVICE_H
#define DEVICE_H
#include "../VNA_embedded/Application/Communication/Protocol.hpp"
#include <functional>
#include <libusb-1.0/libusb.h>
#include <thread>
#include <QObject>
#include <condition_variable>
Q_DECLARE_METATYPE(Protocol::Datapoint);
Q_DECLARE_METATYPE(Protocol::ManualStatus);
Q_DECLARE_METATYPE(Protocol::DeviceInfo);
class USBInBuffer : public QObject {
Q_OBJECT;
public:
USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size);
~USBInBuffer();
void removeBytes(int handled_bytes);
int getReceived() const;
uint8_t *getBuffer() const;
signals:
void DataReceived();
void TransferError();
private:
void Callback(libusb_transfer *transfer);
static void LIBUSB_CALL CallbackTrampoline(libusb_transfer *transfer);
libusb_transfer *transfer;
unsigned char *buffer;
int buffer_size;
int received_size;
bool inCallback;
std::condition_variable cv;
};
class Device : public QObject
{
Q_OBJECT
public:
// connect to a VNA device. If serial is specified only connecting to this device, otherwise to the first one found
Device(QString serial = QString());
~Device();
bool Configure(Protocol::SweepSettings settings);
bool SetManual(Protocol::ManualControl manual);
bool SendFirmwareChunk(Protocol::FirmwarePacket &fw);
// Returns serial numbers of all connected devices
static std::vector<QString> GetDevices();
QString serial() const;
Protocol::DeviceInfo getLastInfo() const;
QString getLastDeviceInfoString();
signals:
void DatapointReceived(Protocol::Datapoint);
void ManualStatusReceived(Protocol::ManualStatus);
void DeviceInfoUpdated();
void ConnectionLost();
void AckReceived();
void LogLineReceived(QString line);
private slots:
void ReceivedData();
void ReceivedLog();
private:
static constexpr int VID = 0x0483;
static constexpr int PID = 0x564e;
static constexpr int EP_Data_Out_Addr = 0x01;
static constexpr int EP_Data_In_Addr = 0x81;
static constexpr int EP_Log_In_Addr = 0x82;
void USBHandleThread();
// foundCallback is called for every device that is found. If it returns true the search continues, otherwise it is aborted.
// When the search is aborted the last found device is still opened
static void SearchDevices(std::function<bool(libusb_device_handle *handle, QString serial)> foundCallback, libusb_context *context);
libusb_device_handle *m_handle;
libusb_context *m_context;
USBInBuffer *dataBuffer;
USBInBuffer *logBuffer;
QString m_serial;
bool m_connected;
std::thread *m_receiveThread;
Protocol::DeviceInfo lastInfo;
bool lastInfoValid;
};
#endif // DEVICE_H

View File

@ -0,0 +1,61 @@
#include "devicelog.h"
#include "ui_devicelog.h"
#include <QScrollBar>
#include <QFileDialog>
#include <fstream>
using namespace std;
DeviceLog::DeviceLog(QWidget *parent) :
QWidget(parent),
ui(new Ui::DeviceLog)
{
ui->setupUi(this);
connect(ui->bClear, &QPushButton::clicked, this, &DeviceLog::clear);
}
DeviceLog::~DeviceLog()
{
delete ui;
}
void DeviceLog::addLine(QString line)
{
// Set color depending on log level
QColor color = Qt::black;
if(line.contains(",CRT]")) {
color = Qt::red;
} else if(line.contains(",ERR]")) {
color = QColor("orange");
} else if(line.contains(",WRN]")) {
color = Qt::darkYellow;
} else if(line.contains(",DBG")) {
color = Qt::gray;
}
QTextCharFormat tf;
tf = ui->text->currentCharFormat();
tf.setForeground(QBrush(color));
ui->text->setCurrentCharFormat(tf);
ui->text->appendPlainText(line);
if(ui->cbAutoscroll->isChecked()) {
QScrollBar *sb = ui->text->verticalScrollBar();
sb->setValue(sb->maximum());
}
}
void DeviceLog::clear()
{
ui->text->clear();
}
void DeviceLog::on_bToFile_clicked()
{
auto filename = QFileDialog::getSaveFileName(this, "Select file for device log", "", "", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.length() > 0) {
// create file
ofstream file;
file.open(filename.toStdString());
file << ui->text->toPlainText().toStdString();
file.close();
}
}

View File

@ -0,0 +1,29 @@
#ifndef DEVICELOG_H
#define DEVICELOG_H
#include <QWidget>
namespace Ui {
class DeviceLog;
}
class DeviceLog : public QWidget
{
Q_OBJECT
public:
explicit DeviceLog(QWidget *parent = nullptr);
~DeviceLog();
public slots:
void addLine(QString line);
void clear();
private slots:
void on_bToFile_clicked();
private:
Ui::DeviceLog *ui;
};
#endif // DEVICELOG_H

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DeviceLog</class>
<widget class="QWidget" name="DeviceLog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>810</width>
<height>211</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPlainTextEdit" name="text">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="bToFile">
<property name="text">
<string>To File</string>
</property>
<property name="icon">
<iconset theme="document-save"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bClear">
<property name="text">
<string>Clear</string>
</property>
<property name="icon">
<iconset theme="edit-clear"/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbAutoscroll">
<property name="text">
<string>Autoscroll</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,127 @@
#include "firmwareupdatedialog.h"
#include "ui_firmwareupdatedialog.h"
#include <QFileDialog>
FirmwareUpdateDialog::FirmwareUpdateDialog(Device &dev, QWidget *parent) :
QDialog(parent),
ui(new Ui::FirmwareUpdateDialog),
dev(dev),
file(),
timer(),
state(State::Idle)
{
ui->setupUi(this);
ui->bFile->setIcon(this->style()->standardPixmap(QStyle::SP_FileDialogStart));
ui->bStart->setIcon(this->style()->standardPixmap(QStyle::SP_MediaPlay));
timer.setSingleShot(true);
connect(&timer, &QTimer::timeout, [=](){
abortWithError("Response timed out");
});
}
FirmwareUpdateDialog::~FirmwareUpdateDialog()
{
delete ui;
}
void FirmwareUpdateDialog::on_bFile_clicked()
{
ui->bStart->setEnabled(false);
auto filename = QFileDialog::getOpenFileName(nullptr, "Open firmware file", "", "Firmware file (*.vnafw)", nullptr, QFileDialog::DontUseNativeDialog);
if (filename.length() > 0) {
ui->lFile->setText(filename);
if(file) {
delete file;
}
file = new QFile(filename);
ui->bStart->setEnabled(true);
}
}
void FirmwareUpdateDialog::on_bStart_clicked()
{
ui->status->clear();
ui->bStart->setEnabled(false);
if(!file->isOpen()) {
if(!file->open(QIODevice::ReadOnly)) {
abortWithError("Unable to open file");
return;
}
}
file->seek(0);
addStatus("Evaluating file...");
if(file->size() % Protocol::FirmwareChunkSize != 0) {
abortWithError("Invalid file size");
return;
}
char header[24];
file->read(header, sizeof(header));
if(strncmp(header, "VNA!", 4)) {
abortWithError("Invalid magic header constant");
return;
}
state = State::ErasingFLASH;
addStatus("Erasing device memory...");
// TODO issue write command
timer.start(10000);
}
void FirmwareUpdateDialog::addStatus(QString line)
{
ui->status->appendPlainText(line);
}
void FirmwareUpdateDialog::abortWithError(QString error)
{
QTextCharFormat tf;
tf = ui->status->currentCharFormat();
tf.setForeground(QBrush(Qt::red));
ui->status->setCurrentCharFormat(tf);
ui->status->appendPlainText(error);
tf.setForeground(QBrush(Qt::black));
ui->status->setCurrentCharFormat(tf);
ui->bStart->setEnabled(true);
state = State::Idle;
}
void FirmwareUpdateDialog::receivedAck()
{
switch(state) {
case State::Idle:
// no firmware update in progress, ignore
break;
case State::ErasingFLASH:
// FLASH erased, begin transferring firmware
state = State::TransferringData;
transferredBytes = 0;
addStatus("Transferring firmware...");
sendNextFirmwareChunk();
timer.start(1000);
break;
case State::TransferringData:
transferredBytes += Protocol::FirmwareChunkSize;
ui->progress->setValue(100 * transferredBytes / file->size());
if(transferredBytes >= file->size()) {
// complete file transferred
addStatus("Triggering device update...");
state = State::TriggeringUpdate;
// TODO trigger update
timer.start(5000);
}
sendNextFirmwareChunk();
timer.start(1000);
break;
case State::TriggeringUpdate:
addStatus("Rebooting device...");
// TODO listen for detected device
state = State::Idle;
break;
}
}
void FirmwareUpdateDialog::sendNextFirmwareChunk()
{
Protocol::FirmwarePacket fw;
fw.address = transferredBytes;
file->read((char*) &fw.data, Protocol::FirmwareChunkSize);
}

View File

@ -0,0 +1,46 @@
#ifndef FIRMWAREUPDATEDIALOG_H
#define FIRMWAREUPDATEDIALOG_H
#include <QDialog>
#include "device.h"
#include <QFile>
#include <QTimer>
namespace Ui {
class FirmwareUpdateDialog;
}
class FirmwareUpdateDialog : public QDialog
{
Q_OBJECT
public:
explicit FirmwareUpdateDialog(Device &dev, QWidget *parent = nullptr);
~FirmwareUpdateDialog();
private slots:
void on_bFile_clicked();
void on_bStart_clicked();
private:
void addStatus(QString line);
void abortWithError(QString error);
void receivedAck();
void sendNextFirmwareChunk();
Ui::FirmwareUpdateDialog *ui;
Device &dev;
QFile *file;
QTimer timer;
enum class State {
Idle,
ErasingFLASH,
TransferringData,
TriggeringUpdate,
};
State state;
unsigned int transferredBytes;
};
#endif // FIRMWAREUPDATEDIALOG_H

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FirmwareUpdateDialog</class>
<widget class="QDialog" name="FirmwareUpdateDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>327</height>
</rect>
</property>
<property name="windowTitle">
<string>Firmware Update</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>File:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lFile">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bFile">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="status">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progress">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="bStart">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,254 @@
#include "manualcontroldialog.h"
#include "ui_manualcontroldialog.h"
#include <QComboBox>
#include <QDebug>
#include <QButtonGroup>
#include <complex>
using namespace std;
ManualControlDialog::ManualControlDialog(Device &dev, QWidget *parent) :
QDialog(parent),
ui(new Ui::ManualControlDialog),
dev(dev)
{
ui->setupUi(this);
ui->SourceLowFrequency->setUnit("Hz");
ui->SourceLowFrequency->setPrefixes(" kM");
ui->SourceLowFrequency->setPrecision(6);
ui->SourceLowFrequency->setValueQuiet(1000000);
ui->SourceHighFrequency->setUnit("Hz");
ui->SourceHighFrequency->setPrefixes(" kMG");
ui->SourceHighFrequency->setPrecision(6);
ui->SourceHighFrequency->setValueQuiet(1000000000);
ui->IF1->setUnit("Hz");
ui->IF1->setPrefixes(" kM");
ui->IF1->setPrecision(6);
ui->LO1Frequency->setUnit("Hz");
ui->LO1Frequency->setPrefixes(" kMG");
ui->LO1Frequency->setPrecision(6);
ui->IF2->setUnit("Hz");
ui->IF2->setPrefixes(" kM");
ui->IF2->setPrecision(6);
ui->LO2Frequency->setUnit("Hz");
ui->LO2Frequency->setPrefixes(" kM");
ui->LO2Frequency->setPrecision(6);
auto UpdateLO1 = [=]() {
double sourceFreq;
if (ui->SwitchLowband->isChecked()) {
sourceFreq = ui->SourceLowFrequency->value();
} else {
sourceFreq = ui->SourceHighFrequency->value();
}
if (ui->LO1FreqType->currentIndex() == 0) {
// fixed IF mode
ui->LO1Frequency->setValueQuiet(sourceFreq + ui->IF1->value());
} else {
// Manual Frequency mode
ui->IF1->setValueQuiet(ui->LO1Frequency->value() - sourceFreq);
}
};
auto UpdateLO2 = [=]() {
double IF1 = ui->IF1->value();
if (ui->LO2FreqType->currentIndex() == 0) {
// fixed IF mode
ui->LO2Frequency->setValueQuiet(IF1 + ui->IF2->value());
} else {
// Manual Frequency mode
ui->IF2->setValueQuiet(ui->LO2Frequency->value() - IF1);
}
};
connect(ui->IF1, &SIUnitEdit::valueChanged, [=](double) {
UpdateLO1();
UpdateLO2();
});
connect(ui->LO1Frequency, &SIUnitEdit::valueChanged, [=](double) {
UpdateLO1();
UpdateLO2();
});
connect(ui->IF2, &SIUnitEdit::valueChanged, [=](double) {
UpdateLO2();
});
connect(ui->LO2Frequency, &SIUnitEdit::valueChanged, [=](double) {
UpdateLO2();
});
connect(ui->SourceSwitchGroup, qOverload<int, bool>(&QButtonGroup::buttonToggled), [=](int, bool) {
UpdateLO1();
UpdateLO2();
});
connect(ui->SourceLowFrequency, &SIUnitEdit::valueChanged, [=](double) {
UpdateLO1();
UpdateLO2();
});
connect(ui->SourceHighFrequency, &SIUnitEdit::valueChanged, [=](double) {
UpdateLO1();
UpdateLO2();
});
ui->IF1->setValue(60000000);
ui->IF2->setValue(250000);
// LO1/LO2 mode switch connections
connect(ui->LO1FreqType, qOverload<int>(&QComboBox::activated), [=](int index) {
switch(index) {
case 0:
ui->LO1Frequency->setEnabled(false);
ui->IF1->setEnabled(true);
break;
case 1:
ui->LO1Frequency->setEnabled(true);
ui->IF1->setEnabled(false);
break;
}
});
connect(ui->LO2FreqType, qOverload<int>(&QComboBox::activated), [=](int index) {
switch(index) {
case 0:
ui->LO2Frequency->setEnabled(false);
ui->IF2->setEnabled(true);
break;
case 1:
ui->LO2Frequency->setEnabled(true);
ui->IF2->setEnabled(false);
break;
}
});
// Readonly widgets
auto MakeReadOnly = [](QWidget* w) {
w->setAttribute(Qt::WA_TransparentForMouseEvents);
w->setFocusPolicy(Qt::NoFocus);
};
MakeReadOnly(ui->SourceLocked);
MakeReadOnly(ui->LO1locked);
MakeReadOnly(ui->port1min);
MakeReadOnly(ui->port1max);
MakeReadOnly(ui->port1mag);
MakeReadOnly(ui->port1phase);
MakeReadOnly(ui->port1referenced);
MakeReadOnly(ui->port2min);
MakeReadOnly(ui->port2max);
MakeReadOnly(ui->port2mag);
MakeReadOnly(ui->port2phase);
MakeReadOnly(ui->port2referenced);
MakeReadOnly(ui->refmin);
MakeReadOnly(ui->refmax);
MakeReadOnly(ui->refmag);
MakeReadOnly(ui->refphase);
qRegisterMetaType<Protocol::ManualStatus>("Status");
connect(&dev, &Device::ManualStatusReceived, this, &ManualControlDialog::NewStatus);
connect(ui->SourceCE, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->SourceRFEN, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->LO1CE, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->LO1RFEN, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->SourceLowEnable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->AmplifierEnable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->LO2EN, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->Port1Enable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->Port2Enable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->RefEnable, &QCheckBox::toggled, [=](bool) { UpdateDevice(); });
connect(ui->SourceHighPower, qOverload<int>(&QComboBox::activated), [=](int) { UpdateDevice(); });
connect(ui->SourceLowpass, qOverload<int>(&QComboBox::activated), [=](int) { UpdateDevice(); });
connect(ui->SourceLowPower, qOverload<int>(&QComboBox::activated), [=](int) { UpdateDevice(); });
connect(ui->SourceHighFrequency, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
connect(ui->SourceLowFrequency, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
connect(ui->LO1Frequency, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
connect(ui->IF1, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
connect(ui->LO2Frequency, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
connect(ui->IF2, &SIUnitEdit::valueChanged, [=](double) { UpdateDevice(); });
connect(ui->PortSwitchGroup, qOverload<int, bool>(&QButtonGroup::buttonToggled), [=](int, bool) { UpdateDevice(); });
connect(ui->SourceSwitchGroup, qOverload<int, bool>(&QButtonGroup::buttonToggled), [=](int, bool) { UpdateDevice(); });
connect(ui->Attenuator, qOverload<double>(&QDoubleSpinBox::valueChanged), [=](double) { UpdateDevice(); });
connect(ui->Samples, qOverload<int>(&QSpinBox::valueChanged), [=](double) { UpdateDevice(); });
UpdateDevice();
}
ManualControlDialog::~ManualControlDialog()
{
delete ui;
}
void ManualControlDialog::NewStatus(Protocol::ManualStatus status)
{
// ADC values
ui->port1min->setText(QString::number(status.port1min));
ui->port1max->setText(QString::number(status.port1max));
auto port1 = complex<double>(status.port1real, status.port1imag);
ui->port1mag->setText(QString::number(abs(port1)));
ui->port1phase->setText(QString::number(arg(port1)*180/M_PI));
ui->port2min->setText(QString::number(status.port2min));
ui->port2max->setText(QString::number(status.port2max));
auto port2 = complex<double>(status.port2real, status.port2imag);
ui->port2mag->setText(QString::number(abs(port2)));
ui->port2phase->setText(QString::number(arg(port2)*180/M_PI));
ui->refmin->setText(QString::number(status.refmin));
ui->refmax->setText(QString::number(status.refmax));
auto ref = complex<double>(status.refreal, status.refimag);
ui->refmag->setText(QString::number(abs(ref)));
ui->refphase->setText(QString::number(arg(ref)*180/M_PI));
auto port1referenced = port1 / ref;
auto port2referenced = port2 / ref;
auto port1db = 20*log10(abs(port1referenced));
auto port2db = 20*log10(abs(port2referenced));
ui->port1referenced->setText(QString::number(port1db, 'f', 1) + "db@" + QString::number(arg(port1referenced)*180/M_PI, 'f', 0) + "°");
ui->port2referenced->setText(QString::number(port2db, 'f', 1) + "db@" + QString::number(arg(port2referenced)*180/M_PI, 'f', 0) + "°");
// PLL state
ui->SourceLocked->setChecked(status.source_locked);
ui->LO1locked->setChecked(status.LO_locked);
}
void ManualControlDialog::UpdateDevice()
{
Protocol::ManualControl m;
// Source highband
m.SourceHighCE = ui->SourceCE->isChecked();
m.SourceHighRFEN = ui->SourceRFEN->isChecked();
m.SourceHighPower = ui->SourceHighPower->currentIndex();
m.SourceHighFrequency = ui->SourceHighFrequency->value();
m.SourceHighLowpass = ui->SourceLowpass->currentIndex();
// Source lowband
m.SourceLowEN = ui->SourceLowEnable->isChecked();
m.SourceLowPower = ui->SourceLowPower->currentIndex();
m.SourceLowFrequency = ui->SourceLowFrequency->value();
// Source signal path
m.SourceHighband = ui->SwitchHighband->isChecked();
m.AmplifierEN = ui->AmplifierEnable->isChecked();
m.PortSwitch = ui->Port2Switch->isChecked();
m.attenuator = -ui->Attenuator->value() / 0.25;
// LO1
m.LO1CE = ui->LO1CE->isChecked();
m.LO1RFEN = ui->LO1RFEN->isChecked();
m.LO1Frequency = ui->LO1Frequency->value();
// LO2
m.LO2EN = ui->LO2EN->isChecked();
m.LO2Frequency = ui->LO2Frequency->value();
// Acquisition
m.Port1EN = ui->Port1Enable->isChecked();
m.Port2EN = ui->Port2Enable->isChecked();
m.RefEN = ui->RefEnable->isChecked();
m.Samples = ui->Samples->value();
qDebug() << "Updating manual control state";
dev.SetManual(m);
}

View File

@ -0,0 +1,28 @@
#ifndef MANUALCONTROLDIALOG_H
#define MANUALCONTROLDIALOG_H
#include <QDialog>
#include "device.h"
namespace Ui {
class ManualControlDialog;
}
class ManualControlDialog : public QDialog
{
Q_OBJECT
public:
explicit ManualControlDialog(Device &dev, QWidget *parent = nullptr);
~ManualControlDialog();
public slots:
void NewStatus(Protocol::ManualStatus status);
private:
void UpdateDevice();
Ui::ManualControlDialog *ui;
Device &dev;
};
#endif // MANUALCONTROLDIALOG_H

View File

@ -0,0 +1,751 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ManualControlDialog</class>
<widget class="QDialog" name="ManualControlDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1023</width>
<height>628</height>
</rect>
</property>
<property name="windowTitle">
<string>Manual System Control</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
<string>Signal Generation</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Highband Source</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<widget class="QCheckBox" name="SourceCE">
<property name="text">
<string>Chip Enable</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="SourceRFEN">
<property name="text">
<string>RF Enable</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="SourceLocked">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Locked</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Power:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="SourceHighPower">
<item>
<property name="text">
<string>-4dbm</string>
</property>
</item>
<item>
<property name="text">
<string>-1dbm</string>
</property>
</item>
<item>
<property name="text">
<string>+2dbm</string>
</property>
</item>
<item>
<property name="text">
<string>+5dbm</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Frequency:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="SourceHighFrequency"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Lowpass:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="SourceLowpass">
<item>
<property name="text">
<string>947MHz</string>
</property>
</item>
<item>
<property name="text">
<string>1880MHz</string>
</property>
</item>
<item>
<property name="text">
<string>3500MHz</string>
</property>
</item>
<item>
<property name="text">
<string>None</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Lowband Source</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="SourceLowEnable">
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Power:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="SourceLowPower">
<item>
<property name="text">
<string>2mA</string>
</property>
</item>
<item>
<property name="text">
<string>4mA</string>
</property>
</item>
<item>
<property name="text">
<string>6mA</string>
</property>
</item>
<item>
<property name="text">
<string>8mA</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Frequency:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="SourceLowFrequency"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Source Switch</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="SwitchLowband">
<property name="text">
<string>Lowband</string>
</property>
<attribute name="buttonGroup">
<string notr="true">SourceSwitchGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="SwitchHighband">
<property name="text">
<string>Highband</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">SourceSwitchGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Attenuator</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QDoubleSpinBox" name="Attenuator">
<property name="suffix">
<string>db</string>
</property>
<property name="minimum">
<double>-31.750000000000000</double>
</property>
<property name="maximum">
<double>0.000000000000000</double>
</property>
<property name="singleStep">
<double>0.250000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Amplifier</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
<widget class="QCheckBox" name="AmplifierEnable">
<property name="text">
<string>Enable</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Port Switch</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QRadioButton" name="Port1Switch">
<property name="text">
<string>Port 1</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">PortSwitchGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Port2Switch">
<property name="text">
<string>Port 2</string>
</property>
<attribute name="buttonGroup">
<string notr="true">PortSwitchGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_11">
<property name="title">
<string>Signal Analysis</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>LO1</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QCheckBox" name="LO1CE">
<property name="text">
<string>Chip Enable</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="LO1RFEN">
<property name="text">
<string>RF Enable</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="LO1locked">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Locked</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Freq. Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="LO1FreqType">
<item>
<property name="text">
<string>IF1</string>
</property>
</item>
<item>
<property name="text">
<string>Absolute</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Frequency:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="LO1Frequency">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>IF1:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="IF1"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_8">
<property name="title">
<string>LO2</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QCheckBox" name="LO2EN">
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Freq. Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="LO2FreqType">
<item>
<property name="text">
<string>IF2</string>
</property>
</item>
<item>
<property name="text">
<string>Absolute</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Frequency:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="LO2Frequency">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>IF2:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="IF2"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_9">
<property name="title">
<string>Aquisition</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QCheckBox" name="Port1Enable">
<property name="text">
<string>Port 1 Enable</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="Port2Enable">
<property name="text">
<string>Port 2 Enable</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="RefEnable">
<property name="text">
<string>Reference Enable</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Samples:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="Samples">
<property name="minimum">
<number>128</number>
</property>
<property name="maximum">
<number>131072</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>131072</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox_12">
<property name="title">
<string>Measurements</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_16">
<item>
<widget class="QGroupBox" name="groupBox_16">
<property name="title">
<string>Port 1</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_19">
<item>
<layout class="QFormLayout" name="formLayout_9">
<item row="0" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>ADC min:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="port1min"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>ADC max:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="port1max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_27">
<property name="text">
<string>Magnitude:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="port1mag"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_28">
<property name="text">
<string>Phase:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="port1phase"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Referenced:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="port1referenced"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_14">
<property name="title">
<string>Port 2</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_17">
<item>
<layout class="QFormLayout" name="formLayout_7">
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>ADC min:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="port2min"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>ADC max:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="port2max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Magnitude:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="port2mag"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Phase:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="port2phase"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Referenced:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="port2referenced"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_15">
<property name="title">
<string>Reference</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_18">
<item>
<layout class="QFormLayout" name="formLayout_8">
<item row="0" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>ADC min:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="refmin"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>ADC max:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="refmax"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>Magnitude:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="refmag"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Phase:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="refphase"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SIUnitEdit</class>
<extends>QLineEdit</extends>
<header>CustomWidgets/siunitedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="PortSwitchGroup"/>
<buttongroup name="SourceSwitchGroup"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,84 @@
#include "menu.h"
#include <QKeyEvent>
#include "menuaction.h"
#include <iostream>
using namespace std;
Menu::Menu(QStackedLayout &layout, QString name)
: name(name),
m_containingLayout(layout)
{
m_layout = new QVBoxLayout;
setLayout(m_layout);
m_widgetCount = 0;
setFixedSize(180, 800);
parent = nullptr;
layout.addWidget(this);
if(name.length() > 0) {
auto back = new MenuAction(name, MenuAction::ArrowType::Left);
back->setStyleSheet("background-color:lightblue;");
connect(back, &MenuAction::triggered, this, &Menu::leave);
addItem(back);
}
}
void Menu::addItem(MenuItem *i)
{
if(m_widgetCount >= maxWidgets) {
throw runtime_error("Menu already at maximum capacity");
}
m_layout->addWidget(i, 1);
items.push_back(i);
m_widgetCount++;
}
void Menu::addMenu(Menu *m)
{
auto menuLabel = new MenuAction(m->name, MenuAction::ArrowType::Right);
submenus.push_back(SubmenuEntry(menuLabel, m, m_widgetCount));
connect(menuLabel, &MenuAction::triggered, [=]() {
m->m_containingLayout.setCurrentWidget(m);
});
addItem(menuLabel);
m->parent = this;
}
void Menu::finalize()
{
m_layout->addStretch(maxWidgets - m_widgetCount);
}
void Menu::keyPressEvent(QKeyEvent *event)
{
// check if softkey pressed
int index = -1;
switch(event->key()) {
case Qt::Key_F1: index = 0; break;
case Qt::Key_F2: index = 1; break;
case Qt::Key_F3: index = 2; break;
case Qt::Key_F4: index = 3; break;
case Qt::Key_F5: index = 4; break;
case Qt::Key_F6: index = 5; break;
case Qt::Key_F7: index = 6; break;
case Qt::Key_F8: index = 7; break;
}
if(index >= 0) {
auto w = m_layout->itemAt(index);
w->widget()->setFocus();
items[index]->userSelected();
event->accept();
} else if(event->key() == Qt::Key_Escape) {
leave();
event->accept();
}
}
void Menu::leave()
{
if(parent) {
// got a parent menu
parent->m_containingLayout.setCurrentWidget(parent);
}
}

View File

@ -0,0 +1,41 @@
#ifndef MENU_H
#define MENU_H
#include <QWidget>
#include <QStackedLayout>
#include "menuitem.h"
#include "menuaction.h"
class Menu : public QWidget
{
Q_OBJECT
public:
Menu(QStackedLayout &layout, QString name = QString());
void addItem(MenuItem *i);
void addMenu(Menu *m);
void finalize();
signals:
protected:
void keyPressEvent(QKeyEvent *event) override;
private:
void leave();
class SubmenuEntry {
public:
SubmenuEntry(MenuAction *label, Menu *menu, int index) :
label(label), menu(menu), index(index){};
MenuAction *label;
Menu *menu;
int index;
};
static constexpr int maxWidgets = 12;
QVBoxLayout *m_layout;
Menu *parent;
const QString name;
QStackedLayout &m_containingLayout;
std::vector<SubmenuEntry> submenus;
std::vector<MenuItem*> items;
int m_widgetCount;
};
#endif // MENU_H

View File

@ -0,0 +1,65 @@
#include "menuaction.h"
#include <QLabel>
#include <QStyle>
#include <QSizePolicy>
MenuAction::MenuAction(const QString &l, MenuAction::ArrowType arrow)
{
subline = nullptr;
auto label = new QLabel(l, this);
label->setAlignment(Qt::AlignCenter);
auto labelLayout = new QHBoxLayout();
if(arrow == ArrowType::Left) {
auto lIcon = new QLabel(this);
lIcon->setPixmap(style()->standardIcon(QStyle::SP_ArrowLeft).pixmap(16));
lIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
labelLayout->addWidget(lIcon);
}
labelLayout->addWidget(label);
if(arrow == ArrowType::Right) {
auto lIcon = new QLabel(this);
lIcon->setPixmap(style()->standardIcon(QStyle::SP_ArrowRight).pixmap(16));
lIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
labelLayout->addWidget(lIcon);
}
layout.addLayout(labelLayout);
setLayout(&layout);
}
void MenuAction::AddSubline(const QString &l)
{
if(!subline) {
subline = new QLabel(this);
subline->setAlignment(Qt::AlignCenter);
layout.addWidget(subline);
}
QFont f( "Arial", 8);
subline->setFont( f);
subline->setText(l);
}
void MenuAction::RemoveSubline()
{
if(subline) {
layout.removeWidget(subline);
delete subline;
subline = nullptr;
}
}
void MenuAction::userSelected()
{
emit triggered();
}
void MenuAction::mouseReleaseEvent(QMouseEvent *me)
{
setFrameStyle(QFrame::Raised | QFrame::Panel);
MenuItem::mouseReleaseEvent(me);
}
void MenuAction::mousePressEvent(QMouseEvent *)
{
setFrameStyle(QFrame::Sunken | QFrame::Panel);
}

View File

@ -0,0 +1,33 @@
#ifndef MENULABEL_H
#define MENULABEL_H
#include "menuitem.h"
#include <QVBoxLayout>
#include <QLabel>
class MenuAction : public MenuItem
{
Q_OBJECT
public:
enum class ArrowType {
None,
Left,
Right,
};
MenuAction(const QString &l, ArrowType arrow = ArrowType::None);
void AddSubline(const QString &l);
void RemoveSubline();
signals:
void triggered();
public slots:
void userSelected() override;
private:
QVBoxLayout layout;
QLabel *subline;
protected:
void mouseReleaseEvent(QMouseEvent *me) override;
void mousePressEvent(QMouseEvent *me) override;
};
#endif // MENULABEL_H

View File

@ -0,0 +1,36 @@
#include "menubool.h"
#include <QLabel>
MenuBool::MenuBool(QString name, bool defaultValue)
{
auto label = new QLabel(name, this);
label->setAlignment(Qt::AlignCenter);
layout.addWidget(label);
sw = new ToggleSwitch(this, defaultValue);
layout.addWidget(sw);
setLayout(&layout);
connect(sw, &ToggleSwitch::toggled, this, &MenuBool::valueChanged);
sw->setFocusPolicy(Qt::NoFocus);
}
void MenuBool::setValue(bool value)
{
sw->setState(value);
}
void MenuBool::userSelected()
{
sw->toggle();
}
void MenuBool::mouseReleaseEvent(QMouseEvent *me)
{
setFrameStyle(QFrame::Raised | QFrame::Panel);
MenuItem::mouseReleaseEvent(me);
}
void MenuBool::mousePressEvent(QMouseEvent *)
{
setFrameStyle(QFrame::Sunken | QFrame::Panel);
}

View File

@ -0,0 +1,28 @@
#ifndef MENUBOOL_H
#define MENUBOOL_H
#include <QObject>
#include "menuitem.h"
#include <QVBoxLayout>
#include "CustomWidgets/toggleswitch.h"
class MenuBool : public MenuItem
{
Q_OBJECT
public:
MenuBool(QString name, bool defaultValue = false);
signals:
void valueChanged(bool value);
public slots:
void setValue(bool value);
void userSelected() override;
protected:
void mouseReleaseEvent(QMouseEvent *me) override;
void mousePressEvent(QMouseEvent *me) override;
private:
QVBoxLayout layout;
ToggleSwitch *sw;
};
#endif // MENUBOOL_H

View File

@ -0,0 +1,17 @@
#include "menuitem.h"
#include <QMouseEvent>
MenuItem::MenuItem() : QFrame()
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
//setStyleSheet("*:focus {background: lightblue}");
setFocusPolicy(Qt::StrongFocus);
setFrameStyle(QFrame::Raised | QFrame::Panel);
}
void MenuItem::mouseReleaseEvent(QMouseEvent *me)
{
userSelected();
me->accept();
}

View File

@ -0,0 +1,20 @@
#ifndef MENUITEM_H
#define MENUITEM_H
#include <QFrame>
class MenuItem : public QFrame
{
Q_OBJECT
public:
MenuItem();
public slots:
virtual void userSelected(){};
protected:
void mouseReleaseEvent(QMouseEvent *me) override;
};
#endif // MENUITEM_H

View File

@ -0,0 +1,46 @@
#include "menuvalue.h"
#include <QVBoxLayout>
#include <math.h>
#include <sstream>
#include <iomanip>
#include "valueinput.h"
#include <QMouseEvent>
#include "unit.h"
#include <QDoubleValidator>
using namespace std;
MenuValue::MenuValue(QString name, double defaultValue, QString unit, QString prefixes, int precision)
: name(name)
{
if(prefixes.indexOf(' ') < 0) {
throw runtime_error("Prefix string must contain space");
}
auto layout = new QVBoxLayout;
auto label = new QLabel(name, this);
label->setAlignment(Qt::AlignCenter);
layout->addWidget(label);
lvalue = new SIUnitEdit(unit, prefixes, precision);
// pass on signal
connect(lvalue, &SIUnitEdit::valueChanged, this, &MenuValue::valueChanged);
layout->addWidget(lvalue);
setValue(defaultValue);
setLayout(layout);
}
void MenuValue::setValue(double value)
{
lvalue->setValue(value);
}
void MenuValue::setValueQuiet(double value)
{
lvalue->setValueQuiet(value);
}
void MenuValue::userSelected()
{
lvalue->setFocus();
//startInputDialog();
}

View File

@ -0,0 +1,26 @@
#ifndef MENUVALUE_H
#define MENUVALUE_H
#include "menuitem.h"
#include <QLabel>
#include <CustomWidgets/siunitedit.h>
class MenuValue : public MenuItem
{
Q_OBJECT
public:
MenuValue(QString name, double defaultValue = 0.0, QString unit = QString(), QString prefixes = " ", int precision = 0);
signals:
void valueChanged(double value);
public slots:
void setValue(double value);
// same as setValue, except that no valueChanged signal is emitted
void setValueQuiet(double value);
void userSelected() override;
private:
SIUnitEdit *lvalue;
const QString name;
};
#endif // MENUVALUE_H

View File

@ -0,0 +1,60 @@
#include "eseries.h"
#include <math.h>
#include <vector>
static const std::vector<double> E6 = {
1.0, 1.5, 2.2, 3.3, 4.7, 6.8
};
static const std::vector<double> E12 = {
1.0, 1.2, 1.5, 1.8, 2.2, 2.7, 3.3, 3.9, 4.7, 5.6, 6.8, 8.2
};
static const std::vector<double> E24 = {
1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1
};
static const std::vector<double> E48 = {
1.00, 1.05, 1.10, 1.15, 1.21, 1.27, 1.33, 1.40, 1.47, 1.54, 1.62, 1.69, 1.78, 1.87, 1.96, 2.05, 2.15, 2.26, 2.37, 2.49, 2.61, 2.74, 2.87, 3.01, 3.16, 3.32, 3.48, 3.65, 3.83, 4.02, 4.22, 4.42, 4.64, 4.87, 5.11, 5.36, 5.62, 5.90, 6.19, 6.49, 6.81, 7.15, 7.50, 7.87, 8.25, 8.66, 9.09, 9.53
};
static const std::vector<double> E96 = {
1.00, 1.02, 1.05, 1.07, 1.10, 1.13, 1.15, 1.18, 1.21, 1.24, 1.27, 1.30, 1.33, 1.37, 1.40, 1.43, 1.47, 1.50, 1.54, 1.58, 1.62, 1.65, 1.69, 1.74, 1.78, 1.82, 1.87, 1.91, 1.96, 2.00, 2.05, 2.10, 2.15, 2.21, 2.26, 2.32, 2.37, 2.43, 2.49, 2.55, 2.61, 2.67, 2.74, 2.80, 2.87, 2.94, 3.01, 3.09, 3.16, 3.24, 3.32, 3.40, 3.48, 3.57, 3.65, 3.74, 3.83, 3.92, 4.02, 4.12, 4.22, 4.32, 4.42, 4.53, 4.64, 4.75, 4.87, 4.99, 5.11, 5.23, 5.36, 5.49, 5.62, 5.76, 5.90, 6.04, 6.19, 6.34, 6.49, 6.65, 6.81, 6.98, 7.15, 7.32, 7.50, 7.68, 7.87, 8.06, 8.25, 8.45, 8.66, 8.87, 9.09, 9.31, 9.53, 9.76
};
double ESeries::ToESeries(double value, ESeries::Series s, ESeries::Type t)
{
if(s == Series::Ideal) {
// nothing to do
return value;
}
// bring value into [1.0, 10.0) interval
int shift10 = floor(log10(value));
value *= pow(10.0, -shift10);
std::vector<double> series;
switch(s) {
case Series::E96: series = E96; break;
case Series::E48: series = E48; break;
case Series::E24: series = E24; break;
case Series::E12: series = E12; break;
case Series::E6: series = E6; break;
case Series::Ideal: /* already handled */ break;
}
unsigned int index = 1;
while(index < 96 && series[index] <= value) {
index++;
}
auto lower = series[index - 1];
double higher = 10.0;
if(index < series.size()) {
higher = series[index];
}
double approximation;
switch(t) {
case Type::Lower: approximation = lower; break;
case Type::Higher: approximation = higher; break;
case Type::BestMatch:
if(fabs(value - lower) < fabs(value - higher)) {
approximation = lower;
} else {
approximation = higher;
}
}
return approximation * pow(10, shift10);
}

View File

@ -0,0 +1,25 @@
#ifndef ESERIES_H
#define ESERIES_H
class ESeries
{
public:
enum class Series {
Ideal = 0,
E6 = 1,
E12 = 2,
E24 = 3,
E48 = 4,
E96 = 5,
};
enum class Type {
BestMatch = 0,
Lower = 1,
Higher = 2,
};
static double ToESeries(double value, Series s, Type t = Type::BestMatch);
};
#endif // ESERIES_H

View File

@ -0,0 +1,275 @@
#include "impedancematchdialog.h"
#include "ui_impedancematchdialog.h"
#include "Tools/eseries.h"
using namespace std;
constexpr double ImpedanceMatchDialog::Z0;
ImpedanceMatchDialog::ImpedanceMatchDialog(TraceMarkerModel &model, TraceMarker *marker, QWidget *parent) :
QDialog(parent),
ui(new Ui::ImpedanceMatchDialog)
{
ui->setupUi(this);
// set SI units and prefixes
ui->zReal->setUnit("Ohm");
ui->zImag->setUnit("Ohm");
ui->zFreq->setUnit("Hz");
ui->zFreq->setPrefixes(" kMG");
ui->mImag->setUnit("Ohm");
ui->mReal->setUnit("Ohm");
ui->mLoss->setUnit("db");
ui->lValue->setUnit("H");
ui->lValue->setPrefixes("pnum ");
ui->cValue->setUnit("F");
ui->cValue->setPrefixes("pnum ");
connect(ui->zFreq, &SIUnitEdit::valueChanged, this, &ImpedanceMatchDialog::calculateMatch);
connect(ui->zImag, &SIUnitEdit::valueChanged, this, &ImpedanceMatchDialog::calculateMatch);
connect(ui->zReal, &SIUnitEdit::valueChanged, this, &ImpedanceMatchDialog::calculateMatch);
connect(ui->zGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImpedanceMatchDialog::calculateMatch);
connect(ui->cMatchType, qOverload<int>(&QComboBox::currentIndexChanged), this, &ImpedanceMatchDialog::calculateMatch);
connect(ui->lGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImpedanceMatchDialog::calculateMatch);
connect(ui->cGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImpedanceMatchDialog::calculateMatch);
connect(ui->zGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImpedanceMatchDialog::calculateMatch);
// populate marker options
auto markers = model.getMarker();
for(auto m : markers) {
if(!m->trace()->isReflection()) {
// matching only possible for reflections
continue;
}
ui->cSource->addItem("From Marker "+QString::number(m->getNumber()), QVariant::fromValue<TraceMarker*>(m));
if(m == marker) {
// select the last index, e.i. the just created marker
ui->cSource->setCurrentIndex(ui->cSource->count()-1);
}
}
}
ImpedanceMatchDialog::~ImpedanceMatchDialog()
{
delete ui;
}
void ImpedanceMatchDialog::on_cSource_currentIndexChanged(int index)
{
ui->rbSeries->setEnabled(index == 0);
ui->rbParallel->setEnabled(index == 0);
ui->zReal->setEnabled(index == 0);
ui->zImag->setEnabled(index == 0);
ui->zFreq->setEnabled(index == 0);
if(index > 0) {
auto m = qvariant_cast<TraceMarker*>(ui->cSource->itemData(index));
ui->rbSeries->setChecked(true);
auto data = m->getData();
auto reflection = Z0 * (1.0 + data) / (1.0 - data);
ui->zReal->setValue(reflection.real());
ui->zImag->setValue(reflection.imag());
ui->zFreq->setValue(m->getFrequency());
}
}
void ImpedanceMatchDialog::calculateMatch()
{
try {
double freq = ui->zFreq->value();
complex<double> Z;
if(ui->rbSeries->isChecked()) {
Z.real(ui->zReal->value());
Z.imag(ui->zImag->value());
} else {
auto real = complex<double>(ui->zReal->value(), 0.0);
auto imag = complex<double>(0.0, ui->zImag->value());
// calculate parallel impedance
Z = real * imag / (real + imag);
}
bool seriesC = ui->cMatchType->currentIndex() == 0 ? true : false;
// equations taken from http://www.ittc.ku.edu/~jstiles/723/handouts/section_5_1_Matching_with_Lumped_Elements_package.pdf
double B, X;
if(Z.real() > Z0) {
B = sqrt(Z.real()/Z0)*sqrt(norm(Z)-Z0*Z.real());
if (seriesC) {
B = -B;
}
B += Z.imag();
B /= norm(Z);
X = 1/B + Z.imag()*Z0/Z.real()-Z0/(B*Z.real());
} else {
B = sqrt((Z0-Z.real())/Z.real())/Z0;
X = sqrt(Z.real()*(Z0-Z.real()));
if (seriesC) {
B = -B;
X = -X;
}
X -= Z.imag();
}
// convert X and B to inductor and capacitor
bool twoCs = false;
bool twoLs = false;
double L, C, C2, L2;
if(X >= 0) {
L = X/(2*M_PI*freq);
if(B > 0) {
C = B/(2*M_PI*freq);
} else {
L2 = X/(2*M_PI*freq);
L = -1/(B*2*M_PI*freq);
twoLs = true;
}
} else {
C = -1/(X*2*M_PI*freq);
if(B < 0) {
L = -1/(B*2*M_PI*freq);
} else {
C2 = B/(2*M_PI*freq);
twoCs = true;
}
}
ESeries::Series Lseries;
if(ui->lE6->isChecked()) {
Lseries = ESeries::Series::E6;
} else if(ui->lE12->isChecked()) {
Lseries = ESeries::Series::E12;
} else if(ui->lE24->isChecked()) {
Lseries = ESeries::Series::E24;
} else if(ui->lE48->isChecked()) {
Lseries = ESeries::Series::E48;
} else if(ui->lE96->isChecked()) {
Lseries = ESeries::Series::E96;
} else {
Lseries = ESeries::Series::Ideal;
}
ESeries::Series Cseries;
if(ui->cE6->isChecked()) {
Cseries = ESeries::Series::E6;
} else if(ui->cE12->isChecked()) {
Cseries = ESeries::Series::E12;
} else if(ui->cE24->isChecked()) {
Cseries = ESeries::Series::E24;
} else if(ui->cE48->isChecked()) {
Cseries = ESeries::Series::E48;
} else if(ui->cE96->isChecked()) {
Cseries = ESeries::Series::E96;
} else {
Cseries = ESeries::Series::Ideal;
}
L = ESeries::ToESeries(L, Lseries);
C = ESeries::ToESeries(C, Cseries);
L2 = ESeries::ToESeries(L2, Lseries);
C2 = ESeries::ToESeries(C2, Cseries);
if(twoCs) {
for(auto b : ui->lGroup->buttons()) {
b->setEnabled(false);
}
for(auto b : ui->cGroup->buttons()) {
b->setEnabled(true);
}
ui->lL->setText("C1:");
ui->lC->setText("C2:");
ui->lValue->setUnit("F");
ui->cValue->setUnit("F");
ui->lValue->setValue(C2);
ui->cValue->setValue(C);
} else if(twoLs) {
for(auto b : ui->cGroup->buttons()) {
b->setEnabled(false);
}
for(auto b : ui->lGroup->buttons()) {
b->setEnabled(true);
}
ui->lC->setText("L2:");
ui->lL->setText("L1:");
ui->cValue->setUnit("H");
ui->lValue->setUnit("H");
ui->cValue->setValue(L2);
ui->lValue->setValue(L);
} else {
for(auto b : ui->cGroup->buttons()) {
b->setEnabled(true);
}
for(auto b : ui->lGroup->buttons()) {
b->setEnabled(true);
}
ui->lC->setText("C:");
ui->lL->setText("L:");
ui->lValue->setUnit("H");
ui->cValue->setUnit("F");
ui->lValue->setValue(L);
ui->cValue->setValue(C);
}
// calculate actual matched impedance
complex<double> Zmatched;
complex<double> Zp, Zs;
if(seriesC) {
if(twoLs) {
Zs = complex<double>(0, 2*M_PI*freq*L2);
Zp = complex<double>(0, 2*M_PI*freq*L);
} else if(twoCs) {
Zs = complex<double>(0, -1/(2*M_PI*freq*C2));
Zp = complex<double>(0, -1/(2*M_PI*freq*C));
} else {
Zs = complex<double>(0, -1/(2*M_PI*freq*C));
Zp = complex<double>(0, 2*M_PI*freq*L);
}
} else {
if(twoCs) {
Zs = complex<double>(0, -1/(2*M_PI*freq*C));
Zp = complex<double>(0, -1/(2*M_PI*freq*C2));
} else if(twoLs){
Zs = complex<double>(0, 2*M_PI*freq*L);
Zp = complex<double>(0, 2*M_PI*freq*L2);
} else {
Zs = complex<double>(0, 2*M_PI*freq*L);
Zp = complex<double>(0, -1/(2*M_PI*freq*C));
}
}
if(Z.real() > Z0) {
Zmatched = Z*Zp/(Z+Zp) + Zs;
} else {
Zmatched = Zp*(Z+Zs)/(Zp+Z+Zs);
}
ui->mReal->setValue(Zmatched.real());
ui->mImag->setValue(Zmatched.imag());
double reflection = abs((Zmatched-Z0)/(Zmatched+Z0));
auto loss = 20.0*log10(reflection);
ui->mLoss->setValue(loss);
// set correct image
if(Z.real() > Z0) {
if(X >= 0 && B >= 0) {
ui->Image->setPixmap(QPixmap(":/icons/sLpC_small.png"));
} else if(X < 0 && B < 0) {
ui->Image->setPixmap(QPixmap(":/icons/sCpL_small.png"));
} else if(X >= 0 && B < 0) {
ui->Image->setPixmap(QPixmap(":/icons/sCpC_small.png")); // TODO check
} else {
ui->Image->setPixmap(QPixmap(":/icons/sLpL_small.png")); // TODO check
}
} else {
if(X >= 0 && B >= 0) {
ui->Image->setPixmap(QPixmap(":/icons/pCsL_small.png"));
} else if(X < 0 && B < 0) {
ui->Image->setPixmap(QPixmap(":/icons/pLsC_small.png"));
} else if(X >= 0 && B < 0) {
ui->Image->setPixmap(QPixmap(":/icons/pLsL_small.png"));
} else {
ui->Image->setPixmap(QPixmap(":/icons/pCsC_small.png"));
}
}
} catch (exception e){
// something went wrong, probably caused by (intermediate) invalid input, such as f=0Hz
ui->lValue->setValue(nan(""));
ui->cValue->setValue(nan(""));
ui->mReal->setValue(nan(""));
ui->mImag->setValue(nan(""));
ui->mLoss->setValue(nan(""));
}
}

View File

@ -0,0 +1,28 @@
#ifndef IMPEDANCEMATCHDIALOG_H
#define IMPEDANCEMATCHDIALOG_H
#include <QDialog>
#include "Traces/tracemarkermodel.h"
namespace Ui {
class ImpedanceMatchDialog;
}
class ImpedanceMatchDialog : public QDialog
{
Q_OBJECT
public:
explicit ImpedanceMatchDialog(TraceMarkerModel &model, TraceMarker *marker = nullptr, QWidget *parent = nullptr);
~ImpedanceMatchDialog();
private slots:
void on_cSource_currentIndexChanged(int index);
void calculateMatch();
private:
static constexpr double Z0 = 50.0;
Ui::ImpedanceMatchDialog *ui;
};
#endif // IMPEDANCEMATCHDIALOG_H

View File

@ -0,0 +1,457 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ImpedanceMatchDialog</class>
<widget class="QDialog" name="ImpedanceMatchDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>664</width>
<height>497</height>
</rect>
</property>
<property name="windowTitle">
<string>Impedance Matching</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1">
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Unmatched Impedance Z</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QComboBox" name="cSource">
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="rbSeries">
<property name="text">
<string>Series</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">zGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbParallel">
<property name="text">
<string>Parallel</string>
</property>
<attribute name="buttonGroup">
<string notr="true">zGroup</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Real:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="SIUnitEdit" name="zReal"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Imag:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="zImag"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="frequencyLabel">
<property name="text">
<string>Frequency:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="zFreq"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Matched Impedance</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Real:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="SIUnitEdit" name="mReal">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Imag:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="mImag">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Return loss:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="mLoss">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QComboBox" name="cMatchType">
<item>
<property name="text">
<string>Series C - Parallel L</string>
</property>
</item>
<item>
<property name="text">
<string>Parallel C - Series L</string>
</property>
</item>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Inductor</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QRadioButton" name="lIdeal">
<property name="text">
<string>Ideal</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">lGroup</string>
</attribute>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="lE24">
<property name="text">
<string>E24</string>
</property>
<attribute name="buttonGroup">
<string notr="true">lGroup</string>
</attribute>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="lE6">
<property name="text">
<string>E6</string>
</property>
<attribute name="buttonGroup">
<string notr="true">lGroup</string>
</attribute>
</widget>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="lE48">
<property name="text">
<string>E48</string>
</property>
<attribute name="buttonGroup">
<string notr="true">lGroup</string>
</attribute>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="lE12">
<property name="text">
<string>E12</string>
</property>
<attribute name="buttonGroup">
<string notr="true">lGroup</string>
</attribute>
</widget>
</item>
<item row="2" column="1">
<widget class="QRadioButton" name="lE96">
<property name="text">
<string>E96</string>
</property>
<attribute name="buttonGroup">
<string notr="true">lGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="lL">
<property name="text">
<string>L:</string>
</property>
</widget>
</item>
<item>
<widget class="SIUnitEdit" name="lValue">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Capacitor</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QRadioButton" name="cE6">
<property name="text">
<string>E6</string>
</property>
<attribute name="buttonGroup">
<string notr="true">cGroup</string>
</attribute>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="cE12">
<property name="text">
<string>E12</string>
</property>
<attribute name="buttonGroup">
<string notr="true">cGroup</string>
</attribute>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="cIdeal">
<property name="text">
<string>Ideal</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">cGroup</string>
</attribute>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="cE24">
<property name="text">
<string>E24</string>
</property>
<attribute name="buttonGroup">
<string notr="true">cGroup</string>
</attribute>
</widget>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="cE48">
<property name="text">
<string>E48</string>
</property>
<attribute name="buttonGroup">
<string notr="true">cGroup</string>
</attribute>
</widget>
</item>
<item row="2" column="1">
<widget class="QRadioButton" name="cE96">
<property name="text">
<string>E96</string>
</property>
<attribute name="buttonGroup">
<string notr="true">cGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="lC">
<property name="text">
<string>C:</string>
</property>
</widget>
</item>
<item>
<widget class="SIUnitEdit" name="cValue">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="Image">
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SIUnitEdit</class>
<extends>QLineEdit</extends>
<header>CustomWidgets/siunitedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ImpedanceMatchDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ImpedanceMatchDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="zGroup"/>
<buttongroup name="cGroup"/>
<buttongroup name="lGroup"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,118 @@
#include "bodeplotaxisdialog.h"
#include "ui_bodeplotaxisdialog.h"
BodeplotAxisDialog::BodeplotAxisDialog(TraceBodePlot *plot) :
QDialog(nullptr),
ui(new Ui::BodeplotAxisDialog),
plot(plot)
{
ui->setupUi(this);
// Setup GUI connections
connect(ui->Y1type, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) {
ui->Y1log->setEnabled(index != 0);
ui->Y1linear->setEnabled(index != 0);
ui->Y1auto->setEnabled(index != 0);
bool autoRange = ui->Y1auto->isChecked();
ui->Y1min->setEnabled(index != 0 && !autoRange);
ui->Y1max->setEnabled(index != 0 && !autoRange);
ui->Y1divs->setEnabled(index != 0 && !autoRange);
auto type = (TraceBodePlot::YAxisType) index;
QString unit;
switch(type) {
case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break;
case TraceBodePlot::YAxisType::Phase: unit = "°"; break;
case TraceBodePlot::YAxisType::VSWR: unit = ""; break;
default: unit = ""; break;
}
ui->Y1min->setUnit(unit);
ui->Y1max->setUnit(unit);
ui->Y1divs->setUnit(unit);
});
connect(ui->Y1auto, &QCheckBox::toggled, [this](bool checked) {
ui->Y1min->setEnabled(!checked);
ui->Y1max->setEnabled(!checked);
ui->Y1divs->setEnabled(!checked);
});
connect(ui->Y2type, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) {
ui->Y2log->setEnabled(index != 0);
ui->Y2linear->setEnabled(index != 0);
ui->Y2auto->setEnabled(index != 0);
bool autoRange = ui->Y2auto->isChecked();
ui->Y2min->setEnabled(index != 0 && !autoRange);
ui->Y2max->setEnabled(index != 0 && !autoRange);
ui->Y2divs->setEnabled(index != 0 && !autoRange);
auto type = (TraceBodePlot::YAxisType) index;
QString unit;
switch(type) {
case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break;
case TraceBodePlot::YAxisType::Phase: unit = "°"; break;
case TraceBodePlot::YAxisType::VSWR: unit = ""; break;
default: unit = ""; break;
}
ui->Y2min->setUnit(unit);
ui->Y2max->setUnit(unit);
ui->Y2divs->setUnit(unit);
});
connect(ui->Y2auto, &QCheckBox::toggled, [this](bool checked) {
ui->Y2min->setEnabled(!checked);
ui->Y2max->setEnabled(!checked);
ui->Y2divs->setEnabled(!checked);
});
connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) {
ui->Xmin->setEnabled(!checked);
ui->Xmax->setEnabled(!checked);
ui->Xdivs->setEnabled(!checked);
});
ui->Xmin->setUnit("Hz");
ui->Xmax->setUnit("Hz");
ui->Xdivs->setUnit("Hz");
ui->Xmin->setPrefixes(" kMG");
ui->Xmax->setPrefixes(" kMG");
ui->Xdivs->setPrefixes(" kMG");
// Fill initial values
// assume same order in YAxisType enum as in ComboBox items
ui->Y1type->setCurrentIndex((int) plot->YAxis[0].type);
if(plot->YAxis[0].log) {
ui->Y1log->setChecked(true);
} else {
ui->Y1linear->setChecked(true);
}
ui->Y1auto->setChecked(plot->YAxis[0].autorange);
ui->Y1min->setValueQuiet(plot->YAxis[0].rangeMin);
ui->Y1max->setValueQuiet(plot->YAxis[0].rangeMax);
ui->Y1divs->setValueQuiet(plot->YAxis[0].rangeDiv);
ui->Y2type->setCurrentIndex((int) plot->YAxis[1].type);
if(plot->YAxis[1].log) {
ui->Y2log->setChecked(true);
} else {
ui->Y2linear->setChecked(true);
}
ui->Y2auto->setChecked(plot->YAxis[1].autorange);
ui->Y2min->setValueQuiet(plot->YAxis[1].rangeMin);
ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax);
ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv);
ui->Xauto->setChecked(plot->XAxis.autorange);
ui->Xmin->setValueQuiet(plot->XAxis.rangeMin);
ui->Xmax->setValueQuiet(plot->XAxis.rangeMax);
ui->Xdivs->setValueQuiet(plot->XAxis.rangeDiv);
}
BodeplotAxisDialog::~BodeplotAxisDialog()
{
delete ui;
}
void BodeplotAxisDialog::on_buttonBox_accepted()
{
// set plot values to the ones selected in the dialog
plot->setYAxis(0, (TraceBodePlot::YAxisType) ui->Y1type->currentIndex(), ui->Y1log->isChecked(), ui->Y1auto->isChecked(), ui->Y1min->value(), ui->Y1max->value(), ui->Y1divs->value());
plot->setYAxis(1, (TraceBodePlot::YAxisType) ui->Y2type->currentIndex(), ui->Y2log->isChecked(), ui->Y2auto->isChecked(), ui->Y2min->value(), ui->Y2max->value(), ui->Y2divs->value());
plot->setXAxis(ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value());
}

View File

@ -0,0 +1,27 @@
#ifndef BODEPLOTAXISDIALOG_H
#define BODEPLOTAXISDIALOG_H
#include <QDialog>
#include "tracebodeplot.h"
namespace Ui {
class BodeplotAxisDialog;
}
class BodeplotAxisDialog : public QDialog
{
Q_OBJECT
public:
explicit BodeplotAxisDialog(TraceBodePlot *plot);
~BodeplotAxisDialog();
private slots:
void on_buttonBox_accepted();
private:
Ui::BodeplotAxisDialog *ui;
TraceBodePlot *plot;
};
#endif // BODEPLOTAXISDIALOG_H

View File

@ -0,0 +1,462 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BodeplotAxisDialog</class>
<widget class="QDialog" name="BodeplotAxisDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>715</width>
<height>282</height>
</rect>
</property>
<property name="windowTitle">
<string>Axis Setup</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Primary Y axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="Y1type">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude</string>
</property>
</item>
<item>
<property name="text">
<string>Phase</string>
</property>
</item>
<item>
<property name="text">
<string>VSWR</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="Y1linear">
<property name="text">
<string>Linear</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y1group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Y1log">
<property name="text">
<string>Log</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y1group</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Y1auto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Y1max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Y1min"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Y1divs"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_7">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Secondary Y axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="Y2type">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude</string>
</property>
</item>
<item>
<property name="text">
<string>Phase</string>
</property>
</item>
<item>
<property name="text">
<string>VSWR</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="Y2linear">
<property name="text">
<string>Linear</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y2group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Y2log">
<property name="text">
<string>Log</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y2group</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Y2auto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Y2max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Y2min"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Y2divs"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_13">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>X axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QFormLayout" name="formLayout_6">
<item row="0" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Xauto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Xmax"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Xmin"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Xdivs"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SIUnitEdit</class>
<extends>QLineEdit</extends>
<header>CustomWidgets/siunitedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>BodeplotAxisDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>BodeplotAxisDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="Y1group"/>
<buttongroup name="Y2group"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,41 @@
#include "markerwidget.h"
#include "ui_markerwidget.h"
MarkerWidget::MarkerWidget(TraceMarkerModel &model, QWidget *parent) :
QWidget(parent),
ui(new Ui::MarkerWidget),
model(model)
{
ui->setupUi(this);
ui->tableView->setModel(&model);
ui->tableView->setItemDelegateForColumn(1, new TraceChooserDelegate);
connect(&model.getModel(), &TraceModel::traceAdded, this, &MarkerWidget::updatePersistentEditors);
connect(&model.getModel(), &TraceModel::traceRemoved, this, &MarkerWidget::updatePersistentEditors);
}
MarkerWidget::~MarkerWidget()
{
delete ui;
}
void MarkerWidget::on_bDelete_clicked()
{
model.removeMarker(ui->tableView->currentIndex().row());
}
void MarkerWidget::on_bAdd_clicked()
{
auto marker = model.createDefaultMarker();
model.addMarker(marker);
updatePersistentEditors();
}
void MarkerWidget::updatePersistentEditors(Trace *)
{
for(int i=0;i<model.rowCount();i++) {
auto index = model.index(i, TraceMarkerModel::ColIndexTrace);
ui->tableView->closePersistentEditor(index);
ui->tableView->openPersistentEditor(index);
}
}

View File

@ -0,0 +1,29 @@
#ifndef MARKERWIDGET_H
#define MARKERWIDGET_H
#include <QWidget>
#include "tracemarkermodel.h"
namespace Ui {
class MarkerWidget;
}
class MarkerWidget : public QWidget
{
Q_OBJECT
public:
explicit MarkerWidget(TraceMarkerModel &model, QWidget *parent = nullptr);
~MarkerWidget();
private slots:
void on_bDelete_clicked();
void on_bAdd_clicked();
void updatePersistentEditors(Trace *dummy = nullptr);
private:
Ui::MarkerWidget *ui;
TraceMarkerModel &model;
};
#endif // MARKERWIDGET_H

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MarkerWidget</class>
<widget class="QWidget" name="MarkerWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>729</width>
<height>195</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableView" name="tableView">
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="bAdd">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Add</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-add"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bDelete">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Delete</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-remove"/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,238 @@
#include "trace.h"
using namespace std;
Trace::Trace(QString name, QColor color)
: _name(name),
_color(color),
_liveType(LivedataType::Overwrite),
reflection(true),
visible(true),
paused(false),
touchstone(false),
calibration(false)
{
}
Trace::~Trace()
{
emit deleted(this);
}
void Trace::clear() {
if(paused) {
return;
}
_data.clear();
emit cleared(this);
emit dataChanged();
}
void Trace::addData(Trace::Data d) {
// add or replace data in vector while keeping it sorted with increasing frequency
auto lower = lower_bound(_data.begin(), _data.end(), d, [](const Data &lhs, const Data &rhs) -> bool {
return lhs.frequency < rhs.frequency;
});
if(lower == _data.end()) {
// highest frequency yet, add to vector
_data.push_back(d);
} else if(lower->frequency == d.frequency) {
switch(_liveType) {
case LivedataType::Overwrite:
// replace this data element
*lower = d;
break;
case LivedataType::MaxHold:
// replace this data element
if(abs(d.S) > abs(lower->S)) {
*lower = d;
}
break;
case LivedataType::MinHold:
// replace this data element
if(abs(d.S) < abs(lower->S)) {
*lower = d;
}
break;
}
} else {
// insert at this position
_data.insert(lower, d);
}
emit dataAdded(this, d);
emit dataChanged();
}
void Trace::setName(QString name) {
_name = name;
emit nameChanged();
}
void Trace::fillFromTouchstone(Touchstone &t, unsigned int parameter, QString filename)
{
if(parameter >= t.ports()*t.ports()) {
throw runtime_error("Parameter for touchstone out of range");
}
clear();
setTouchstoneParameter(parameter);
setTouchstoneFilename(filename);
for(unsigned int i=0;i<t.points();i++) {
auto tData = t.point(i);
Data d;
d.frequency = tData.frequency;
d.S = t.point(i).S[parameter];
addData(d);
}
// check if parameter is square (e.i. S11/S22/S33/...)
parameter++;
bool isSquare = false;
for (unsigned int i = 1; i * i <= parameter; i++) {
// If (i * i = n)
if ((parameter % i == 0) && (parameter / i == i)) {
isSquare = true;
break;
}
}
if(isSquare == 1) {
reflection = true;
} else {
reflection = false;
}
touchstone = true;
emit typeChanged(this);
}
void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param)
{
touchstone = false;
_liveType = type;
_liveParam = param;
if(param == LiveParameter::S11 || param == LiveParameter::S22) {
reflection = true;
} else {
reflection = false;
}
emit typeChanged(this);
}
void Trace::setColor(QColor color) {
if(_color != color) {
_color = color;
emit colorChanged(this);
}
}
void Trace::addMarker(TraceMarker *m)
{
markers.insert(m);
emit markerAdded(m);
}
void Trace::removeMarker(TraceMarker *m)
{
markers.erase(m);
emit markerRemoved(m);
}
void Trace::setReflection(bool value)
{
reflection = value;
}
void Trace::setCalibration(bool value)
{
calibration = value;
}
std::set<TraceMarker *> Trace::getMarkers() const
{
return markers;
}
void Trace::setVisible(bool visible)
{
if(visible != this->visible) {
this->visible = visible;
emit visibilityChanged(this);
}
}
bool Trace::isVisible()
{
return visible;
}
void Trace::pause()
{
paused = true;
}
void Trace::resume()
{
paused = false;
}
bool Trace::isPaused()
{
return paused;
}
bool Trace::isTouchstone()
{
return touchstone;
}
bool Trace::isCalibration()
{
return calibration;
}
bool Trace::isLive()
{
return !isCalibration() && !isTouchstone() && !isPaused();
}
bool Trace::isReflection()
{
return reflection;
}
QString Trace::getTouchstoneFilename() const
{
return touchstoneFilename;
}
void Trace::setTouchstoneFilename(const QString &value)
{
touchstoneFilename = value;
}
unsigned int Trace::getTouchstoneParameter() const
{
return touchstoneParameter;
}
std::complex<double> Trace::getData(double frequency)
{
if(_data.size() == 0 || frequency < minFreq() || frequency > maxFreq()) {
return std::numeric_limits<std::complex<double>>::quiet_NaN();
}
return sample(index(frequency)).S;
}
int Trace::index(double frequency)
{
auto lower = lower_bound(_data.begin(), _data.end(), frequency, [](const Data &lhs, const double freq) -> bool {
return lhs.frequency < freq;
});
return lower - _data.begin();
}
void Trace::setTouchstoneParameter(int value)
{
touchstoneParameter = value;
}

View File

@ -0,0 +1,105 @@
#ifndef TRACE_H
#define TRACE_H
#include <QObject>
#include <complex>
#include <map>
#include <QColor>
#include <set>
#include "touchstone.h"
class TraceMarker;
class Trace : public QObject
{
Q_OBJECT
public:
class Data {
public:
double frequency;
std::complex<double> S;
};
Trace(QString name = QString(), QColor color = Qt::darkYellow);
~Trace();
enum class LivedataType {
Overwrite,
MaxHold,
MinHold,
};
enum class LiveParameter {
S11,
S12,
S21,
S22,
};
void clear();
void addData(Data d);
void setName(QString name);
void fillFromTouchstone(Touchstone &t, unsigned int parameter, QString filename = QString());
void fromLivedata(LivedataType type, LiveParameter param);
QString name() { return _name; };
QColor color() { return _color; };
bool isVisible();
void pause();
void resume();
bool isPaused();
bool isTouchstone();
bool isCalibration();
bool isLive();
bool isReflection();
LiveParameter liveParameter() { return _liveParam; }
LivedataType liveType() { return _liveType; }
unsigned int size() { return _data.size(); }
double minFreq() { return _data.front().frequency; };
double maxFreq() { return _data.back().frequency; };
Data sample(unsigned int index) { return _data.at(index); }
QString getTouchstoneFilename() const;
unsigned int getTouchstoneParameter() const;
std::complex<double> getData(double frequency);
int index(double frequency);
std::set<TraceMarker *> getMarkers() const;
void setCalibration(bool value);
void setReflection(bool value);
public slots:
void setTouchstoneParameter(int value);
void setTouchstoneFilename(const QString &value);
void setVisible(bool visible);
void setColor(QColor color);
void addMarker(TraceMarker *m);
void removeMarker(TraceMarker *m);
private:
signals:
void cleared(Trace *t);
void typeChanged(Trace *t);
void dataAdded(Trace *t, Data d);
void deleted(Trace *t);
void visibilityChanged(Trace *t);
void dataChanged();
void nameChanged();
void colorChanged(Trace *t);
void markerAdded(TraceMarker *m);
void markerRemoved(TraceMarker *m);
private:
std::vector<Data> _data;
QString _name;
QColor _color;
LivedataType _liveType;
LiveParameter _liveParam;
bool reflection;
bool visible;
bool paused;
bool touchstone;
bool calibration;
QString touchstoneFilename;
unsigned int touchstoneParameter;
std::set<TraceMarker*> markers;
};
#endif // TRACE_H

View File

@ -0,0 +1,489 @@
#include "tracebodeplot.h"
#include <QGridLayout>
#include <qwt_plot_grid.h>
#include "qwtplotpiecewisecurve.h"
#include "qwt_series_data.h"
#include "trace.h"
#include <cmath>
#include <QFrame>
#include <qwt_plot_canvas.h>
#include <qwt_scale_div.h>
#include <qwt_plot_layout.h>
#include "tracemarker.h"
#include <qwt_symbol.h>
#include <qwt_plot_picker.h>
#include <qwt_picker_machine.h>
#include "bodeplotaxisdialog.h"
using namespace std;
static double AxisTransformation(TraceBodePlot::YAxisType type, complex<double> data) {
switch(type) {
case TraceBodePlot::YAxisType::Magnitude: return 20*log10(abs(data)); break;
case TraceBodePlot::YAxisType::Phase: return arg(data) * 180.0 / M_PI; break;
case TraceBodePlot::YAxisType::VSWR:
if(abs(data) < 1.0) {
return (1+abs(data)) / (1-abs(data));
}
break;
default: break;
}
return numeric_limits<double>::quiet_NaN();
}
template<TraceBodePlot::YAxisType E> class QwtTraceSeries : public QwtSeriesData<QPointF> {
public:
QwtTraceSeries(Trace &t)
: QwtSeriesData<QPointF>(),
t(t){};
size_t size() const override {
return t.size();
}
QPointF sample(size_t i) const override {
Trace::Data d = t.sample(i);
QPointF p;
p.setX(d.frequency);
p.setY(AxisTransformation(E, d.S));
return p;
}
QRectF boundingRect() const override {
return qwtBoundingRect(*this);
}
private:
Trace &t;
};
// Derived plotpicker, exposing transformation functions
class BodeplotPicker : public QwtPlotPicker {
public:
BodeplotPicker(int xAxis, int yAxis, RubberBand rubberBand, DisplayMode trackerMode, QWidget *w)
: QwtPlotPicker(xAxis, yAxis, rubberBand, trackerMode, w) {};
QPoint plotToPixel(const QPointF &pos) {
return transform(pos);
}
QPointF pixelToPlot(const QPoint &pos) {
return invTransform(pos);
}
};
TraceBodePlot::TraceBodePlot(TraceModel &model, QWidget *parent)
: TracePlot(parent),
selectedMarker(nullptr)
{
plot = new QwtPlot(this);
plot->setCanvasBackground(Background);
auto pal = plot->palette();
pal.setColor(QPalette::Window, Background);
pal.setColor(QPalette::WindowText, Border);
pal.setColor(QPalette::Text, Border);
auto canvas = new QwtPlotCanvas(plot);
canvas->setFrameStyle(QFrame::Plain);
plot->setCanvas(canvas);
plot->setPalette(pal);
plot->setAutoFillBackground(true);
auto selectPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
selectPicker->setStateMachine(new QwtPickerClickPointMachine);
auto drawPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
drawPicker->setStateMachine(new QwtPickerDragPointMachine);
drawPicker->setTrackerPen(QPen(Qt::white));
// Marker selection
connect(selectPicker, qOverload<const QPointF&>(&QwtPlotPicker::selected), [=](const QPointF pos) {
auto clickPoint = drawPicker->plotToPixel(pos);
unsigned int closestDistance = numeric_limits<unsigned int>::max();
TraceMarker *closestMarker = nullptr;
for(auto m : markers) {
auto markerPoint = drawPicker->plotToPixel(m.second->value());
auto yDiff = abs(markerPoint.y() - clickPoint.y());
auto xDiff = abs(markerPoint.x() - clickPoint.x());
unsigned int distance = xDiff * xDiff + yDiff * yDiff;
if(distance < closestDistance) {
closestDistance = distance;
closestMarker = m.first;
}
}
if(closestDistance <= 400) {
selectedMarker = closestMarker;
selectedCurve = curves[0][selectedMarker->trace()].curve;
} else {
selectedMarker = nullptr;
selectedCurve = nullptr;
}
});
// Marker movement
connect(drawPicker, qOverload<const QPointF&>(&QwtPlotPicker::moved), [=](const QPointF pos) {
if(!selectedMarker || !selectedCurve) {
return;
}
// int index = selectedCurve->closestPoint(pos.toPoint());
// qDebug() << index;
// if(index < 0) {
// // unable to find closest point
// return;
// }
// selectedMarker->setFrequency(selectedCurve->sample(index).x());
selectedMarker->setFrequency(pos.x());
});
QwtPlotGrid *grid = new QwtPlotGrid();
grid->setMajorPen(QPen(Divisions, 1.0, Qt::DotLine));
grid->attach(plot);
auto layout = new QGridLayout;
layout->addWidget(plot);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
plot->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
// plot->plotLayout()->setAlignCanvasToScales(true);
initializeTraceInfo(model);
setAutoFillBackground(true);
// Setup default axis
setYAxis(0, YAxisType::Magnitude, false, false, -120, 20, 10);
setYAxis(1, YAxisType::Phase, false, false, -180, 180, 30);
// enable autoscaling and set for full span (no information about actual span available yet)
setXAxis(0, 6000000000);
setXAxis(true, 0, 6000000000, 600000000);
}
TraceBodePlot::~TraceBodePlot()
{
for(int axis = 0;axis < 2;axis++) {
for(auto pd : curves[axis]) {
delete pd.second.curve;
}
}
}
void TraceBodePlot::setXAxis(double min, double max)
{
sweep_fmin = min;
sweep_fmax = max;
updateXAxis();
}
void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log, bool autorange, double min, double max, double div)
{
if(YAxis[axis].type != type) {
YAxis[axis].type = type;
// remove traces that are active but not supported with the new axis type
bool erased = false;
do {
erased = false;
for(auto t : tracesAxis[axis]) {
if(!supported(t, type)) {
enableTraceAxis(t, axis, false);
erased = true;
break;
}
}
} while(erased);
for(auto t : tracesAxis[axis]) {
// supported but needs an adjusted QwtSeriesData
auto td = curves[axis][t];
td.data = createQwtSeriesData(*t, axis);
// call to setSamples deletes old QwtSeriesData
td.curve->setSamples(td.data);
if(axis == 0) {
// update marker data
auto marker = t->getMarkers();
for(auto m : marker) {
markerDataChanged(m);
}
}
}
}
YAxis[axis].log = log;
YAxis[axis].autorange = autorange;
YAxis[axis].rangeMin = min;
YAxis[axis].rangeMax = max;
YAxis[axis].rangeDiv = div;
// enable/disable y axis
auto qwtaxis = axis == 0 ? QwtPlot::yLeft : QwtPlot::yRight;
plot->enableAxis(qwtaxis, type != YAxisType::Disabled);
if(autorange) {
plot->setAxisAutoScale(qwtaxis, true);
} else {
plot->setAxisScale(qwtaxis, min, max, div);
}
updateContextMenu();
replot();
}
void TraceBodePlot::setXAxis(bool autorange, double min, double max, double div)
{
XAxis.autorange = autorange;
XAxis.rangeMin = min;
XAxis.rangeMax = max;
XAxis.rangeDiv = div;
updateXAxis();
}
void TraceBodePlot::enableTrace(Trace *t, bool enabled)
{
for(int axis = 0;axis < 2;axis++) {
if(supported(t, YAxis[axis].type)) {
enableTraceAxis(t, axis, enabled);
}
}
}
void TraceBodePlot::updateContextMenu()
{
contextmenu->clear();
// for(int axis = 0;axis < 2;axis++) {
// QMenu *axisMenu;
// if(axis == 0) {
// axisMenu = contextmenu->addMenu("Primary Axis");
// } else {
// axisMenu = contextmenu->addMenu("Secondary Axis");
// }
// auto group = new QActionGroup(this);
// for(int i=0;i<(int) YAxisType::Last;i++) {
// auto action = new QAction(AxisTypeToName((YAxisType) i));
// action->setCheckable(true);
// group->addAction(action);
// if(YAxis[axis].type == (YAxisType) i) {
// action->setChecked(true);
// }
// connect(action, &QAction::triggered, [=](bool active) {
// if(active) {
// setYAxisType(axis, (YAxisType) i);
// }
// });
// }
// axisMenu->addActions(group->actions());
// }
auto setup = new QAction("Axis setup...");
connect(setup, &QAction::triggered, [this]() {
auto setup = new BodeplotAxisDialog(this);
setup->show();
});
contextmenu->addAction(setup);
for(int axis = 0;axis < 2;axis++) {
if(YAxis[axis].type == YAxisType::Disabled) {
continue;
}
if(axis == 0) {
contextmenu->addSection("Primary Traces");
} else {
contextmenu->addSection("Secondary Traces");
}
for(auto t : traces) {
// Skip traces that are not applicable for the selected axis type
if(!supported(t.first, YAxis[axis].type)) {
continue;
}
auto action = new QAction(t.first->name());
action->setCheckable(true);
if(tracesAxis[axis].find(t.first) != tracesAxis[axis].end()) {
action->setChecked(true);
}
connect(action, &QAction::toggled, [=](bool active) {
enableTraceAxis(t.first, axis, active);
});
contextmenu->addAction(action);
}
}
contextmenu->addSeparator();
auto close = new QAction("Close");
contextmenu->addAction(close);
connect(close, &QAction::triggered, [=]() {
markedForDeletion = true;
});
}
bool TraceBodePlot::supported(Trace *)
{
// potentially possible to add every kind of trace (depends on axis)
return true;
}
void TraceBodePlot::replot()
{
plot->replot();
}
QString TraceBodePlot::AxisTypeToName(TraceBodePlot::YAxisType type)
{
switch(type) {
case YAxisType::Disabled: return "Disabled"; break;
case YAxisType::Magnitude: return "Magnitude"; break;
case YAxisType::Phase: return "Phase"; break;
case YAxisType::VSWR: return "VSWR"; break;
default: return "Unknown"; break;
}
}
void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled)
{
bool alreadyEnabled = tracesAxis[axis].find(t) != tracesAxis[axis].end();
if(alreadyEnabled != enabled) {
if(enabled) {
tracesAxis[axis].insert(t);
CurveData cd;
cd.data = createQwtSeriesData(*t, axis);
cd.curve = new QwtPlotPiecewiseCurve();
cd.curve->attach(plot);
cd.curve->setYAxis(axis == 0 ? QwtPlot::yLeft : QwtPlot::yRight);
cd.curve->setSamples(cd.data);
curves[axis][t] = cd;
// connect signals
connect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot);
connect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged);
connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged);
connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot);
if(axis == 0) {
connect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded);
connect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved);
auto tracemarkers = t->getMarkers();
for(auto m : tracemarkers) {
markerAdded(m);
}
}
traceColorChanged(t);
} else {
tracesAxis[axis].erase(t);
// clean up and delete
if(curves[axis].find(t) != curves[axis].end()) {
if(curves[axis][t].curve) {
delete curves[axis][t].curve;
}
curves[axis].erase(t);
}
int otherAxis = axis == 0 ? 1 : 0;
if(curves[otherAxis].find(t) == curves[otherAxis].end()) {
// this trace is not used anymore, disconnect from notifications
disconnect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot);
disconnect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged);
disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged);
disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot);
}
if(axis == 0) {
disconnect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded);
disconnect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved);
auto tracemarkers = t->getMarkers();
for(auto m : tracemarkers) {
markerRemoved(m);
}
}
}
updateContextMenu();
replot();
}
}
bool TraceBodePlot::supported(Trace *t, TraceBodePlot::YAxisType type)
{
switch(type) {
case YAxisType::Disabled:
return false;
case YAxisType::VSWR:
if(!t->isReflection()) {
return false;
}
break;
default:
break;
}
return true;
}
void TraceBodePlot::updateXAxis()
{
if(XAxis.autorange) {
QList<double> tickList;
for(double tick = sweep_fmin;tick <= sweep_fmax;tick+= (sweep_fmax-sweep_fmin)/10) {
tickList.append(tick);
}
QwtScaleDiv scalediv(sweep_fmin, sweep_fmax, QList<double>(), QList<double>(), tickList);
plot->setAxisScaleDiv(QwtPlot::xBottom, scalediv);
} else {
plot->setAxisScale(QwtPlot::xBottom, XAxis.rangeMin, XAxis.rangeMax, XAxis.rangeDiv);
}
triggerReplot();
}
QwtSeriesData<QPointF> *TraceBodePlot::createQwtSeriesData(Trace &t, int axis)
{
switch(YAxis[axis].type) {
case YAxisType::Magnitude:
return new QwtTraceSeries<YAxisType::Magnitude>(t);
case YAxisType::Phase:
return new QwtTraceSeries<YAxisType::Phase>(t);
case YAxisType::VSWR:
return new QwtTraceSeries<YAxisType::VSWR>(t);
default:
return nullptr;
}
}
void TraceBodePlot::traceColorChanged(Trace *t)
{
for(int axis = 0;axis < 2;axis++) {
if(curves[axis].find(t) != curves[axis].end()) {
// trace active, change the pen color
if(t->isVisible()) {
if(axis == 0) {
curves[axis][t].curve->setPen(t->color());
} else {
curves[axis][t].curve->setPen(t->color(), 1.0, Qt::DashLine);
}
for(auto m : t->getMarkers()) {
if(markers.count(m)) {
markers[m]->attach(plot);
}
}
} else {
curves[axis][t].curve->setPen(t->color(), 0.0, Qt::NoPen);
for(auto m : t->getMarkers()) {
if(markers.count(m)) {
markers[m]->detach();
}
}
}
}
}
}
void TraceBodePlot::markerAdded(TraceMarker *m)
{
if(markers.count(m)) {
return;
}
QwtSymbol *sym=new QwtSymbol;
sym->setPixmap(m->getSymbol());
sym->setPinPoint(QPointF(m->getSymbol().width()/2, m->getSymbol().height()));
auto qwtMarker = new QwtPlotMarker;
qwtMarker->setSymbol(sym);
connect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged);
markers[m] = qwtMarker;
markerDataChanged(m);
qwtMarker->attach(plot);
triggerReplot();
}
void TraceBodePlot::markerRemoved(TraceMarker *m)
{
disconnect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged);
if(markers.count(m)) {
markers[m]->detach();
delete markers[m];
markers.erase(m);
}
triggerReplot();
}
void TraceBodePlot::markerDataChanged(TraceMarker *m)
{
auto qwtMarker = markers[m];
qwtMarker->setXValue(m->getFrequency());
qwtMarker->setYValue(AxisTransformation(YAxis[0].type, m->getData()));
triggerReplot();
}

View File

@ -0,0 +1,76 @@
#ifndef TRACEBODEPLOT_H
#define TRACEBODEPLOT_H
#include "traceplot.h"
#include <set>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_series_data.h>
#include <qwt_plot_marker.h>
class TraceBodePlot : public TracePlot
{
friend class BodeplotAxisDialog;
Q_OBJECT
public:
TraceBodePlot(TraceModel &model, QWidget *parent = nullptr);
~TraceBodePlot();
enum class YAxisType {
Disabled = 0,
Magnitude = 1,
Phase = 2,
VSWR = 3,
Last,
};
virtual void setXAxis(double min, double max) override;
void setYAxis(int axis, YAxisType type, bool log, bool autorange, double min, double max, double div);
void setXAxis(bool autorange, double min, double max, double div);
void enableTrace(Trace *t, bool enabled) override;
protected:
virtual void updateContextMenu();
virtual bool supported(Trace *t);
void replot() override;
private slots:
void traceColorChanged(Trace *t);
void markerAdded(TraceMarker *m) override;
void markerRemoved(TraceMarker *m) override;
void markerDataChanged(TraceMarker *m);
private:
QString AxisTypeToName(YAxisType type);
void enableTraceAxis(Trace *t, int axis, bool enabled);
bool supported(Trace *t, YAxisType type);
void updateXAxis();
QwtSeriesData<QPointF> *createQwtSeriesData(Trace &t, int axis);
std::set<Trace*> tracesAxis[2];
class Axis {
public:
YAxisType type;
bool log;
bool autorange;
double rangeMin;
double rangeMax;
double rangeDiv;
};
Axis YAxis[2];
Axis XAxis;
double sweep_fmin, sweep_fmax;
using CurveData = struct {
QwtPlotCurve *curve;
QwtSeriesData<QPointF> *data;
};
std::map<Trace*, CurveData> curves[2];
std::map<TraceMarker*, QwtPlotMarker*> markers;
QwtPlot *plot;
TraceMarker *selectedMarker;
QwtPlotCurve *selectedCurve;
};
#endif // TRACEBODEPLOT_H

View File

@ -0,0 +1,123 @@
#include "traceeditdialog.h"
#include "ui_traceeditdialog.h"
#include <QColorDialog>
#include <QFileDialog>
TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
QDialog(parent),
ui(new Ui::TraceEditDialog),
trace(t)
{
ui->setupUi(this);
ui->name->setText(t.name());
setColor(trace.color());
ui->GSource->setId(ui->bLive, 0);
ui->GSource->setId(ui->bFile, 1);
if(t.isCalibration()) {
// prevent editing imported calibration traces
ui->bLive->setEnabled(false);
ui->bFile->setEnabled(false);
ui->CLiveType->setEnabled(false);
ui->CLiveParam->setEnabled(false);
}
if(t.isTouchstone()) {
ui->bFile->click();
ui->touchstoneImport->setFile(t.getTouchstoneFilename());
}
auto updateFileStatus = [this]() {
// remove all options from paramater combo box
while(ui->CParameter->count() > 0) {
ui->CParameter->removeItem(0);
}
if (ui->bFile->isChecked() && !ui->touchstoneImport->getStatus()) {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
} else {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
auto touchstone = ui->touchstoneImport->getTouchstone();
for(unsigned int i=0;i<touchstone.ports();i++) {
for(unsigned int j=0;j<touchstone.ports();j++) {
QString name = "S"+QString::number(i+1)+QString::number(j+1);
ui->CParameter->addItem(name);
}
}
if(trace.getTouchstoneParameter() < touchstone.ports()*touchstone.ports()) {
ui->CParameter->setCurrentIndex(trace.getTouchstoneParameter());
} else {
ui->CParameter->setCurrentIndex(0);
}
}
};
switch(t.liveType()) {
case Trace::LivedataType::Overwrite: ui->CLiveType->setCurrentIndex(0); break;
case Trace::LivedataType::MaxHold: ui->CLiveType->setCurrentIndex(1); break;
case Trace::LivedataType::MinHold: ui->CLiveType->setCurrentIndex(2); break;
}
switch(t.liveParameter()) {
case Trace::LiveParameter::S11: ui->CLiveParam->setCurrentIndex(0); break;
case Trace::LiveParameter::S12: ui->CLiveParam->setCurrentIndex(1); break;
case Trace::LiveParameter::S21: ui->CLiveParam->setCurrentIndex(2); break;
case Trace::LiveParameter::S22: ui->CLiveParam->setCurrentIndex(3); break;
}
connect(ui->GSource, qOverload<int>(&QButtonGroup::buttonClicked), updateFileStatus);
connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, updateFileStatus);
connect(ui->touchstoneImport, &TouchstoneImport::filenameChanged, updateFileStatus);
updateFileStatus();
}
TraceEditDialog::~TraceEditDialog()
{
delete ui;
}
void TraceEditDialog::on_color_clicked()
{
auto color = QColorDialog::getColor(trace.color(), this, "Select color", QColorDialog::DontUseNativeDialog);
setColor(color);
}
void TraceEditDialog::on_buttonBox_accepted()
{
trace.setName(ui->name->text());
if(!trace.isCalibration()) {
// only apply changes if it is not a calibration trace
if (ui->bFile->isChecked()) {
auto t = ui->touchstoneImport->getTouchstone();
trace.fillFromTouchstone(t, ui->CParameter->currentIndex(), ui->touchstoneImport->getFilename());
} else {
Trace::LivedataType type;
Trace::LiveParameter param;
switch(ui->CLiveType->currentIndex()) {
case 0: type = Trace::LivedataType::Overwrite; break;
case 1: type = Trace::LivedataType::MaxHold; break;
case 2: type = Trace::LivedataType::MinHold; break;
}
switch(ui->CLiveParam->currentIndex()) {
case 0: param = Trace::LiveParameter::S11; break;
case 1: param = Trace::LiveParameter::S12; break;
case 2: param = Trace::LiveParameter::S21; break;
case 3: param = Trace::LiveParameter::S22; break;
}
trace.fromLivedata(type, param);
}
}
delete this;
}
void TraceEditDialog::setColor(QColor c)
{
QPalette pal = ui->color->palette();
pal.setColor(QPalette::Button, c);
ui->color->setAutoFillBackground(true);
ui->color->setPalette(pal);
ui->color->update();
trace.setColor(c);
}

View File

@ -0,0 +1,29 @@
#ifndef TRACEEDITDIALOG_H
#define TRACEEDITDIALOG_H
#include <QDialog>
#include "trace.h"
namespace Ui {
class TraceEditDialog;
}
class TraceEditDialog : public QDialog
{
Q_OBJECT
public:
explicit TraceEditDialog(Trace &t, QWidget *parent = nullptr);
~TraceEditDialog();
private slots:
void on_color_clicked();
void on_buttonBox_accepted();
private:
void setColor(QColor c);
Ui::TraceEditDialog *ui;
Trace &trace;
};
#endif // TRACEEDITDIALOG_H

View File

@ -0,0 +1,209 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceEditDialog</class>
<widget class="QDialog" name="TraceEditDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>282</width>
<height>355</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit Trace</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="name"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Color:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="color">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="bLive">
<property name="text">
<string>Live Capture</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">GSource</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="bFile">
<property name="text">
<string>From File</string>
</property>
<attribute name="buttonGroup">
<string notr="true">GSource</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="CLiveType">
<item>
<property name="text">
<string>Overwrite</string>
</property>
</item>
<item>
<property name="text">
<string>Max hold</string>
</property>
</item>
<item>
<property name="text">
<string>Min hold</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Parameter:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="CLiveParam">
<item>
<property name="text">
<string>S11</string>
</property>
</item>
<item>
<property name="text">
<string>S12</string>
</property>
</item>
<item>
<property name="text">
<string>S21</string>
</property>
</item>
<item>
<property name="text">
<string>S22</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="TouchstoneImport" name="touchstoneImport" native="true"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Parameter:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="CParameter"/>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TouchstoneImport</class>
<extends>QWidget</extends>
<header>CustomWidgets/touchstoneimport.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>GSource</sender>
<signal>buttonClicked(int)</signal>
<receiver>stack</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>146</x>
<y>216</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="GSource"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,186 @@
#include "traceexportdialog.h"
#include "ui_traceexportdialog.h"
#include <QDebug>
#include <QFileDialog>
#include "touchstone.h"
#include <QPushButton>
TraceExportDialog::TraceExportDialog(TraceModel &model, QWidget *parent) :
QDialog(parent),
ui(new Ui::TraceExportDialog),
model(model),
freqsSet(false)
{
ui->setupUi(this);
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
ui->gbTraces->setLayout(new QGridLayout);
on_sbPorts_valueChanged(ui->sbPorts->value());
}
TraceExportDialog::~TraceExportDialog()
{
delete ui;
}
bool TraceExportDialog::setTrace(int portFrom, int portTo, Trace *t)
{
if(portFrom < 0 || portFrom >= ui->sbPorts->value() || portTo < 0 || portTo >= ui->sbPorts->value()) {
// invalid port selection
return false;
}
auto c = cTraces[portTo][portFrom];
if(t) {
for(int i=1;i<c->count();i++) {
if(t == qvariant_cast<Trace*>(c->itemData(i))) {
// select this trace
c->setCurrentIndex(i);
return true;
}
}
// requested trace is not an option
return false;
} else {
// select 'none' option
c->setCurrentIndex(0);
return true;
}
}
bool TraceExportDialog::setPortNum(int ports)
{
if(ports < 1 || ports > 4) {
return false;
}
ui->sbPorts->setValue(ports);
return true;
}
void TraceExportDialog::on_buttonBox_accepted()
{
auto filename = QFileDialog::getSaveFileName(this, "Select file for exporting traces", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.length() > 0) {
auto ports = ui->sbPorts->value();
auto t = Touchstone(ports);
// add trace points to touchstone
for(unsigned int s=0;s<points;s++) {
Touchstone::Datapoint tData;
for(int i=0;i<ports;i++) {
for(int j=0;j<ports;j++) {
if(cTraces[i][j]->currentIndex() == 0) {
// missing trace, set to 0
tData.S.push_back(0.0);
} else {
Trace *t = qvariant_cast<Trace*>(cTraces[i][j]->itemData(cTraces[i][j]->currentIndex()));
// extract frequency (will overwrite for each trace but all traces have the same frequency points anyway)
tData.frequency = t->sample(s).frequency;
// add S parameter from trace to touchstone
tData.S.push_back(t->sample(s).S);
}
}
}
t.AddDatapoint(tData);
}
Touchstone::Unit unit;
switch(ui->cUnit->currentIndex()) {
case 0: unit = Touchstone::Unit::Hz; break;
case 1: unit = Touchstone::Unit::kHz; break;
case 2: unit = Touchstone::Unit::MHz; break;
case 3: unit = Touchstone::Unit::GHz; break;
}
Touchstone::Format format;
switch(ui->cFormat->currentIndex()) {
case 0: format = Touchstone::Format::DBAngle; break;
case 1: format = Touchstone::Format::MagnitudeAngle; break;
case 2: format = Touchstone::Format::RealImaginary; break;
}
t.toFile(filename.toStdString(), unit, format);
delete this;
}
}
void TraceExportDialog::on_sbPorts_valueChanged(int ports)
{
// remove the previous widgets
QGridLayout *layout = static_cast<QGridLayout*>(ui->gbTraces->layout());
QLayoutItem *child;
while ((child = layout->takeAt(0)) != 0) {
delete child->widget();
delete child;
}
cTraces.clear();
auto availableTraces = model.getTraces();
for(int i=0;i<ports;i++) {
cTraces.push_back(std::vector<QComboBox*>());
for(int j=0;j<ports;j++) {
auto l = new QLabel("S"+QString::number(i+1)+QString::number(j+1)+":");
auto c = new QComboBox();
// create possible trace selections
c->addItem("None");
for(auto t : availableTraces) {
if(i == j && !t->isReflection()) {
// can not add through measurement at reflection port
continue;
} else if(i != j && t->isReflection()) {
// can not add reflection measurement at through port
continue;
}
c->addItem(t->name(), QVariant::fromValue<Trace*>(t));
}
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [=](int) {
selectionChanged(c);
});
cTraces[i].push_back(c);
layout->addWidget(l, i, j*2);
layout->addWidget(c, i, j*2 + 1);
}
}
}
void TraceExportDialog::selectionChanged(QComboBox *w)
{
if(w->currentIndex() != 0 && !freqsSet) {
// the first trace has been selected, extract frequency info
Trace *t = qvariant_cast<Trace*>(w->itemData(w->currentIndex()));
points = t->size();
ui->points->setText(QString::number(points));
if(points > 0) {
lowerFreq = t->minFreq();
upperFreq = t->maxFreq();
ui->lowerFreq->setText(QString::number(lowerFreq));
ui->upperFreq->setText(QString::number(upperFreq));
}
freqsSet = true;
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(true);
// remove all trace options with incompatible frequencies
for(auto v1 : cTraces) {
for(auto c : v1) {
for(int i=1;i<c->count();i++) {
Trace *t = qvariant_cast<Trace*>(c->itemData(i));
if(t->size() != points || (points > 0 && (t->minFreq() != lowerFreq || t->maxFreq() != upperFreq))) {
// this trace is not available anymore
c->removeItem(i);
// decrement to check the next index in the next loop iteration
i--;
}
}
}
}
} else if(w->currentIndex() == 0 && freqsSet) {
// Check if all trace selections are set for none
for(auto v1 : cTraces) {
for(auto c : v1) {
if(c->currentIndex() != 0) {
// some trace is still selected, abort
return;
}
}
}
// all traces set for none
freqsSet = false;
ui->points->clear();
ui->lowerFreq->clear();
ui->upperFreq->clear();
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
}
}

View File

@ -0,0 +1,38 @@
#ifndef TRACEEXPORTDIALOG_H
#define TRACEEXPORTDIALOG_H
#include <QDialog>
#include <QComboBox>
#include "tracemodel.h"
#include <QSignalMapper>
namespace Ui {
class TraceExportDialog;
}
class TraceExportDialog : public QDialog
{
Q_OBJECT
public:
explicit TraceExportDialog(TraceModel &model, QWidget *parent = nullptr);
~TraceExportDialog();
bool setTrace(int portFrom, int portTo, Trace *t);
bool setPortNum(int ports);
private slots:
void on_buttonBox_accepted();
void on_sbPorts_valueChanged(int ports);
void selectionChanged(QComboBox *w);
private:
Ui::TraceExportDialog *ui;
TraceModel &model;
std::vector<std::vector<QComboBox*>> cTraces;
unsigned int points;
double lowerFreq, upperFreq;
bool freqsSet;
};
#endif // TRACEEXPORTDIALOG_H

View File

@ -0,0 +1,243 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceExportDialog</class>
<widget class="QDialog" name="TraceExportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>516</width>
<height>486</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Ports:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="sbPorts">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>4</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="gbTraces">
<property name="title">
<string>Traces</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Touchstone Settings</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Unit:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="cUnit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>3</number>
</property>
<item>
<property name="text">
<string>Hz</string>
</property>
</item>
<item>
<property name="text">
<string>kHz</string>
</property>
</item>
<item>
<property name="text">
<string>MHz</string>
</property>
</item>
<item>
<property name="text">
<string>GHz</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Format:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cFormat">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>db/Angle</string>
</property>
</item>
<item>
<property name="text">
<string>Mag/Angle</string>
</property>
</item>
<item>
<property name="text">
<string>Real/Imag</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Trace Info</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Points:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="points">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Lower Frequency:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lowerFreq">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Upper Frequency:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="upperFreq">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TraceExportDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,178 @@
#include "traceimportdialog.h"
#include "ui_traceimportdialog.h"
#include <QAbstractTableModel>
#include <QObject>
#include <QModelIndex>
#include <QColorDialog>
TraceImportDialog::TraceImportDialog(TraceModel &model, std::vector<Trace*> traces, QString prefix, QWidget *parent) :
QDialog(parent),
ui(new Ui::TraceImportDialog),
model(model)
{
ui->setupUi(this);
tableModel = new TraceParameterModel(traces, prefix);
ui->tableView->setModel(tableModel);
}
TraceImportDialog::~TraceImportDialog()
{
delete ui;
delete tableModel;
}
void TraceImportDialog::on_buttonBox_accepted()
{
tableModel->import(model);
}
TraceParameterModel::TraceParameterModel(std::vector<Trace *> traces, QString prefix, QObject *parent)
: QAbstractTableModel(parent),
traces(traces)
{
int hue = 0;
for(auto t : traces) {
Parameter p;
p.name = prefix + t->name();
p.color = QColor::fromHsl((hue++ * 30) % 360, 250, 128);
p.enabled = true;
p.trace = t->name();
params.push_back(p);
}
}
int TraceParameterModel::rowCount(const QModelIndex &) const {
return params.size();
}
int TraceParameterModel::columnCount(const QModelIndex &) const {
return 4;
}
QVariant TraceParameterModel::data(const QModelIndex &index, int role) const {
if (!index.isValid())
return QVariant();
if ((unsigned int) index.row() >= params.size())
return QVariant();
auto p = params[index.row()];
if (index.column() == 0) {
if (role == Qt::DisplayRole) {
return p.trace;
} else {
return QVariant();
}
} else if (index.column() == 1) {
if (role == Qt::CheckStateRole) {
if(p.enabled) {
return Qt::Checked;
} else {
return Qt::Unchecked;
}
} else {
return QVariant();
}
} else if (index.column() == 2) {
if (role == Qt::BackgroundRole || role == Qt::EditRole) {
if(p.enabled) {
return p.color;
} else {
return (QColor) Qt::gray;
}
} else {
return QVariant();
}
} else if (index.column() == 3) {
if (role == Qt::DisplayRole || role == Qt::EditRole) {
return p.name;
} else {
return QVariant();
}
} else {
return QVariant();
}
}
QVariant TraceParameterModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch(section) {
case 0: return "Parameter"; break;
case 1: return "Import"; break;
case 2: return "Color"; break;
case 3: return "Tracename"; break;
default: return QVariant(); break;
}
} else {
return QVariant();
}
}
bool TraceParameterModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if((unsigned int) index.row() >= params.size()) {
return false;
}
auto &p = params[index.row()];
if(role == Qt::CheckStateRole && index.column()==1) {
if((Qt::CheckState)value.toInt() == Qt::Checked) {
p.enabled = true;
} else {
p.enabled = false;
}
dataChanged(this->index(index.row(),2), this->index(index.row(),3));
return true;
} else if(role == Qt::EditRole && index.column() == 2) {
p.color = value.value<QColor>();
return true;
} else if(role == Qt::EditRole && index.column() == 3) {
p.name = value.toString();
return true;
}
return false;
}
Qt::ItemFlags TraceParameterModel::flags(const QModelIndex &index) const
{
int flags = Qt::NoItemFlags;
if(index.column() == 0) {
flags |= Qt::ItemIsEnabled;
} else if(index.column() == 1) {
flags |= Qt::ItemIsUserCheckable;
flags |= Qt::ItemIsEnabled;
} else if(index.column() == 2) {
if(params[index.row()].enabled) {
flags |= Qt::ItemIsEnabled;
}
} else if(index.column() == 3) {
flags |= Qt::ItemIsEditable;
if(params[index.row()].enabled) {
flags |= Qt::ItemIsEnabled;
}
}
return (Qt::ItemFlags) flags;
}
void TraceParameterModel::import(TraceModel &model)
{
for(unsigned int i=0;i<params.size();i++) {
if(params[i].enabled) {
traces[i]->setColor(params[i].color);
traces[i]->setName(params[i].name);
model.addTrace(traces[i]);
} else {
delete traces[i];
}
}
}
void TraceImportDialog::on_tableView_doubleClicked(const QModelIndex &index)
{
if(index.column() == 2 && tableModel->params[index.row()].enabled) {
auto initialColor = tableModel->params[index.row()].color;
auto newColor = QColorDialog::getColor(initialColor, this, "Select color", QColorDialog::DontUseNativeDialog);
if(newColor.isValid()) {
tableModel->setData(index, newColor);
}
}
}

View File

@ -0,0 +1,59 @@
#ifndef TRACEIMPORTDIALOG_H
#define TRACEIMPORTDIALOG_H
#include <QDialog>
#include "tracemodel.h"
#include <vector>
namespace Ui {
class TraceImportDialog;
}
class TraceParameterModel : public QAbstractTableModel
{
Q_OBJECT
friend class TraceImportDialog;
public:
TraceParameterModel(std::vector<Trace*> traces, QString prefix, QObject *parent = 0);
~TraceParameterModel(){};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
// adds all enabled traces to the model, deletes other traces
void import(TraceModel &model);
private:
class Parameter {
public:
bool enabled;
QString trace;
QString name;
QColor color;
};
std::vector<Parameter> params;
std::vector<Trace*> traces;
};
class TraceImportDialog : public QDialog
{
Q_OBJECT
public:
explicit TraceImportDialog(TraceModel &model, std::vector<Trace*> traces, QString prefix = QString(), QWidget *parent = nullptr);
~TraceImportDialog();
private slots:
void on_buttonBox_accepted();
void on_tableView_doubleClicked(const QModelIndex &index);
private:
Ui::TraceImportDialog *ui;
TraceModel &model;
TraceParameterModel *tableModel;
};
#endif // TRACEIMPORTDIALOG_H

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceImportDialog</class>
<widget class="QDialog" name="TraceImportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>420</height>
</rect>
</property>
<property name="windowTitle">
<string>Import Traces</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableView">
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>21</number>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>TraceImportDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TraceImportDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,122 @@
#include "tracemarker.h"
#include <QPainter>
TraceMarker::TraceMarker()
: parentTrace(nullptr),
frequency(1000000000),
number(1),
data(0)
{
}
TraceMarker::~TraceMarker()
{
if(parentTrace) {
parentTrace->removeMarker(this);
}
emit deleted(this);
}
void TraceMarker::assignTrace(Trace *t)
{
if(parentTrace) {
// remove connection from previous parent trace
parentTrace->removeMarker(this);
disconnect(parentTrace, &Trace::deleted, this, &TraceMarker::parentTraceDeleted);
disconnect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged);
disconnect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
}
parentTrace = t;
connect(parentTrace, &Trace::deleted, this, &TraceMarker::parentTraceDeleted);
connect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged);
connect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
constrainFrequency();
updateSymbol();
parentTrace->addMarker(this);
}
Trace *TraceMarker::trace()
{
return parentTrace;
}
QString TraceMarker::readableData()
{
auto db = 20*log10(abs(data));
auto phase = arg(data);
return QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
}
void TraceMarker::setFrequency(double freq)
{
frequency = freq;
constrainFrequency();
}
void TraceMarker::parentTraceDeleted(Trace *t)
{
if(t == parentTrace) {
delete this;
}
}
void TraceMarker::traceDataChanged()
{
// some data of the parent trace changed, check if marker data also changed
auto tracedata = parentTrace->getData(frequency);
if(tracedata != data) {
data = tracedata;
emit dataChanged(this);
}
}
void TraceMarker::updateSymbol()
{
constexpr int width = 15, height = 15;
symbol = QPixmap(width, height);
symbol.fill(Qt::transparent);
QPainter p(&symbol);
p.setRenderHint(QPainter::Antialiasing);
QPointF points[] = {QPointF(0,0),QPointF(width,0),QPointF(width/2,height)};
auto traceColor = parentTrace->color();
p.setPen(traceColor);
p.setBrush(traceColor);
p.drawConvexPolygon(points, 3);
auto brightness = traceColor.redF() * 0.299 + traceColor.greenF() * 0.587 + traceColor.blueF() * 0.114;
p.setPen((brightness > 0.6) ? Qt::black : Qt::white);
p.drawText(QRectF(0,0,width, height*2.0/3.0), Qt::AlignCenter, QString::number(number));
}
void TraceMarker::constrainFrequency()
{
if(parentTrace && parentTrace->size() > 0) {
if(frequency > parentTrace->maxFreq()) {
frequency = parentTrace->maxFreq();
} else if(frequency < parentTrace->minFreq()) {
frequency = parentTrace->minFreq();
}
traceDataChanged();
}
}
int TraceMarker::getNumber() const
{
return number;
}
std::complex<double> TraceMarker::getData() const
{
return data;
}
QPixmap &TraceMarker::getSymbol()
{
return symbol;
}
double TraceMarker::getFrequency() const
{
return frequency;
}

View File

@ -0,0 +1,45 @@
#ifndef TRACEMARKER_H
#define TRACEMARKER_H
#include <QPixmap>
#include <QObject>
#include "trace.h"
class TraceMarker : public QObject
{
friend class TraceMarkerModel;
Q_OBJECT;
public:
TraceMarker();
~TraceMarker();
void assignTrace(Trace *t);
Trace* trace();
QString readableData();
double getFrequency() const;
std::complex<double> getData() const;
QPixmap& getSymbol();
int getNumber() const;
public slots:
void setFrequency(double freq);
signals:
void deleted(TraceMarker *m);
void dataChanged(TraceMarker *m);
private slots:
void parentTraceDeleted(Trace *t);
void traceDataChanged();
void updateSymbol();
private:
void constrainFrequency();
Trace *parentTrace;
double frequency;
int number;
std::complex<double> data;
QPixmap symbol;
};
#endif // TRACEMARKER_H

View File

@ -0,0 +1,228 @@
#include "tracemarkermodel.h"
#include "unit.h"
#include <QComboBox>
#include <QApplication>
TraceMarkerModel::TraceMarkerModel(TraceModel &model, QObject *parent)
: QAbstractTableModel(parent),
model(model)
{
markers.clear();
}
TraceMarker *TraceMarkerModel::createDefaultMarker()
{
// find lowest free number
int number = 0;
bool used;
do {
number++;
used = false;
for(auto m : markers) {
if(m->number == number) {
used = true;
break;
}
}
} while (used);
auto marker = new TraceMarker();
marker->number = number;
marker->frequency = 2150000000;
marker->assignTrace(model.trace(0));
return marker;
}
void TraceMarkerModel::addMarker(TraceMarker *t)
{
beginInsertRows(QModelIndex(), markers.size(), markers.size());
markers.push_back(t);
endInsertRows();
connect(t, &TraceMarker::dataChanged, this, &TraceMarkerModel::markerDataChanged);
connect(t, &TraceMarker::deleted, this, qOverload<TraceMarker*>(&TraceMarkerModel::removeMarker));
emit markerAdded(t);
}
void TraceMarkerModel::removeMarker(unsigned int index, bool delete_marker)
{
if (index < markers.size()) {
beginRemoveRows(QModelIndex(), index, index);
if(delete_marker) {
delete markers[index];
}
markers.erase(markers.begin() + index);
endRemoveRows();
}
}
void TraceMarkerModel::removeMarker(TraceMarker *m)
{
auto it = std::find(markers.begin(), markers.end(), m);
if(it != markers.end()) {
removeMarker(it - markers.begin(), false);
}
}
void TraceMarkerModel::markerDataChanged(TraceMarker *)
{
emit dataChanged(index(0, ColIndexFreq), index(markers.size()-1, ColIndexData));
}
TraceMarker *TraceMarkerModel::marker(int index)
{
return markers.at(index);
}
int TraceMarkerModel::rowCount(const QModelIndex &) const
{
return markers.size();
}
int TraceMarkerModel::columnCount(const QModelIndex &) const
{
return 4;
}
QVariant TraceMarkerModel::data(const QModelIndex &index, int role) const
{
auto marker = markers[index.row()];
switch(index.column()) {
case ColIndexNumber:
switch(role) {
case Qt::DisplayRole: return QVariant((unsigned int)marker->number); break;
}
case ColIndexTrace:
switch(role) {
case Qt::DisplayRole:
if(marker->parentTrace) {
return marker->parentTrace->name();
}
break;
}
case ColIndexFreq:
switch(role) {
case Qt::DisplayRole: return Unit::ToString(marker->frequency, "Hz", " kMG", 6); break;
}
case ColIndexData:
switch(role) {
case Qt::DisplayRole: return marker->readableData(); break;
}
break;
}
return QVariant();
}
QVariant TraceMarkerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch(section) {
case ColIndexNumber: return "#"; break;
case ColIndexTrace: return "Trace"; break;
case ColIndexFreq: return "Frequency"; break;
case ColIndexData: return "Data"; break;
default: return QVariant(); break;
}
} else {
return QVariant();
}
}
bool TraceMarkerModel::setData(const QModelIndex &index, const QVariant &value, int)
{
if((unsigned int) index.row() >= markers.size()) {
return false;
}
auto m = markers[index.row()];
switch(index.column()) {
case ColIndexNumber: {
bool convertOk;
unsigned int number;
number = value.toUInt(&convertOk);
if(convertOk) {
m->number = number;
return true;
}
break;
}
case ColIndexTrace: {
auto trace = qvariant_cast<Trace*>(value);
m->assignTrace(trace);
}
break;
case ColIndexFreq: {
auto newval = Unit::FromString(value.toString(), "Hz", " kMG");
if(!qIsNaN(newval)) {
m->setFrequency(newval);
}
}
break;
}
return false;
}
Qt::ItemFlags TraceMarkerModel::flags(const QModelIndex &index) const
{
int flags = Qt::NoItemFlags;
switch(index.column()) {
case ColIndexNumber: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
case ColIndexTrace: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
case ColIndexFreq: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
case ColIndexData: flags |= Qt::ItemIsEnabled; break;
}
return (Qt::ItemFlags) flags;
}
std::vector<TraceMarker *> TraceMarkerModel::getMarker()
{
return markers;
}
std::vector<TraceMarker *> TraceMarkerModel::getMarker(Trace *t)
{
std::vector<TraceMarker*> attachedMarkers;
for(auto m : markers) {
if(m->parentTrace == t) {
attachedMarkers.push_back(m);
}
}
return attachedMarkers;
}
TraceModel &TraceMarkerModel::getModel()
{
return model;
}
QWidget *TraceChooserDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
{
auto model = (TraceMarkerModel*) index.model();
auto c = new QComboBox(parent);
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [c](int) {
c->clearFocus();
});
auto traces = model->getModel().getTraces();
for(auto t : traces) {
c->addItem(t->name(), QVariant::fromValue<Trace*>(t));
}
return c;
}
void TraceChooserDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
auto model = (TraceMarkerModel*) index.model();
auto marker = model->getMarker()[index.row()];
auto c = (QComboBox*) editor;
for(int i=0;i<c->count();i++) {
if(qvariant_cast<Trace*>(c->itemData(i)) == marker->trace()) {
c->setCurrentIndex(i);
return;
}
}
}
void TraceChooserDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
auto markerModel = (TraceMarkerModel*) model;
auto c = (QComboBox*) editor;
markerModel->setData(index, c->itemData(c->currentIndex()));
}

View File

@ -0,0 +1,61 @@
#ifndef TRACEMARKERMODEL_H
#define TRACEMARKERMODEL_H
#include <QAbstractTableModel>
#include "tracemarker.h"
#include <vector>
#include "tracemodel.h"
#include <QItemDelegate>
class TraceChooserDelegate : public QItemDelegate
{
Q_OBJECT;
QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
void setEditorData(QWidget * editor, const QModelIndex & index) const override;
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
};
class TraceMarkerModel : public QAbstractTableModel
{
Q_OBJECT
public:
TraceMarkerModel(TraceModel &model, QObject *parent = 0);
enum {
ColIndexNumber = 0,
ColIndexTrace = 1,
ColIndexFreq = 2,
ColIndexData = 3,
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
TraceMarker* createDefaultMarker();
TraceMarker *marker(int index);
std::vector<TraceMarker*> getMarker();
std::vector<TraceMarker*> getMarker(Trace *t);
TraceModel& getModel();
public slots:
void addMarker(TraceMarker *t);
void removeMarker(unsigned int index, bool delete_marker = true);
void removeMarker(TraceMarker *m);
signals:
void markerAdded(TraceMarker *t);
private slots:
void markerDataChanged(TraceMarker *m);
private:
std::vector<TraceMarker*> markers;
TraceModel &model;
};
#endif // TRACEMARKERMODEL_H

View File

@ -0,0 +1,137 @@
#include "tracemodel.h"
#include <QIcon>
using namespace std;
TraceModel::TraceModel(QObject *parent)
: QAbstractTableModel(parent)
{
traces.clear();
}
void TraceModel::addTrace(Trace *t)
{
beginInsertRows(QModelIndex(), traces.size(), traces.size());
traces.push_back(t);
endInsertRows();
emit traceAdded(t);
}
void TraceModel::removeTrace(unsigned int index)
{
if (index < traces.size()) {
beginRemoveRows(QModelIndex(), index, index);
auto trace = traces[index];
delete trace;
traces.erase(traces.begin() + index);
endRemoveRows();
emit traceRemoved(trace);
}
}
Trace *TraceModel::trace(unsigned int index)
{
return traces.at(index);
}
void TraceModel::toggleVisibility(unsigned int index)
{
if (index < traces.size()) {
traces[index]->setVisible(!traces[index]->isVisible());
emit dataChanged(createIndex(index, 0), createIndex(index, 0));
}
}
void TraceModel::togglePause(unsigned int index)
{
if (index < traces.size()) {
if(traces[index]->isPaused()) {
traces[index]->resume();
} else {
traces[index]->pause();
}
emit dataChanged(createIndex(index, 1), createIndex(index, 1));
}
}
int TraceModel::rowCount(const QModelIndex &) const
{
return traces.size();
}
int TraceModel::columnCount(const QModelIndex &) const
{
return 3;
}
QVariant TraceModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if ((unsigned int) index.row() >= traces.size())
return QVariant();
if (index.column() == 0) {
if (role == Qt::DecorationRole) {
if (traces[index.row()]->isVisible()) {
return QIcon(":/icons/visible.svg");
} else {
return QIcon(":/icons/invisible.svg");
}
} else {
return QVariant();
}
} else if (index.column() == 1) {
if (role == Qt::DecorationRole && !traces[index.row()]->isTouchstone()) {
if (traces[index.row()]->isPaused()) {
return QIcon(":/icons/pause.svg");
} else {
return QIcon(":/icons/play.svg");
}
} else {
return QVariant();
}
} else if (index.column() == 2) {
if (role == Qt::DisplayRole) {
return traces[index.row()]->name();
} else if (role == Qt::ForegroundRole) {
return traces[index.row()]->color();
} else {
return QVariant();
}
} else {
return QVariant();
}
}
std::vector<Trace *> TraceModel::getTraces()
{
return traces;
}
void TraceModel::clearVNAData()
{
for(auto t : traces) {
if (!t->isTouchstone()) {
// this trace is fed from live data
t->clear();
}
}
}
void TraceModel::addVNAData(Protocol::Datapoint d)
{
for(auto t : traces) {
if (t->isLive()) {
Trace::Data td;
td.frequency = d.frequency;
switch(t->liveParameter()) {
case Trace::LiveParameter::S11: td.S = complex<double>(d.real_S11, d.imag_S11); break;
case Trace::LiveParameter::S12: td.S = complex<double>(d.real_S12, d.imag_S12); break;
case Trace::LiveParameter::S21: td.S = complex<double>(d.real_S21, d.imag_S21); break;
case Trace::LiveParameter::S22: td.S = complex<double>(d.real_S22, d.imag_S22); break;
}
t->addData(td);
}
}
}

View File

@ -0,0 +1,38 @@
#ifndef TRACEMODEL_H
#define TRACEMODEL_H
#include <QAbstractTableModel>
#include "trace.h"
#include <vector>
#include "Device/device.h"
class TraceModel : public QAbstractTableModel
{
Q_OBJECT
public:
TraceModel(QObject *parent = 0);
void addTrace(Trace *t);
void removeTrace(unsigned int index);
Trace *trace(unsigned int index);
void toggleVisibility(unsigned int index);
void togglePause(unsigned int index);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
std::vector<Trace*> getTraces();
signals:
void traceAdded(Trace *t);
void traceRemoved(Trace *t);
public slots:
void clearVNAData();
void addVNAData(Protocol::Datapoint d);
private:
std::vector<Trace*> traces;
};
#endif // TRACEMODEL_H

View File

@ -0,0 +1,145 @@
#include "traceplot.h"
const QColor TracePlot::Background = QColor(0,0,0);
const QColor TracePlot::Border = QColor(255,255,255);
const QColor TracePlot::Divisions = QColor(255,255,255);
#include "tracemarker.h"
std::set<TracePlot*> TracePlot::plots;
TracePlot::TracePlot(QWidget *parent) : QWidget(parent)
{
contextmenu = new QMenu();
markedForDeletion = false;
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
lastUpdate = QTime::currentTime();
plots.insert(this);
}
TracePlot::~TracePlot()
{
plots.erase(this);
delete contextmenu;
}
void TracePlot::enableTrace(Trace *t, bool enabled)
{
if(traces[t] != enabled) {
traces[t] = enabled;
if(enabled) {
// connect signals
connect(t, &Trace::dataChanged, this, &TracePlot::triggerReplot);
connect(t, &Trace::visibilityChanged, this, &TracePlot::triggerReplot);
connect(t, &Trace::markerAdded, this, &TracePlot::markerAdded);
connect(t, &Trace::markerRemoved, this, &TracePlot::markerRemoved);
} else {
// disconnect from notifications
disconnect(t, &Trace::dataChanged, this, &TracePlot::triggerReplot);
disconnect(t, &Trace::visibilityChanged, this, &TracePlot::triggerReplot);
disconnect(t, &Trace::markerAdded, this, &TracePlot::markerAdded);
disconnect(t, &Trace::markerRemoved, this, &TracePlot::markerRemoved);
}
updateContextMenu();
triggerReplot();
}
}
void TracePlot::mouseDoubleClickEvent(QMouseEvent *) {
emit doubleClicked(this);
}
void TracePlot::UpdateSpan(double fmin, double fmax)
{
for(auto p : plots) {
p->setXAxis(fmin, fmax);
}
}
void TracePlot::initializeTraceInfo(TraceModel &model)
{
// Populate already present traces
auto tvect = model.getTraces();
for(auto t : tvect) {
newTraceAvailable(t);
}
// connect notification of traces added at later point
connect(&model, &TraceModel::traceAdded, this, &TracePlot::newTraceAvailable);
}
void TracePlot::contextMenuEvent(QContextMenuEvent *event)
{
contextmenu->exec(event->globalPos());
if(markedForDeletion) {
emit deleted(this);
delete this;
}
}
void TracePlot::updateContextMenu()
{
contextmenu->clear();
contextmenu->addSection("Traces");
// Populate context menu
for(auto t : traces) {
auto action = new QAction(t.first->name());
action->setCheckable(true);
if(t.second) {
action->setChecked(true);
}
connect(action, &QAction::toggled, [=](bool active) {
enableTrace(t.first, active);
});
contextmenu->addAction(action);
}
contextmenu->addSeparator();
auto close = new QAction("Close");
contextmenu->addAction(close);
connect(close, &QAction::triggered, [=]() {
markedForDeletion = true;
});
}
std::set<TracePlot *> TracePlot::getPlots()
{
return plots;
}
void TracePlot::newTraceAvailable(Trace *t)
{
if(supported(t)) {
traces[t] = false;
connect(t, &Trace::deleted, this, &TracePlot::traceDeleted);
connect(t, &Trace::nameChanged, this, &TracePlot::updateContextMenu);
connect(t, &Trace::typeChanged, this, &TracePlot::updateContextMenu);
}
updateContextMenu();
}
void TracePlot::traceDeleted(Trace *t)
{
enableTrace(t, false);
traces.erase(t);
updateContextMenu();
triggerReplot();
}
void TracePlot::triggerReplot()
{
auto now = QTime::currentTime();
if (lastUpdate.msecsTo(now) >= MinUpdateInterval) {
replot();
lastUpdate = now;
}
}
void TracePlot::markerAdded(TraceMarker *m)
{
connect(m, &TraceMarker::dataChanged, this, &TracePlot::triggerReplot);
triggerReplot();
}
void TracePlot::markerRemoved(TraceMarker *)
{
triggerReplot();
}

View File

@ -0,0 +1,55 @@
#ifndef TRACEPLOT_H
#define TRACEPLOT_H
#include <QWidget>
#include "tracemodel.h"
#include <QMenu>
#include <QContextMenuEvent>
#include <QTime>
class TracePlot : public QWidget
{
Q_OBJECT
public:
TracePlot( QWidget *parent = nullptr);
~TracePlot();
virtual void enableTrace(Trace *t, bool enabled);
void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void setXAxis(double min, double max){Q_UNUSED(min);Q_UNUSED(max)};
static std::set<TracePlot *> getPlots();
static void UpdateSpan(double fmin, double fmax);
signals:
void doubleClicked(QWidget *w);
void deleted(TracePlot*);
protected:
static const QColor Background;// = QColor(0,0,0);
static const QColor Border;// = QColor(255,255,255);
static const QColor Divisions;// = QColor(255,255,255);
static constexpr int MinUpdateInterval = 100;
// need to be called in derived class constructor
void initializeTraceInfo(TraceModel &model);
void contextMenuEvent(QContextMenuEvent *event) override;
virtual void updateContextMenu();
virtual bool supported(Trace *t) = 0;
virtual void replot(){};
std::map<Trace*, bool> traces;
QMenu *contextmenu;
QTime lastUpdate;
bool markedForDeletion;
static std::set<TracePlot*> plots;
protected slots:
void newTraceAvailable(Trace *t);
void traceDeleted(Trace *t);
void triggerReplot();
virtual void markerAdded(TraceMarker *m);
virtual void markerRemoved(TraceMarker *m);
};
#endif // TRACEPLOT_H

View File

@ -0,0 +1,184 @@
#include "tracesmithchart.h"
#include <QPainter>
#include <array>
#include <math.h>
#include "tracemarker.h"
#include <QDebug>
using namespace std;
TraceSmithChart::TraceSmithChart(TraceModel &model, QWidget *parent)
: TracePlot(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);
initializeTraceInfo(model);
}
QPoint TraceSmithChart::plotToPixel(std::complex<double> S)
{
QPoint ret;
ret.setX(S.real() * plotToPixelXScale + plotToPixelXOffset);
ret.setY(S.imag() * plotToPixelYScale + plotToPixelYOffset);
return ret;
}
std::complex<double> TraceSmithChart::pixelToPlot(const QPoint &pos)
{
return complex<double>((pos.x() - plotToPixelXOffset) / plotToPixelXScale, (pos.y() - plotToPixelYOffset) / plotToPixelYScale);
}
void TraceSmithChart::mousePressEvent(QMouseEvent *event)
{
auto clickPoint = event->pos();
unsigned int closestDistance = numeric_limits<unsigned int>::max();
TraceMarker *closestMarker = nullptr;
for(auto t : traces) {
auto markers = t.first->getMarkers();
for(auto m : markers) {
auto S = m->getData();
auto markerPoint = plotToPixel(S);
auto yDiff = abs(markerPoint.y() - clickPoint.y());
auto xDiff = abs(markerPoint.x() - clickPoint.x());
unsigned int distance = xDiff * xDiff + yDiff * yDiff;
if(distance < closestDistance) {
closestDistance = distance;
closestMarker = m;
}
}
}
if(closestDistance <= 400) {
selectedMarker = closestMarker;
} else {
selectedMarker = nullptr;
}
}
void TraceSmithChart::mouseMoveEvent(QMouseEvent *event)
{
if(selectedMarker) {
auto t = selectedMarker->trace();
auto mouseS = pixelToPlot(event->pos());
auto samples = t->size();
double closestDistance = numeric_limits<double>::max();
unsigned int closestIndex = 0;
for(unsigned int i=0;i<samples;i++) {
auto data = t->sample(i);
auto distance = norm(data.S - mouseS);
if(distance < closestDistance) {
closestDistance = distance;
closestIndex = i;
}
}
selectedMarker->setFrequency(t->sample(closestIndex).frequency);
}
}
void TraceSmithChart::draw(QPainter * painter, double width_factor) {
painter->setPen(QPen(1.0 * width_factor));
painter->setBrush(palette().windowText());
painter->setRenderHint(QPainter::Antialiasing);
// // Display parameter name
// QFont font = painter->font();
// font.setPixelSize(48);
// font.setBold(true);
// painter->setFont(font);
// painter->drawText(-512, -512, title);
// Outer circle
painter->setPen(QPen(Border, 1.5 * width_factor));
QRectF rectangle(-smithCoordMax, -smithCoordMax, 2*smithCoordMax, 2*smithCoordMax);
painter->drawArc(rectangle, 0, 5760);
constexpr int Circles = 6;
painter->setPen(QPen(Divisions, 0.5 * width_factor, Qt::DashLine));
for(int i=1;i<Circles;i++) {
rectangle.adjust(2*smithCoordMax/Circles, smithCoordMax/Circles, 0, -smithCoordMax/Circles);
painter->drawArc(rectangle, 0, 5760);
}
painter->drawLine(-smithCoordMax, 0, smithCoordMax, 0);
constexpr std::array<double, 5> impedanceLines = {10, 25, 50, 100, 250};
for(auto z : impedanceLines) {
z /= ReferenceImpedance;
auto radius = smithCoordMax * 1.0/z;
double span = M_PI - 2 * atan(radius/smithCoordMax);
span *= 5760 / (2 * M_PI);
QRectF rectangle(smithCoordMax - radius, -2*radius, 2 * radius, 2 * radius);
painter->drawArc(rectangle, 4320 - span, span);
rectangle = QRectF(smithCoordMax - radius, 0, 2 * radius, 2 * radius);
painter->drawArc(rectangle, 1440, span);
}
for(auto t : traces) {
if(!t.second) {
// trace not enabled in plot
continue;
}
auto trace = t.first;
if(!trace->isVisible()) {
// trace marked invisible
continue;
}
painter->setPen(QPen(trace->color(), 1.5 * width_factor));
int nPoints = trace->size();
for(int i=1;i<nPoints;i++) {
auto last = trace->sample(i-1).S;
auto now = trace->sample(i).S;
if(isnan(now.real())) {
break;
}
// scale to size of smith diagram
last *= smithCoordMax;
now *= smithCoordMax;
// draw line
painter->drawLine(std::real(last), -std::imag(last), std::real(now), -std::imag(now));
}
auto markers = t.first->getMarkers();
for(auto m : markers) {
auto coords = m->getData();
coords *= smithCoordMax;
auto symbol = m->getSymbol();
symbol = symbol.scaled(symbol.width()*width_factor, symbol.height()*width_factor);
painter->drawPixmap(coords.real() - symbol.width()/2, -coords.imag() - symbol.height(), symbol);
}
}
}
void TraceSmithChart::replot()
{
update();
}
void TraceSmithChart::paintEvent(QPaintEvent * /* the event */)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBackground(QBrush(Background));
painter.fillRect(0, 0, width(), height(), QBrush(Background));
double side = qMin(width(), height()) * screenUsage;
painter.setViewport((width()-side)/2, (height()-side)/2, side, side);
painter.setWindow(-smithCoordMax, -smithCoordMax, 2*smithCoordMax, 2*smithCoordMax);
plotToPixelXOffset = width()/2;
plotToPixelYOffset = height()/2;
plotToPixelXScale = side/2;
plotToPixelYScale = -side/2;
draw(&painter, 2*smithCoordMax/side);
}
bool TraceSmithChart::supported(Trace *t)
{
if(t->isReflection()) {
return true;
} else {
return false;
}
}

View File

@ -0,0 +1,44 @@
#ifndef TRACESMITHCHART_H
#define TRACESMITHCHART_H
#include "traceplot.h"
#include <QPen>
class TraceSmithChart : public TracePlot
{
Q_OBJECT
public:
TraceSmithChart(TraceModel &model, QWidget *parent = 0);
protected:
static constexpr double ReferenceImpedance = 50.0;
static constexpr double screenUsage = 0.9;
static constexpr int smithCoordMax = 4096;
QPoint plotToPixel(std::complex<double> S);
std::complex<double> pixelToPlot(const QPoint &pos);
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
bool supported(Trace *t) override;
void draw(QPainter * painter, double width_factor);
void replot() override;
QPen textPen;
QPen chartLinesPen;
QPen thinPen;
QPen pointDataPen;
QPen lineDataPen;
/// Path for the thin arcs
QPainterPath thinArcsPath;
/// Path for the thick arcs
QPainterPath thickArcsPath;
double plotToPixelXOffset, plotToPixelXScale;
double plotToPixelYOffset, plotToPixelYScale;
TraceMarker *selectedMarker;
};
#endif // TRACESMITHCHART_H

View File

@ -0,0 +1,121 @@
#include "tracewidget.h"
#include "ui_tracewidget.h"
#include "trace.h"
#include <QKeyEvent>
#include "traceeditdialog.h"
#include "traceimportdialog.h"
#include "traceexportdialog.h"
#include <QFileDialog>
TraceWidget::TraceWidget(TraceModel &model, QWidget *parent) :
QWidget(parent),
ui(new Ui::TraceWidget),
model(model)
{
ui->setupUi(this);
ui->view->setModel(&model);
ui->view->setAutoScroll(false);
installEventFilter(this);
createCount = 0;
}
TraceWidget::~TraceWidget()
{
delete ui;
}
void TraceWidget::on_add_clicked()
{
createCount++;
auto t = new Trace("Trace #"+QString::number(createCount));
t->setColor(QColor::fromHsl((createCount * 50) % 360, 250, 128));
model.addTrace(t);
}
void TraceWidget::on_remove_clicked()
{
model.removeTrace(ui->view->currentIndex().row());
}
bool TraceWidget::eventFilter(QObject *, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
int key = static_cast<QKeyEvent *>(event)->key();
if(key == Qt::Key_Escape) {
ui->view->clearSelection();
return true;
} else if(key == Qt::Key_Delete) {
model.removeTrace(ui->view->currentIndex().row());
return true;
}
}
return false;
}
void TraceWidget::on_edit_clicked()
{
if(ui->view->currentIndex().isValid()) {
auto edit = new TraceEditDialog(*model.trace(ui->view->currentIndex().row()));
edit->show();
}
}
void TraceWidget::on_view_doubleClicked(const QModelIndex &index)
{
if(index.column() == 2) {
auto edit = new TraceEditDialog(*model.trace(index.row()));
edit->show();
}
}
void TraceWidget::on_view_clicked(const QModelIndex &index)
{
if(index.column()==0) {
model.toggleVisibility(index.row());
} else if(index.column()==1) {
model.togglePause(index.row());
}
}
void TraceWidget::on_bImport_clicked()
{
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
if (filename.length() > 0) {
auto t = Touchstone::fromFile(filename.toStdString());
std::vector<Trace*> traces;
for(unsigned int i=0;i<t.ports()*t.ports();i++) {
auto trace = new Trace();
trace->fillFromTouchstone(t, i, filename);
unsigned int sink = i / t.ports() + 1;
unsigned int source = i % t.ports() + 1;
trace->setName("S"+QString::number(sink)+QString::number(source));
traces.push_back(trace);
}
// contruct prefix from filename
// remove any directory names (keep only the filename itself)
int lastSlash = qMax(filename.lastIndexOf('/'), filename.lastIndexOf('\\'));
if(lastSlash != -1) {
filename.remove(0, lastSlash + 1);
}
// remove file type
filename.truncate(filename.indexOf('.'));
auto i = new TraceImportDialog(model, traces, filename+"_");
i->show();
}
}
void TraceWidget::on_bExport_clicked()
{
auto e = new TraceExportDialog(model);
// Attempt to set default traces (this will result in correctly populated
// 2 port export if the initial 4 traces have not been modified)
e->setPortNum(2);
auto traces = model.getTraces();
for(unsigned int i=0;i<4;i++) {
if(i >= traces.size()) {
break;
}
e->setTrace(i%2, i/2, traces[i]);
}
e->show();
}

View File

@ -0,0 +1,41 @@
#ifndef TRACEWIDGET_H
#define TRACEWIDGET_H
#include <QWidget>
#include "tracemodel.h"
namespace Ui {
class TraceWidget;
}
class TraceWidget : public QWidget
{
Q_OBJECT
public:
explicit TraceWidget(TraceModel &model, QWidget *parent = nullptr);
~TraceWidget();
public slots:
void on_add_clicked();
private slots:
void on_remove_clicked();
void on_edit_clicked();
void on_view_doubleClicked(const QModelIndex &index);
void on_view_clicked(const QModelIndex &index);
void on_bImport_clicked();
void on_bExport_clicked();
private:
bool eventFilter(QObject *obj, QEvent *event) override;
Ui::TraceWidget *ui;
TraceModel &model;
int createCount;
};
#endif // TRACEWIDGET_H

View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceWidget</class>
<widget class="QWidget" name="TraceWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>206</width>
<height>268</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="view">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>0</number>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>21</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>21</number>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="add">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Add</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-add"/>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Delete</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-remove"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bImport">
<property name="toolTip">
<string>Import</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/import.svg</normaloff>:/icons/import.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bExport">
<property name="toolTip">
<string>Export</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/export.svg</normaloff>:/icons/export.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Edit</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="accessories-text-editor"/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,78 @@
#include "averaging.h"
using namespace std;
Averaging::Averaging()
{
averages = 1;
}
void Averaging::reset()
{
avg.clear();
}
void Averaging::setAverages(unsigned int a)
{
averages = a;
reset();
}
Protocol::Datapoint Averaging::process(Protocol::Datapoint d)
{
auto S11 = complex<double>(d.real_S11, d.imag_S11);
auto S12 = complex<double>(d.real_S12, d.imag_S12);
auto S21 = complex<double>(d.real_S21, d.imag_S21);
auto S22 = complex<double>(d.real_S22, d.imag_S22);
if (d.pointNum == avg.size()) {
// add moving average entry
deque<array<complex<double>, 4>> deque;
avg.push_back(deque);
}
if (d.pointNum < avg.size()) {
// can compute average
// get correct queue
auto deque = &avg[d.pointNum];
// add newest sample to queue
array<complex<double>, 4> sample = {S11, S12, S21, S22};
deque->push_back(sample);
if(deque->size() > averages) {
deque->pop_front();
}
// calculate average
complex<double> sum[4];
for(auto s : *deque) {
sum[0] += s[0];
sum[1] += s[1];
sum[2] += s[2];
sum[3] += s[3];
}
S11 = sum[0] / (double) (deque->size());
S12 = sum[1] / (double) (deque->size());
S21 = sum[2] / (double) (deque->size());
S22 = sum[3] / (double) (deque->size());
}
d.real_S11 = S11.real();
d.imag_S11 = S11.imag();
d.real_S12 = S12.real();
d.imag_S12 = S12.imag();
d.real_S21 = S21.real();
d.imag_S21 = S21.imag();
d.real_S22 = S22.real();
d.imag_S22 = S22.imag();
return d;
}
unsigned int Averaging::getLevel()
{
if(avg.size() > 0) {
return avg.back().size();
} else {
return 0;
}
}

View File

@ -0,0 +1,23 @@
#ifndef AVERAGING_H
#define AVERAGING_H
#include "Device/device.h"
#include <deque>
#include <complex>
class Averaging
{
public:
Averaging();
void reset();
void setAverages(unsigned int a);
Protocol::Datapoint process(Protocol::Datapoint d);
unsigned int getLevel();
private:
std::vector<std::deque<std::array<std::complex<double>, 4>>> avg;
int maxPoints;
unsigned int averages;
};
#endif // AVERAGING_H

View File

@ -0,0 +1,26 @@
<RCC>
<qresource prefix="/icons"/>
<qresource prefix="/">
<file>icons/add.png</file>
<file>icons/delete.png</file>
<file>icons/edit.png</file>
<file>icons/invisible.svg</file>
<file>icons/visible.svg</file>
<file>icons/pause.svg</file>
<file>icons/play.svg</file>
<file>icons/plus.svg</file>
<file>icons/export.svg</file>
<file>icons/import.svg</file>
<file>icons/close.svg</file>
<file>icons/horizontal.svg</file>
<file>icons/vertical.svg</file>
<file>icons/pCsC_small.png</file>
<file>icons/pCsL_small.png</file>
<file>icons/pLsC_small.png</file>
<file>icons/pLsL_small.png</file>
<file>icons/sCpC_small.png</file>
<file>icons/sCpL_small.png</file>
<file>icons/sLpC_small.png</file>
<file>icons/sLpL_small.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,2 @@
tex

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 492 492" style="enable-background:new 0 0 492 492;" xml:space="preserve">
<g>
<g>
<path d="M300.188,246L484.14,62.04c5.06-5.064,7.852-11.82,7.86-19.024c0-7.208-2.792-13.972-7.86-19.028L468.02,7.872
c-5.068-5.076-11.824-7.856-19.036-7.856c-7.2,0-13.956,2.78-19.024,7.856L246.008,191.82L62.048,7.872
c-5.06-5.076-11.82-7.856-19.028-7.856c-7.2,0-13.96,2.78-19.02,7.856L7.872,23.988c-10.496,10.496-10.496,27.568,0,38.052
L191.828,246L7.872,429.952c-5.064,5.072-7.852,11.828-7.852,19.032c0,7.204,2.788,13.96,7.852,19.028l16.124,16.116
c5.06,5.072,11.824,7.856,19.02,7.856c7.208,0,13.968-2.784,19.028-7.856l183.96-183.952l183.952,183.952
c5.068,5.072,11.824,7.856,19.024,7.856h0.008c7.204,0,13.96-2.784,19.028-7.856l16.12-16.116
c5.06-5.064,7.852-11.824,7.852-19.028c0-7.204-2.792-13.96-7.852-19.028L300.188,246z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M352,288.994v127.008H64v-288h96v-64H32c-17.664,0-32,14.336-32,32v352c0,17.696,14.336,32,32,32h352
c17.696,0,32-14.304,32-32V288.994H352z"/>
</g>
</g>
<g>
<g>
<path d="M505.6,131.202l-128-96c-4.8-3.648-11.328-4.224-16.736-1.504c-5.44,2.72-8.864,8.256-8.864,14.304v48h-48
c-97.056,0-176,78.944-176,176c0,7.424,5.12,13.888,12.32,15.584c1.216,0.288,2.464,0.416,3.68,0.416
c5.952,0,11.552-3.328,14.304-8.832l3.776-7.52c24.544-49.12,73.888-79.648,128.8-79.648H352v48
c0,6.048,3.424,11.584,8.832,14.304c5.408,2.72,11.936,2.144,16.768-1.504l128-96c4.032-3.008,6.4-7.776,6.4-12.8
S509.632,134.21,505.6,131.202z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 365.368 365.368" style="enable-background:new 0 0 365.368 365.368;" xml:space="preserve">
<g>
<path style="fill:#231F20;" d="M363.171,177.381L311.1,125.309c-1.406-1.407-3.314-2.197-5.303-2.197
c-1.989,0-3.897,0.79-5.303,2.197l-14.143,14.143c-1.407,1.406-2.197,3.314-2.197,5.303c0,1.989,0.79,3.897,2.197,5.303
l15.126,15.125h-52.543v-95.5c0-4.142-3.358-7.5-7.5-7.5h-20c-4.142,0-7.5,3.358-7.5,7.5v226c0,4.142,3.358,7.5,7.5,7.5h20
c4.142,0,7.5-3.358,7.5-7.5v-95.5h52.543l-15.126,15.126c-1.407,1.406-2.197,3.314-2.197,5.303c0,1.989,0.79,3.897,2.197,5.303
l14.143,14.143c1.406,1.407,3.314,2.197,5.303,2.197c1.989,0,3.897-0.79,5.303-2.197l52.071-52.071
C366.1,185.058,366.1,180.309,363.171,177.381z"/>
<path style="fill:#231F20;" d="M143.934,62.184h-20c-4.142,0-7.5,3.358-7.5,7.5v95.5H63.891l15.126-15.125
c2.929-2.929,2.929-7.677,0-10.606l-14.142-14.143c-1.407-1.406-3.315-2.197-5.304-2.197c-1.989,0-3.897,0.79-5.303,2.197
L2.197,177.381C0.79,178.787,0,180.695,0,182.684c0,1.989,0.79,3.897,2.197,5.303l52.072,52.071
c1.407,1.407,3.314,2.197,5.303,2.197c1.989,0,3.897-0.79,5.304-2.197l14.142-14.143c2.929-2.929,2.928-7.678,0-10.606
l-15.126-15.126h52.543v95.5c0,4.142,3.358,7.5,7.5,7.5h20c4.142,0,7.5-3.358,7.5-7.5v-226
C151.434,65.542,148.076,62.184,143.934,62.184z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M287.52,224.48c-3.36-3.36-8-5.088-12.736-4.64l-124.448,11.296c-6.176,0.576-11.52,4.672-13.6,10.496
c-2.112,5.856-0.672,12.384,3.712,16.768l33.952,33.952L4.704,462.048c-6.24,6.24-6.24,16.384,0,22.624l22.624,22.624
c6.24,6.272,16.352,6.272,22.624,0L219.648,337.6l33.952,33.952c4.384,4.384,10.912,5.824,16.768,3.744
c2.24-0.832,4.224-2.112,5.856-3.744c2.592-2.592,4.288-6.048,4.608-9.888l11.328-124.448
C292.608,232.48,290.88,227.84,287.52,224.48z"/>
</g>
</g>
<g>
<g>
<path d="M480,0H32C14.336,0,0,14.336,0,32v320h64V64h384v384H160v64h320c17.696,0,32-14.304,32-32V32C512,14.336,497.696,0,480,0z
"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 166 132" height="132px" id="Layer_1" version="1.1" viewBox="0 0 166 132" width="166px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><path d="M132.658-0.18l-24.321,24.321c-7.915-2.71-16.342-4.392-25.087-4.392c-45.84,0-83,46-83,46 s14.1,17.44,35.635,30.844L12.32,120.158l12.021,12.021L144.68,11.841L132.658-0.18z M52.033,80.445 c-2.104-4.458-3.283-9.438-3.283-14.695c0-19.054,15.446-34.5,34.5-34.5c5.258,0,10.237,1.179,14.695,3.284L52.033,80.445z" fill="#231F20"/><path d="M134.865,37.656l-18.482,18.482c0.884,3.052,1.367,6.275,1.367,9.612c0,19.055-15.446,34.5-34.5,34.5 c-3.337,0-6.56-0.483-9.611-1.367l-10.124,10.124c6.326,1.725,12.934,2.743,19.735,2.743c45.84,0,83-46,83-46 S153.987,50.575,134.865,37.656z" fill="#231F20"/></g></svg>

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Some files were not shown because too many files have changed in this diff Show More