added librevna firmware reproduce kit
Полное дерево LibreVNA v1.6.5 с доработками master/slave, эталонные битстримы и прошивки STM32, упаковщик .vnafw. Проверено: build.sh пересобирает обе прошивки побайтово идентично лежащим рядом .vnafw. Мастер-битстрим (top_master.bin / extracted/master_fpga.bin) больше нигде не существует — place&route в ISE недетерминирован, из исходников он не восстанавливается. Положено в multi_libreVNA_firmware/reproduce, а не в device_firmware/reproduce как сказано в README комплекта: SHA256SUMS и build.sh ссылаются на ../combined.vnafw, а .vnafw лежат здесь. Корневые правила .gitignore build*/build/ выбрасывали 482 файла, включая сам build.sh и эталонные прошивки STM32 из SHA256SUMS — дописано разыгноривание. Оставшиеся 7 файлов перебивал вложенный апстримный .gitignore (более глубокий имеет приоритет), они добавлены force-add'ом. Промежуточные .o/.d/.su из build-каталогов убраны, .bin/.elf/.hex/.map оставлены. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
/.metadata/
|
||||
RemoteSystemsTempFiles
|
||||
build-*
|
||||
*.pyc
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.settings">
|
||||
|
||||
<cconfiguration id="cdt.managedbuild.config.gnu.exe.debug.2118587971">
|
||||
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.exe.debug.2118587971" moduleId="org.eclipse.cdt.core.settings" name="Debug">
|
||||
|
||||
<externalSettings/>
|
||||
|
||||
<extensions>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GNU_ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
</extensions>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
|
||||
<configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.debug" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.exe.debug.2118587971" name="Debug" optionalBuildProperties="org.eclipse.cdt.docker.launcher.containerbuild.property.selectedvolumes=,org.eclipse.cdt.docker.launcher.containerbuild.property.volumes=" parent="cdt.managedbuild.config.gnu.exe.debug">
|
||||
|
||||
<folderInfo id="cdt.managedbuild.config.gnu.exe.debug.2118587971." name="/" resourcePath="">
|
||||
|
||||
<toolChain id="cdt.managedbuild.toolchain.gnu.exe.debug.1873591644" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.exe.debug">
|
||||
|
||||
<targetPlatform id="cdt.managedbuild.target.gnu.platform.exe.debug.1811214834" name="Debug Platform" superClass="cdt.managedbuild.target.gnu.platform.exe.debug"/>
|
||||
|
||||
<builder buildPath="${workspace_loc:/SignalIDSamplerates}/Debug" id="cdt.managedbuild.target.gnu.builder.exe.debug.1564235544" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.target.gnu.builder.exe.debug"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.archiver.base.1348364703" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug.1167532039" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug">
|
||||
|
||||
<option id="gnu.cpp.compiler.exe.debug.option.optimization.level.1104651228" name="Optimization Level" superClass="gnu.cpp.compiler.exe.debug.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.none" valueType="enumerated"/>
|
||||
|
||||
<option defaultValue="gnu.cpp.compiler.debugging.level.max" id="gnu.cpp.compiler.exe.debug.option.debugging.level.1222841339" name="Debug Level" superClass="gnu.cpp.compiler.exe.debug.option.debugging.level" useByScannerDiscovery="false" valueType="enumerated"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.266560721" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.c.compiler.exe.debug.2042313583" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.exe.debug">
|
||||
|
||||
<option defaultValue="gnu.c.optimization.level.none" id="gnu.c.compiler.exe.debug.option.optimization.level.1307505016" name="Optimization Level" superClass="gnu.c.compiler.exe.debug.option.optimization.level" useByScannerDiscovery="false" valueType="enumerated"/>
|
||||
|
||||
<option defaultValue="gnu.c.debugging.level.max" id="gnu.c.compiler.exe.debug.option.debugging.level.1981297588" name="Debug Level" superClass="gnu.c.compiler.exe.debug.option.debugging.level" useByScannerDiscovery="false" valueType="enumerated"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.381929295" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.c.linker.exe.debug.407773137" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.exe.debug"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cpp.linker.exe.debug.1838910139" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.exe.debug">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.121550916" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
|
||||
|
||||
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
|
||||
|
||||
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
|
||||
|
||||
</inputType>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.assembler.exe.debug.938829554" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.exe.debug">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.1588140857" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
</toolChain>
|
||||
|
||||
</folderInfo>
|
||||
|
||||
<sourceEntries>
|
||||
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="src"/>
|
||||
|
||||
</sourceEntries>
|
||||
|
||||
</configuration>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
|
||||
</cconfiguration>
|
||||
|
||||
<cconfiguration id="cdt.managedbuild.config.gnu.exe.release.2067076035">
|
||||
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.exe.release.2067076035" moduleId="org.eclipse.cdt.core.settings" name="Release">
|
||||
|
||||
<externalSettings/>
|
||||
|
||||
<extensions>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GNU_ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
</extensions>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
|
||||
<configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.exe.release.2067076035" name="Release" optionalBuildProperties="" parent="cdt.managedbuild.config.gnu.exe.release">
|
||||
|
||||
<folderInfo id="cdt.managedbuild.config.gnu.exe.release.2067076035." name="/" resourcePath="">
|
||||
|
||||
<toolChain id="cdt.managedbuild.toolchain.gnu.exe.release.234790444" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.exe.release">
|
||||
|
||||
<targetPlatform id="cdt.managedbuild.target.gnu.platform.exe.release.1733878166" name="Debug Platform" superClass="cdt.managedbuild.target.gnu.platform.exe.release"/>
|
||||
|
||||
<builder buildPath="${workspace_loc:/SignalIDSamplerates}/Release" id="cdt.managedbuild.target.gnu.builder.exe.release.527109837" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.target.gnu.builder.exe.release"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.archiver.base.711380308" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.exe.release.405853418" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.exe.release">
|
||||
|
||||
<option id="gnu.cpp.compiler.exe.release.option.optimization.level.756591664" name="Optimization Level" superClass="gnu.cpp.compiler.exe.release.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.most" valueType="enumerated"/>
|
||||
|
||||
<option defaultValue="gnu.cpp.compiler.debugging.level.none" id="gnu.cpp.compiler.exe.release.option.debugging.level.866507122" name="Debug Level" superClass="gnu.cpp.compiler.exe.release.option.debugging.level" useByScannerDiscovery="false" valueType="enumerated"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.388380894" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.c.compiler.exe.release.1310733818" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.exe.release">
|
||||
|
||||
<option defaultValue="gnu.c.optimization.level.most" id="gnu.c.compiler.exe.release.option.optimization.level.779300110" name="Optimization Level" superClass="gnu.c.compiler.exe.release.option.optimization.level" useByScannerDiscovery="false" valueType="enumerated"/>
|
||||
|
||||
<option defaultValue="gnu.c.debugging.level.none" id="gnu.c.compiler.exe.release.option.debugging.level.1906990630" name="Debug Level" superClass="gnu.c.compiler.exe.release.option.debugging.level" useByScannerDiscovery="false" valueType="enumerated"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1517881410" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.c.linker.exe.release.1210134901" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.exe.release"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cpp.linker.exe.release.278742790" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.exe.release">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.1368940988" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
|
||||
|
||||
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
|
||||
|
||||
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
|
||||
|
||||
</inputType>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.assembler.exe.release.1281186340" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.exe.release">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.902387081" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
</toolChain>
|
||||
|
||||
</folderInfo>
|
||||
|
||||
<sourceEntries>
|
||||
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="src"/>
|
||||
|
||||
</sourceEntries>
|
||||
|
||||
</configuration>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
|
||||
</cconfiguration>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
|
||||
<project id="SignalIDSamplerates.cdt.managedbuild.target.gnu.exe.1278463665" name="Executable" projectType="cdt.managedbuild.target.gnu.exe"/>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="scannerConfiguration">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.release.2067076035;cdt.managedbuild.config.gnu.exe.release.2067076035.;cdt.managedbuild.tool.gnu.c.compiler.exe.release.1310733818;cdt.managedbuild.tool.gnu.c.compiler.input.1517881410">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.debug.2118587971;cdt.managedbuild.config.gnu.exe.debug.2118587971.;cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug.1167532039;cdt.managedbuild.tool.gnu.cpp.compiler.input.266560721">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.debug.2118587971;cdt.managedbuild.config.gnu.exe.debug.2118587971.;cdt.managedbuild.tool.gnu.c.compiler.exe.debug.2042313583;cdt.managedbuild.tool.gnu.c.compiler.input.381929295">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.release.2067076035;cdt.managedbuild.config.gnu.exe.release.2067076035.;cdt.managedbuild.tool.gnu.cpp.compiler.exe.release.405853418;cdt.managedbuild.tool.gnu.cpp.compiler.input.388380894">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
|
||||
|
||||
<storageModule moduleId="refreshScope" versionNumber="2">
|
||||
|
||||
<configuration configurationName="Debug">
|
||||
|
||||
<resource resourceType="PROJECT" workspacePath="/SignalIDSamplerates"/>
|
||||
|
||||
</configuration>
|
||||
|
||||
<configuration configurationName="Release">
|
||||
|
||||
<resource resourceType="PROJECT" workspacePath="/SignalIDSamplerates"/>
|
||||
|
||||
</configuration>
|
||||
|
||||
</storageModule>
|
||||
|
||||
</cproject>
|
||||
+1
@@ -0,0 +1 @@
|
||||
/Debug/
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>SignalIDSamplerates</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project>
|
||||
|
||||
<configuration id="cdt.managedbuild.config.gnu.exe.debug.2118587971" name="Debug">
|
||||
|
||||
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
|
||||
|
||||
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="-580598439797579494" id="org.eclipse.cdt.managedbuilder.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD "${INPUTS}"" prefer-non-shared="true">
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.gcc"/>
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.g++"/>
|
||||
|
||||
</provider>
|
||||
|
||||
</extension>
|
||||
|
||||
</configuration>
|
||||
|
||||
<configuration id="cdt.managedbuild.config.gnu.exe.release.2067076035" name="Release">
|
||||
|
||||
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
|
||||
|
||||
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="-580598439797579494" id="org.eclipse.cdt.managedbuilder.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD "${INPUTS}"" prefer-non-shared="true">
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.gcc"/>
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.g++"/>
|
||||
|
||||
</provider>
|
||||
|
||||
</extension>
|
||||
|
||||
</configuration>
|
||||
|
||||
</project>
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
//============================================================================
|
||||
// Name : SignalIDSamplerates.cpp
|
||||
// Author :
|
||||
// Version :
|
||||
// Copyright : Your copyright notice
|
||||
// Description : Hello World in C++, Ansi-style
|
||||
//============================================================================
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
using namespace std;
|
||||
|
||||
constexpr int32_t freqRange = 20000000;
|
||||
constexpr int32_t freqStep = 4000;
|
||||
constexpr int32_t FPGA_CLK = 102400000;
|
||||
constexpr int32_t IF_freq = 250000;
|
||||
constexpr int32_t minAliasDistance = 100000;
|
||||
|
||||
constexpr uint8_t minPrescaler = 112;
|
||||
constexpr uint8_t maxPrescaler = 180;
|
||||
constexpr uint8_t maxPrescalers = 5;
|
||||
|
||||
int bestDistance;
|
||||
vector<int> bestPrescalers;
|
||||
|
||||
int Alias(int f, int f_s) {
|
||||
// move into f_s range
|
||||
f %= f_s;
|
||||
if (f < 0) {
|
||||
f += f_s;
|
||||
}
|
||||
// fold at half the samplerate
|
||||
if(f >= f_s / 2) {
|
||||
f = f_s - f;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
int DistanceToIF(double f, double f_s) {
|
||||
auto alias = Alias(f, f_s);
|
||||
return abs(int(alias - IF_freq));
|
||||
}
|
||||
|
||||
int farthestDistanceForFrequency(double f, vector<int> &prescalers) {
|
||||
int highestDistance = numeric_limits<int>::min();
|
||||
|
||||
for(auto p : prescalers) {
|
||||
double f_s = (double) FPGA_CLK / p;
|
||||
auto dist = DistanceToIF(f, f_s);
|
||||
if(dist > highestDistance) {
|
||||
highestDistance = dist;
|
||||
}
|
||||
}
|
||||
return highestDistance;
|
||||
}
|
||||
|
||||
void checkPrescalers(vector<int> &prescalers) {
|
||||
// cout << "Checking [" << prescalers[0];
|
||||
// for (auto i = prescalers.begin() + 1; i != prescalers.end(); i++) {
|
||||
// cout << ',' << *i;
|
||||
// }
|
||||
// cout << "]... ";
|
||||
int lowestDistance = numeric_limits<int>::max();
|
||||
for(int freq = minAliasDistance + freqStep;freq < freqRange; freq += freqStep) {
|
||||
int dist = farthestDistanceForFrequency(IF_freq + freq, prescalers);
|
||||
if(dist < lowestDistance) {
|
||||
lowestDistance = dist;
|
||||
}
|
||||
if(freq > 2*IF_freq + minAliasDistance) {
|
||||
int dist = farthestDistanceForFrequency(IF_freq - freq, prescalers);
|
||||
if(dist < lowestDistance) {
|
||||
lowestDistance = dist;
|
||||
}
|
||||
}
|
||||
if(lowestDistance < bestDistance) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// cout << lowestDistance << endl;
|
||||
if(lowestDistance > bestDistance) {
|
||||
bestDistance = lowestDistance;
|
||||
bestPrescalers = prescalers;
|
||||
}
|
||||
}
|
||||
|
||||
void recursivePrescalersTest(vector<int> &prescalers, int depth) {
|
||||
if(prescalers.size() >= depth) {
|
||||
checkPrescalers(prescalers);
|
||||
} else {
|
||||
prescalers.push_back(0);
|
||||
int start = minPrescaler;
|
||||
if(prescalers.size() > 1) {
|
||||
start = prescalers[prescalers.size() - 2] + 1;
|
||||
}
|
||||
for (int i = start; i <= maxPrescaler; i++) {
|
||||
prescalers.back() = i;
|
||||
recursivePrescalersTest(prescalers, depth);
|
||||
}
|
||||
prescalers.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
vector<int> prescalers = {112};
|
||||
for(int d = 2;d<=maxPrescalers;d++) {
|
||||
bestDistance = 0;
|
||||
recursivePrescalersTest(prescalers, d);
|
||||
cout << "Depth " << d <<": best distance: " << bestDistance << " Prescalers: [" << bestPrescalers[0];
|
||||
for (auto i = bestPrescalers.begin() + 1; i != bestPrescalers.end(); i++) {
|
||||
cout << ',' << *i;
|
||||
}
|
||||
cout << "]" << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"Description": "Dummy ideal calibration kit",
|
||||
"Manufacturer": "LibreVNA",
|
||||
"Serialnumber": "1234",
|
||||
"standards": [
|
||||
{
|
||||
"params": {
|
||||
"C0": 0.0,
|
||||
"C1": 0.0,
|
||||
"C2": 0.0,
|
||||
"C3": 0.0,
|
||||
"Z0": 50.0,
|
||||
"delay": 0.0,
|
||||
"id": 13726173464103258082,
|
||||
"loss": 0.0,
|
||||
"name": "Default"
|
||||
},
|
||||
"type": "Open"
|
||||
},
|
||||
{
|
||||
"params": {
|
||||
"L0": 0.0,
|
||||
"L1": 0.0,
|
||||
"L2": 0.0,
|
||||
"L3": 0.0,
|
||||
"Z0": 50.0,
|
||||
"delay": 0.0,
|
||||
"id": 13573063103112146609,
|
||||
"loss": 0.0,
|
||||
"name": "Default"
|
||||
},
|
||||
"type": "Short"
|
||||
},
|
||||
{
|
||||
"params": {
|
||||
"Cfirst": true,
|
||||
"Cparallel": 0.0,
|
||||
"Lseries": 0.0,
|
||||
"Z0": 50.0,
|
||||
"delay": 0.0,
|
||||
"id": 11987083411312060381,
|
||||
"loss": 0.0,
|
||||
"name": "Default",
|
||||
"resistance": 50.0
|
||||
},
|
||||
"type": "Load"
|
||||
},
|
||||
{
|
||||
"params": {
|
||||
"Z0": 50.0,
|
||||
"delay": 0.0,
|
||||
"id": 2916288069679020801,
|
||||
"loss": 0.0,
|
||||
"name": "Default"
|
||||
},
|
||||
"type": "Through"
|
||||
}
|
||||
],
|
||||
"version": "1.5.0-alpha.1-9b7f457aa"
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import unittest
|
||||
import glob, os
|
||||
|
||||
testmodules = [
|
||||
'tests.TestUpdate', # Must go first because it updates the connected VNA to the firwmare which should be tested
|
||||
'tests.TestConnect',
|
||||
'tests.TestStatusRegisters',
|
||||
'tests.TestMode',
|
||||
'tests.TestTemperature',
|
||||
'tests.TestSync',
|
||||
'tests.TestVNASweep',
|
||||
'tests.TestCalibration',
|
||||
'tests.TestGenerator',
|
||||
'tests.TestSASweep',
|
||||
'tests.TestRST',
|
||||
]
|
||||
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
# Clean up potential error logs from previous test runs
|
||||
for f in glob.glob("errorlog_*"):
|
||||
os.remove(f)
|
||||
for f in glob.glob("packetlog_*"):
|
||||
os.remove(f)
|
||||
|
||||
for t in testmodules:
|
||||
try:
|
||||
# If the module defines a suite() function, call it to get the suite.
|
||||
mod = __import__(t, globals(), locals(), ['suite'])
|
||||
suitefn = getattr(mod, 'suite')
|
||||
suite.addTest(suitefn())
|
||||
except (ImportError, AttributeError):
|
||||
# else, just load all the test cases from the module.
|
||||
suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))
|
||||
|
||||
result = unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
exit(int(not result.wasSuccessful()))
|
||||
|
||||
+117652
File diff suppressed because it is too large
Load Diff
+22
@@ -0,0 +1,22 @@
|
||||
0.027: [debug] Application start
|
||||
0.045: [info] Listening on port 19544
|
||||
0.060: [debug] Activating mode "Vector Network Analyzer"
|
||||
0.068: [debug] Updated device list, found 1
|
||||
0.068: [debug] Trying to connect to "208A367A4B42"
|
||||
0.068: [debug] Attempting to connect to device...
|
||||
0.075: [info] USB connection established
|
||||
0.075: [debug] Receive thread started
|
||||
0.075: [debug] Transmission started (packet type 15 ), queue at 1
|
||||
0.078: [info] Connected to "208A367A4B42"
|
||||
0.079: [debug] Transmission started (packet type 26 ), queue at 1
|
||||
0.082: [debug] No default calibration file set for this device
|
||||
0.082: [debug] Transmission started (packet type 11 ), queue at 1
|
||||
0.187: [debug] Transmission started (packet type 2 ), queue at 1
|
||||
0.533: [debug] Attempting to open calibration from file "LIBRECAL.cal"
|
||||
0.533: [debug] Associated calibration kit expected in "LIBRECAL.calkit"
|
||||
0.533: [debug] Opening calkit to file "LIBRECAL.calkit"
|
||||
0.533: [debug] Parsing of calibration kit failed while opening calibration file: Unable to open file (ignore for calibration format >= 3)
|
||||
0.899: [debug] Transmission started (packet type 2 ), queue at 1
|
||||
1.007: [debug] Deactivated mode "Vector Network Analyzer"
|
||||
1.007: [debug] Transmission started (packet type 20 ), queue at 1
|
||||
1.050: [debug] Disconnected, receive thread exiting
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
from enum import Enum
|
||||
|
||||
class libreCAL:
|
||||
def __init__(self, serialnum = ''):
|
||||
self.ser = None
|
||||
for p in serial.tools.list_ports.comports():
|
||||
if (p.vid == 0x0483 and p.pid == 0x4122) or (p.vid == 0x1209 and p.pid == 0x4122):
|
||||
self.ser = serial.Serial(p.device, timeout = 1)
|
||||
idn = self.SCPICommand("*IDN?").split(",")
|
||||
if idn[0] != "LibreCAL":
|
||||
self.ser = None
|
||||
continue
|
||||
self.serial = idn[2]
|
||||
if len(serialnum) > 0:
|
||||
# serial number specified, compare
|
||||
if self.serial != serialnum:
|
||||
self.ser = None
|
||||
continue
|
||||
break
|
||||
if self.ser == None:
|
||||
if len(serialnum) > 0:
|
||||
raise Exception("LibreCAL with serial number '"+serialnum+"' not detected")
|
||||
else:
|
||||
raise Exception("No LibreCAL device detected")
|
||||
|
||||
def getSerial(self) -> str:
|
||||
return self.serial
|
||||
|
||||
class Standard(Enum):
|
||||
NONE = 0,
|
||||
OPEN = 1,
|
||||
SHORT = 2,
|
||||
LOAD = 3,
|
||||
THROUGH = 4
|
||||
|
||||
def reset(self):
|
||||
self.SCPICommand(":PORT 1 NONE")
|
||||
self.SCPICommand(":PORT 2 NONE")
|
||||
self.SCPICommand(":PORT 3 NONE")
|
||||
self.SCPICommand(":PORT 4 NONE")
|
||||
|
||||
def setPort(self, s : Standard, port, port2 = -1):
|
||||
if s == self.Standard.THROUGH and port2 == -1:
|
||||
raise Exception("When setting a port to THROUGH, the destination port must also be specified")
|
||||
cmd = ":PORT "+str(port)+" "+s.name
|
||||
if s == self.Standard.THROUGH:
|
||||
cmd += " "+str(port2)
|
||||
if len(self.SCPICommand(cmd)) == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def getPort(self, port):
|
||||
resp = self.SCPICommand(":PORT? "+str(port)).split(" ")[0]
|
||||
try:
|
||||
s = self.Standard[resp]
|
||||
return s
|
||||
except:
|
||||
raise Exception("LibreCAL reported unknown standard '"+resp+"'")
|
||||
|
||||
def getTemperature(self):
|
||||
return float(self.SCPICommand(":TEMP?"))
|
||||
|
||||
def isStable(self) -> bool:
|
||||
if self.SCPICommand(":TEMP:STABLE?") == "TRUE":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def getHeaterPower(self):
|
||||
return float(self.SCPICommand(":HEAT:POW?"))
|
||||
|
||||
def getDateTimeUTC(self):
|
||||
return self.SCPICommand(":DATE_TIME?")
|
||||
|
||||
def setDateTimeUTC(self, date_time_utc):
|
||||
return self.SCPICommand(":DATE_TIME "+ date_time_utc)
|
||||
|
||||
def SCPICommand(self, cmd: str) -> str:
|
||||
self.ser.write((cmd+"\r\n").encode())
|
||||
resp = self.ser.readline().decode("ascii")
|
||||
if len(resp) == 0:
|
||||
raise Exception("Timeout occurred in communication with LibreCAL")
|
||||
if resp.strip() == "ERROR":
|
||||
raise Exception("LibreCAL returned 'ERROR' for command '"+cmd+"'")
|
||||
return resp.strip()
|
||||
+1
File diff suppressed because one or more lines are too long
+75
@@ -0,0 +1,75 @@
|
||||
import unittest
|
||||
from tests.libreVNA import libreVNA as libreVNA
|
||||
import tests.definitions as defs
|
||||
import subprocess
|
||||
import time
|
||||
import select
|
||||
import os
|
||||
from signal import SIGINT
|
||||
|
||||
class TestBase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
f = open("log.txt", "w")
|
||||
self.gui = subprocess.Popen([defs.GUI_PATH, '-p', '19544', '--reset-preferences', '--no-gui', '-platform', 'offscreen'], stdout=f, stderr=subprocess.STDOUT)
|
||||
|
||||
# wait for the SCPI server to become available
|
||||
timeout = time.time() + 3;
|
||||
fread = open("log.txt", "r")
|
||||
while True:
|
||||
text = fread.read()
|
||||
if text and "Listening on port 19544" in text:
|
||||
break
|
||||
if time.time() >= timeout:
|
||||
self.tearDown()
|
||||
raise AssertionError("Timed out waiting for SCPI server")
|
||||
|
||||
self.vna = libreVNA('localhost', 19544, timeout=4)
|
||||
try:
|
||||
self.vna.cmd("*CLS")
|
||||
except Exception as e:
|
||||
self.tearDown()
|
||||
raise e
|
||||
if self.vna.query(":DEV:CONN?") == "Not connected":
|
||||
self.tearDown()
|
||||
raise AssertionError("Not connected")
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
if hasattr(self._outcome, 'errors'):
|
||||
# Python 3.4 - 3.10 (These two methods have no side effects)
|
||||
result = self.defaultTestResult()
|
||||
self._feedErrorsToResult(result, self._outcome.errors)
|
||||
else:
|
||||
# Python 3.11+
|
||||
result = self._outcome.result
|
||||
ok = all(test != self for test, text in result.errors + result.failures)
|
||||
|
||||
if not ok:
|
||||
try:
|
||||
# Try to grab the packet log and save it
|
||||
packetlog = self.vna.query(":DEV:PACKETLOG?", timeout=5)
|
||||
f = open("packetlog_"+time.strftime("%Y%m%d%H%M%S.vnalog"), "w")
|
||||
f.write(packetlog)
|
||||
f.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
# make sure the GUI did not crash during the test
|
||||
crashed = self.gui.poll() is not None
|
||||
self.gui.send_signal(SIGINT)
|
||||
try:
|
||||
self.gui.wait(timeout = 3)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.gui.kill()
|
||||
|
||||
|
||||
if ok and not crashed:
|
||||
# remove log file
|
||||
os.remove("log.txt")
|
||||
else:
|
||||
os.rename("log.txt", "errorlog_"+time.strftime("%Y%m%d%H%M%S.txt"))
|
||||
|
||||
if(crashed):
|
||||
raise Exception("GUI crashed")
|
||||
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
from tests.TestBase import TestBase
|
||||
from libreCAL import libreCAL
|
||||
import time
|
||||
import math
|
||||
|
||||
class TestCalibration(TestBase):
|
||||
def cal_measure(self, number, timeout = 3):
|
||||
self.vna.cmd(":VNA:CAL:MEAS "+str(number))
|
||||
# wait for the measurement to finish
|
||||
assert self.vna.query(":VNA:CAL:BUSY?") == "TRUE"
|
||||
self.vna.cmd("*WAI", timeout=timeout)
|
||||
assert self.vna.query(":VNA:CAL:BUSY?") == "FALSE"
|
||||
|
||||
def test_dummy_calibration(self):
|
||||
# This test just iterates through the calibration steps. As no actual standards
|
||||
# are applied to the ports, the calibration result is just random data
|
||||
# Set up the sweep first
|
||||
self.vna.cmd(":DEV:MODE VNA")
|
||||
self.vna.cmd(":VNA:SWEEP FREQUENCY")
|
||||
self.vna.cmd(":VNA:STIM:LVL -10")
|
||||
self.vna.cmd(":VNA:ACQ:IFBW 50000")
|
||||
self.vna.cmd(":VNA:ACQ:AVG 1")
|
||||
self.vna.cmd(":VNA:ACQ:POINTS 501")
|
||||
self.vna.cmd(":VNA:FREQuency:START 1000000")
|
||||
self.vna.cmd(":VNA:FREQuency:STOP 6000000000")
|
||||
|
||||
self.vna.cmd(":VNA:CAL:RESET")
|
||||
|
||||
# No measurements yet, activating should fail
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_1", check=False)
|
||||
self.assertTrue(self.vna.get_status() & 0x3C)
|
||||
|
||||
# Load calibration kit
|
||||
self.assertEqual(self.vna.query(":VNA:CAL:KIT:LOAD? DUMMY.CALKIT"), "TRUE")
|
||||
|
||||
# Lets take the measurements for port 1 first
|
||||
self.vna.cmd(":VNA:CAL:ADD OPEN")
|
||||
self.vna.cmd(":VNA:CAL:ADD SHORT")
|
||||
self.vna.cmd(":VNA:CAL:ADD LOAD")
|
||||
self.vna.cmd(":VNA:CAL:PORT 0 1")
|
||||
self.vna.cmd(":VNA:CAL:PORT 1 1")
|
||||
self.vna.cmd(":VNA:CAL:PORT 2 1")
|
||||
|
||||
self.cal_measure(0)
|
||||
self.cal_measure(1)
|
||||
self.cal_measure(2)
|
||||
|
||||
# SOLT_1 should now be available
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_1", check=False)
|
||||
self.assertFalse(self.vna.get_status() & 0x3C)
|
||||
|
||||
# SOLT_2 and SOLT_12 should still be unavailable
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_2", check=False)
|
||||
self.assertTrue(self.vna.get_status() & 0x3C)
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_12", check=False)
|
||||
self.assertTrue(self.vna.get_status() & 0x3C)
|
||||
|
||||
# Take measurements for SOLT_2
|
||||
self.vna.cmd(":VNA:CAL:ADD OPEN")
|
||||
self.vna.cmd(":VNA:CAL:ADD SHORT")
|
||||
self.vna.cmd(":VNA:CAL:ADD LOAD")
|
||||
self.vna.cmd(":VNA:CAL:PORT 3 2")
|
||||
self.vna.cmd(":VNA:CAL:PORT 4 2")
|
||||
self.vna.cmd(":VNA:CAL:PORT 5 2")
|
||||
|
||||
self.cal_measure(3)
|
||||
self.cal_measure(4)
|
||||
self.cal_measure(5)
|
||||
|
||||
# SOLT_1 and SOLT_2 should now be available
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_1", check=False)
|
||||
self.assertFalse(self.vna.get_status() & 0x3C)
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_2", check=False)
|
||||
self.assertFalse(self.vna.get_status() & 0x3C)
|
||||
|
||||
# SOLT_12 should still be unavailable
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_12", check=False)
|
||||
self.assertTrue(self.vna.get_status() & 0x3C)
|
||||
|
||||
# Take the final through measurement for SOLT_12
|
||||
self.vna.cmd(":VNA:CAL:ADD THROUGH")
|
||||
self.vna.cmd(":VNA:CAL:PORT 6 1 2")
|
||||
|
||||
self.cal_measure(6)
|
||||
|
||||
# SOLT_1, SOLT_2 and SOLT_12 should now be available
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_1", check=False)
|
||||
self.assertFalse(self.vna.get_status() & 0x3C)
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_2", check=False)
|
||||
self.assertFalse(self.vna.get_status() & 0x3C)
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_12", check=False)
|
||||
self.assertFalse(self.vna.get_status() & 0x3C)
|
||||
|
||||
def assertTrace_dB(self, trace, dB_nominal, dB_deviation):
|
||||
for S in trace:
|
||||
dB = 20*math.log10(abs(S[1]))
|
||||
self.assertLessEqual(dB, dB_nominal + dB_deviation)
|
||||
self.assertGreaterEqual(dB, dB_nominal - dB_deviation)
|
||||
|
||||
def test_SOLT_calibration(self):
|
||||
# This test performs a 2-port SOLT calibration with a connected LibreCAL
|
||||
# Afterwqrds, the calibration is checked with a 6dB attenuator
|
||||
# Set up the sweep first
|
||||
self.vna.cmd(":DEV:MODE VNA")
|
||||
self.vna.cmd(":VNA:SWEEP FREQUENCY")
|
||||
self.vna.cmd(":VNA:STIM:LVL -10")
|
||||
self.vna.cmd(":VNA:ACQ:IFBW 1000")
|
||||
self.vna.cmd(":VNA:ACQ:AVG 1")
|
||||
self.vna.cmd(":VNA:ACQ:POINTS 501")
|
||||
self.vna.cmd(":VNA:FREQuency:START 50000000")
|
||||
self.vna.cmd(":VNA:FREQuency:STOP 6000000000")
|
||||
|
||||
self.vna.cmd(":VNA:CAL:RESET")
|
||||
|
||||
# Load calibration file (only for standard and measurement setup, no
|
||||
# measurements are included)
|
||||
self.assertEqual(self.vna.query(":VNA:CAL:LOAD? LIBRECAL.CAL"), "TRUE")
|
||||
|
||||
# Take the measurements
|
||||
|
||||
cal = libreCAL()
|
||||
# Connections:
|
||||
# LibreVNA -> LibreCAL
|
||||
# 1 -> 3
|
||||
# 2 -> 1
|
||||
cal.reset()
|
||||
cal.setPort(cal.Standard.OPEN, 1)
|
||||
cal.setPort(cal.Standard.OPEN, 3)
|
||||
self.cal_measure(0, 15)
|
||||
self.cal_measure(3, 15)
|
||||
|
||||
cal.setPort(cal.Standard.SHORT, 1)
|
||||
cal.setPort(cal.Standard.SHORT, 3)
|
||||
self.cal_measure(1, 15)
|
||||
self.cal_measure(4, 15)
|
||||
|
||||
cal.setPort(cal.Standard.LOAD, 1)
|
||||
cal.setPort(cal.Standard.LOAD, 3)
|
||||
self.cal_measure(2, 15)
|
||||
self.cal_measure(5, 15)
|
||||
|
||||
cal.setPort(cal.Standard.THROUGH, 1, 3)
|
||||
self.cal_measure(6, 15)
|
||||
|
||||
# activate calibration
|
||||
self.vna.cmd(":VNA:CAL:ACT SOLT_12", check=False)
|
||||
self.assertFalse(self.vna.get_status() & 0x3C)
|
||||
|
||||
# switch in 6dB attenuator
|
||||
cal.setPort(cal.Standard.THROUGH, 1, 2)
|
||||
cal.setPort(cal.Standard.THROUGH, 3, 4)
|
||||
|
||||
# Start measurement and grab data
|
||||
self.vna.cmd(":VNA:ACQ:SINGLE TRUE")
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "FALSE")
|
||||
self.vna.cmd("*WAI", timeout=3)
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "TRUE")
|
||||
|
||||
cal.reset()
|
||||
|
||||
# grab trace data
|
||||
S11 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
|
||||
S12 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S12"))
|
||||
S21 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S21"))
|
||||
S22 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S22"))
|
||||
|
||||
# Attenuation is frequency dependent, use excessively large limits
|
||||
# TODO: use smaller limits based on frequency
|
||||
self.assertTrace_dB(S12, -13, 5)
|
||||
self.assertTrace_dB(S21, -13, 5)
|
||||
|
||||
# Reflection should be below -10dB (much lower for most frequencies)
|
||||
self.assertTrace_dB(S11, -100, 91)
|
||||
self.assertTrace_dB(S22, -100, 91)
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
from tests.TestBase import TestBase
|
||||
|
||||
class TestConnect(TestBase):
|
||||
def test_connection(self):
|
||||
self.vna.cmd(":DEV:DISC")
|
||||
self.assertEqual(self.vna.query(":DEV:CONN?"), "Not connected")
|
||||
self.vna.cmd(":DEV:CONN")
|
||||
self.assertNotEqual(self.vna.query(":DEV:CONN?"), "Not connected")
|
||||
|
||||
def test_list(self):
|
||||
list = self.vna.query(":DEV:LIST?")
|
||||
serials = list.split(',')
|
||||
self.assertEqual(len(serials), 1)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
from tests.TestBase import TestBase
|
||||
|
||||
class TestGenerator(TestBase):
|
||||
def test_Generator(self):
|
||||
self.vna.cmd(":DEV:MODE GEN")
|
||||
self.assertEqual(self.vna.query(":DEV:MODE?"), "GEN")
|
||||
|
||||
self.vna.cmd(":GEN:FREQ 2000000000")
|
||||
self.assertEqual(float(self.vna.query(":GEN:FREQ?")), 2000000000)
|
||||
|
||||
self.vna.cmd(":GEN:LVL -12.34")
|
||||
self.assertEqual(float(self.vna.query(":GEN:LVL?")), -12.34)
|
||||
|
||||
self.assertEqual(self.vna.query(":GEN:PORT?"), "0")
|
||||
self.vna.cmd(":GEN:PORT 2")
|
||||
self.assertEqual(self.vna.query(":GEN:PORT?"), "2")
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
from tests.TestBase import TestBase
|
||||
|
||||
class TestMode(TestBase):
|
||||
def test_Mode(self):
|
||||
modes = ["VNA", "GEN", "SA"]
|
||||
for m in modes:
|
||||
self.vna.cmd(":DEV:MODE "+m)
|
||||
self.assertEqual(self.vna.query(":DEV:MODE?"), m)
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
import re
|
||||
from tests.TestBase import TestBase
|
||||
import time
|
||||
|
||||
float_re = re.compile(r'^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]\d+)?$')
|
||||
int_re = re.compile(r'^\d+$')
|
||||
lowerq_re = re.compile('[a-z?]')
|
||||
|
||||
queries = [
|
||||
# Limits used to validate other parameters
|
||||
("float", "DEVice:INFo:LIMits:MINFrequency?"),
|
||||
("float", "DEVice:INFo:LIMits:MAXFrequency?"),
|
||||
("float", "DEVice:INFo:LIMits:MINIFBW?"),
|
||||
("float", "DEVice:INFo:LIMits:MAXIFBW?"),
|
||||
("int", "DEVice:INFo:LIMits:MAXPoints?"),
|
||||
("float", "DEVice:INFo:LIMits:MINPOWer?"),
|
||||
("float", "DEVice:INFo:LIMits:MAXPOWer?"),
|
||||
("float", "DEVice:INFo:LIMits:MINRBW?"),
|
||||
("float", "DEVice:INFo:LIMits:MAXRBW?"),
|
||||
|
||||
# Settable parameters without query arguments
|
||||
("str", "DEVice:MODE?"),
|
||||
("str", "DEVice:REFerence:IN?"),
|
||||
("str", "DEVice:REFerence:OUT?"),
|
||||
("float", "GENerator:FREQuency?"),
|
||||
("float", "GENerator:LVL?"),
|
||||
("int", "GENerator:PORT?"),
|
||||
("int", "SA:ACQuisition:AVG?"),
|
||||
("str", "SA:ACQuisition:DETector?"),
|
||||
("float", "SA:ACQuisition:RBW?"),
|
||||
("bool", "SA:ACQuisition:RUN?"),
|
||||
("bool", "SA:ACQuisition:SINGLE?"),
|
||||
("str", "SA:ACQuisition:WINDow?"),
|
||||
("float", "SA:FREQuency:CENTer?"),
|
||||
("float", "SA:FREQuency:SPAN?"),
|
||||
("float", "SA:FREQuency:START?"),
|
||||
("float", "SA:FREQuency:STOP?"),
|
||||
("bool", "SA:TRACKing:ENable?"),
|
||||
("float", "SA:TRACKing:LVL?"),
|
||||
("bool", "SA:TRACKing:NORMalize:ENable?"),
|
||||
("float", "SA:TRACKing:NORMalize:LVL?"),
|
||||
("float", "SA:TRACKing:OFFset?"),
|
||||
("int", "SA:TRACKing:Port?"),
|
||||
("int", "VNA:ACQuisition:AVG?"),
|
||||
("float", "VNA:ACQuisition:IFBW?"),
|
||||
("int", "VNA:ACQuisition:POINTS?"),
|
||||
("bool", "VNA:ACQuisition:RUN?"),
|
||||
("bool", "VNA:ACQuisition:SINGLE?"),
|
||||
("str", "VNA:CALibration:ACTivate?"),
|
||||
("int", "VNA:DEEMBedding:NUMber?"),
|
||||
("float", "VNA:FREQuency:CENTer?"),
|
||||
("float", "VNA:FREQuency:SPAN?"),
|
||||
("float", "VNA:FREQuency:START?"),
|
||||
("float", "VNA:FREQuency:STOP?"),
|
||||
("float", "VNA:POWer:START?"),
|
||||
("float", "VNA:POWer:STOP?"),
|
||||
("float", "VNA:STIMulus:FREQuency?"),
|
||||
("float", "VNA:STIMulus:LVL?"),
|
||||
("str", "VNA:SWEEP?"),
|
||||
]
|
||||
|
||||
|
||||
class TestRST(TestBase):
|
||||
def query_settings(self) -> dict:
|
||||
result = dict()
|
||||
for qtype, query in queries:
|
||||
resp = self.vna.query(query)
|
||||
if qtype == "float":
|
||||
self.assertTrue(float_re.match(resp),
|
||||
f"Expected float from {query}; got: '{resp}'")
|
||||
value = float(resp)
|
||||
elif qtype == "int":
|
||||
self.assertTrue(int_re.match(resp),
|
||||
f"Expected int from {query}; got: '{resp}'")
|
||||
value = int(resp)
|
||||
elif qtype == "bool":
|
||||
self.assertTrue(resp == "TRUE" or resp == "FALSE",
|
||||
f"Expected bool from {query}; got: '{resp}'")
|
||||
value = True if resp == "TRUE" else False
|
||||
elif qtype == "str":
|
||||
value = resp
|
||||
else:
|
||||
assert False, "invalid type in table"
|
||||
|
||||
query = re.sub(lowerq_re, r'', query)
|
||||
result[query] = value
|
||||
|
||||
return result
|
||||
|
||||
def validate_settings(self, settings):
|
||||
# Copy limits into local vars
|
||||
f_min = settings["DEV:INF:LIM:MINF"]
|
||||
f_max = settings["DEV:INF:LIM:MAXF"]
|
||||
ifbw_min = settings["DEV:INF:LIM:MINIFBW"]
|
||||
ifbw_max = settings["DEV:INF:LIM:MAXIFBW"]
|
||||
points_max = settings["DEV:INF:LIM:MAXP"]
|
||||
pwr_min = settings["DEV:INF:LIM:MINPOW"]
|
||||
pwr_max = settings["DEV:INF:LIM:MAXPOW"]
|
||||
rbw_min = settings["DEV:INF:LIM:MINRBW"]
|
||||
rbw_max = settings["DEV:INF:LIM:MAXRBW"]
|
||||
|
||||
# Validate select settings
|
||||
self.assertEqual(settings["DEV:MODE"], "VNA")
|
||||
self.assertEqual(settings["DEV:REF:IN"], "INT")
|
||||
self.assertEqual(settings["DEV:REF:OUT"], "OFF") # can't source pwr
|
||||
|
||||
f = settings["GEN:FREQ"]
|
||||
self.assertGreaterEqual(f, f_min)
|
||||
self.assertLessEqual(f, f_max)
|
||||
|
||||
pwr = settings["GEN:LVL"]
|
||||
self.assertGreaterEqual(pwr, pwr_min)
|
||||
self.assertLessEqual(pwr, pwr_max)
|
||||
|
||||
self.assertEqual(settings["SA:ACQ:AVG"], 1)
|
||||
|
||||
rbw = settings["SA:ACQ:RBW"]
|
||||
self.assertGreaterEqual(rbw, rbw_min)
|
||||
self.assertLessEqual(rbw, rbw_max)
|
||||
|
||||
f_center = settings["SA:FREQ:CENT"]
|
||||
f_span = settings["SA:FREQ:SPAN"]
|
||||
f_start = settings["SA:FREQ:START"]
|
||||
f_stop = settings["SA:FREQ:STOP"]
|
||||
self.assertGreaterEqual(f_start, f_min)
|
||||
self.assertLessEqual(f_start, f_stop)
|
||||
self.assertLessEqual(f_stop, f_max)
|
||||
f_granularity = (f_max - f_min) / points_max
|
||||
self.assertTrue(abs(f_stop - f_start - f_span) < f_granularity)
|
||||
self.assertTrue(abs((f_start + f_stop) / 2 - f_center) < f_granularity)
|
||||
|
||||
self.assertFalse(settings["SA:TRACK:EN"])
|
||||
pwr = settings["SA:TRACK:LVL"]
|
||||
self.assertGreaterEqual(pwr, pwr_min)
|
||||
self.assertLessEqual(pwr, pwr_max)
|
||||
|
||||
pwr = settings["SA:TRACK:NORM:LVL"]
|
||||
self.assertGreaterEqual(pwr, pwr_min)
|
||||
self.assertLessEqual(pwr, pwr_max)
|
||||
|
||||
self.assertGreaterEqual(settings["SA:TRACK:P"], 1)
|
||||
|
||||
ifbw = settings["VNA:ACQ:IFBW"]
|
||||
self.assertGreaterEqual(ifbw, ifbw_min)
|
||||
self.assertLessEqual(ifbw, ifbw_max)
|
||||
|
||||
points = settings["VNA:ACQ:POINTS"]
|
||||
self.assertGreaterEqual(points, 1)
|
||||
self.assertLessEqual(points, points_max)
|
||||
|
||||
# TODO-check: In standard SCPI, the instrument does not source
|
||||
# power from its ports after *RST. Automation program enables
|
||||
# the output only after completing its setup.
|
||||
#self.assertFalse(settings["VNA:ACQ:RUN"]) # can't source pwr
|
||||
self.assertEqual(settings["VNA:DEEMB:NUM"], 0)
|
||||
|
||||
f_center = settings["VNA:FREQ:CENT"]
|
||||
f_span = settings["VNA:FREQ:SPAN"]
|
||||
f_start = settings["VNA:FREQ:START"]
|
||||
f_stop = settings["VNA:FREQ:STOP"]
|
||||
self.assertGreaterEqual(f_start, f_min)
|
||||
self.assertLessEqual(f_start, f_stop)
|
||||
self.assertLessEqual(f_stop, f_max)
|
||||
self.assertTrue(abs(f_stop - f_start - f_span) < f_granularity)
|
||||
self.assertTrue(abs((f_start + f_stop) / 2 - f_center) < f_granularity)
|
||||
|
||||
pwr_start = settings["VNA:POW:START"]
|
||||
pwr_stop = settings["VNA:POW:STOP"]
|
||||
self.assertGreaterEqual(pwr_start, pwr_min)
|
||||
self.assertLess(pwr_start, pwr_stop)
|
||||
self.assertLessEqual(pwr_stop, pwr_max)
|
||||
|
||||
f = settings["VNA:STIM:FREQ"]
|
||||
self.assertGreaterEqual(f, f_min)
|
||||
self.assertLessEqual(f, f_max)
|
||||
|
||||
pwr = settings["VNA:STIM:LVL"]
|
||||
self.assertGreaterEqual(pwr, pwr_min)
|
||||
self.assertLessEqual(pwr, pwr_max)
|
||||
|
||||
self.assertEqual(settings["VNA:SWEEP"], "FREQUENCY")
|
||||
|
||||
def test_rst_basic(self):
|
||||
self.vna.cmd("*RST")
|
||||
settings = self.query_settings()
|
||||
self.validate_settings(settings)
|
||||
|
||||
def test_rst_hard(self):
|
||||
self.vna.cmd("*RST")
|
||||
settings1 = self.query_settings()
|
||||
self.validate_settings(settings1)
|
||||
|
||||
# Get limits.
|
||||
f_min = settings1["DEV:INF:LIM:MINF"]
|
||||
f_max = settings1["DEV:INF:LIM:MAXF"]
|
||||
f_1_3 = (2 * f_min + f_max) / 3
|
||||
f_1_2 = (f_min + f_max) / 2
|
||||
f_2_3 = (f_min + 2 * f_max) / 3
|
||||
ifbw_min = settings1["DEV:INF:LIM:MINIFBW"]
|
||||
ifbw_max = settings1["DEV:INF:LIM:MAXIFBW"]
|
||||
ifbw_1_2 = (ifbw_min + ifbw_max) / 2
|
||||
points_max = settings1["DEV:INF:LIM:MAXP"]
|
||||
pwr_min = settings1["DEV:INF:LIM:MINPOW"]
|
||||
pwr_max = settings1["DEV:INF:LIM:MAXPOW"]
|
||||
pwr_1_3 = (2 * pwr_min + pwr_max) / 3
|
||||
pwr_2_3 = (pwr_min + 2 * pwr_max) / 3
|
||||
pwr_1_2 = (pwr_min + pwr_max) / 2
|
||||
rbw_min = settings1["DEV:INF:LIM:MINRBW"]
|
||||
rbw_max = settings1["DEV:INF:LIM:MAXRBW"]
|
||||
rbw_1_2 = (rbw_max + rbw_max) / 2
|
||||
|
||||
# Change parameters.
|
||||
self.vna.cmd("DEV:MODE SA")
|
||||
self.vna.cmd("DEV:REF:IN AUTO")
|
||||
self.vna.cmd("DEV:REF:OUT 10")
|
||||
self.vna.cmd(f"GEN:FREQ {f_1_2}")
|
||||
self.vna.cmd(f"GEN:LVL {pwr_1_2}")
|
||||
self.vna.cmd("GEN:PORT 2")
|
||||
self.vna.cmd("SA:ACQ:AVG 3")
|
||||
self.vna.cmd("SA:ACQ:DET -PEAK")
|
||||
self.vna.cmd(f"SA:ACQ:RBW {rbw_1_2}")
|
||||
self.vna.cmd("SA:ACQ:SINGLE TRUE")
|
||||
self.vna.cmd("SA:ACQ:WIND HANN")
|
||||
self.vna.cmd(f"SA:FREQ:START {f_1_3} STOP {f_2_3}")
|
||||
self.vna.cmd("SA:TRACK:EN TRUE")
|
||||
self.vna.cmd(f"SA:TRACK:LVL {pwr_1_2}")
|
||||
self.vna.cmd("SA:TRACK:NORM:EN TRUE")
|
||||
self.vna.cmd(f"SA:TRACK:NORM:LVL {pwr_1_3}")
|
||||
self.vna.cmd("SA:TRACK:OFF 1.0e+6;PORT 2")
|
||||
self.vna.cmd("VNA:ACQ:AVG 10")
|
||||
self.vna.cmd(f"VNA:ACQ:IFBW {ifbw_1_2}")
|
||||
self.vna.cmd(f"VNA:ACQ:POINTS 100")
|
||||
self.vna.cmd("VNA:ACQ:SINGLE TRUE")
|
||||
self.vna.cmd(f"VNA:FREQ:START {f_1_2};STOP {f_max}")
|
||||
self.vna.cmd(f"VNA:POW:START {pwr_1_3};STOP {pwr_2_3}")
|
||||
self.vna.cmd(f"VNA:STIM:FREQ {f_1_3}")
|
||||
self.vna.cmd(f"VNA:STIM:LVL {pwr_min}")
|
||||
self.vna.cmd("VNA:SWEEP POWER")
|
||||
|
||||
# Reset and verify all settings revert.
|
||||
self.vna.cmd("*RST")
|
||||
|
||||
settings2 = self.query_settings()
|
||||
for key, value in settings1.items():
|
||||
self.assertEqual(value, settings2[key])
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
from tests.TestBase import TestBase
|
||||
import time
|
||||
|
||||
class TestSASweep(TestBase):
|
||||
def waitSweepTimeout(self, timeout = 1):
|
||||
self.assertEqual(self.vna.query(":SA:ACQ:FIN?"), "FALSE")
|
||||
stoptime = time.time() + timeout
|
||||
while self.vna.query(":SA:ACQ:FIN?") == "FALSE":
|
||||
if time.time() > stoptime:
|
||||
raise AssertionError("Sweep timed out")
|
||||
|
||||
def assertTrace_dB(self, trace, dB_nominal, dB_deviation):
|
||||
for S in trace:
|
||||
self.assertLessEqual(S[1], dB_nominal + dB_deviation)
|
||||
self.assertGreaterEqual(S[1], dB_nominal - dB_deviation)
|
||||
|
||||
def test_sweep_frequency(self):
|
||||
self.vna.cmd(":DEV:MODE SA")
|
||||
self.vna.cmd(":SA:FREQ:CENT 2000000000")
|
||||
self.vna.cmd(":SA:FREQ:SPAN 200000")
|
||||
|
||||
self.assertEqual(float(self.vna.query(":SA:FREQ:START?")), 1999900000)
|
||||
self.assertEqual(float(self.vna.query(":SA:FREQ:STOP?")), 2000100000)
|
||||
|
||||
self.vna.cmd(":SA:ACQ:RBW 10000")
|
||||
self.waitSweepTimeout(10)
|
||||
|
||||
port1 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT1"))
|
||||
port2 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT2"))
|
||||
|
||||
self.assertEqual(port1[0][0], 1999900000)
|
||||
self.assertEqual(port1[-1][0], 2000100000)
|
||||
self.assertEqual(port2[0][0], 1999900000)
|
||||
self.assertEqual(port2[-1][0], 2000100000)
|
||||
|
||||
# No signal present, signal level should be very low
|
||||
self.assertTrace_dB(port1, -140, 60)
|
||||
self.assertTrace_dB(port2, -140, 60)
|
||||
|
||||
def test_sweep_zerospan(self):
|
||||
self.vna.cmd(":DEV:MODE SA")
|
||||
self.vna.cmd(":SA:FREQ:CENT 2000000000")
|
||||
self.vna.cmd(":SA:FREQ:SPAN 0")
|
||||
|
||||
self.assertEqual(float(self.vna.query(":SA:FREQ:START?")), 2000000000)
|
||||
self.assertEqual(float(self.vna.query(":SA:FREQ:STOP?")), 2000000000)
|
||||
|
||||
self.vna.cmd(":SA:ACQ:RBW 10000")
|
||||
self.waitSweepTimeout(10)
|
||||
|
||||
port1 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT1"))
|
||||
port2 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT2"))
|
||||
|
||||
self.assertEqual(port1[0][0], 0.0)
|
||||
self.assertGreater(port1[-1][0], 4)
|
||||
self.assertLess(port1[-1][0], 8)
|
||||
self.assertEqual(port2[0][0], 0.0)
|
||||
self.assertGreater(port2[-1][0], 4)
|
||||
self.assertLess(port2[-1][0], 8)
|
||||
|
||||
# No signal present, signal level should be very low
|
||||
self.assertTrace_dB(port1, -140, 60)
|
||||
self.assertTrace_dB(port2, -140, 60)
|
||||
|
||||
def test_tracking_generator(self):
|
||||
self.vna.cmd(":DEV:MODE SA")
|
||||
self.vna.cmd(":SA:FREQ:CENT 1930000000")
|
||||
self.vna.cmd(":SA:FREQ:SPAN 200000")
|
||||
self.vna.cmd(":SA:ACQ:RBW 10000")
|
||||
self.vna.cmd(":SA:TRACK:PORT 1")
|
||||
self.assertEqual(self.vna.query(":SA:TRACK:PORT?"), "1")
|
||||
self.vna.cmd(":SA:TRACK:LVL -20")
|
||||
self.vna.cmd(":SA:TRACK:OFFSET 0")
|
||||
self.vna.cmd(":SA:TRACK:EN TRUE")
|
||||
self.waitSweepTimeout(10)
|
||||
|
||||
port1 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT1"))
|
||||
|
||||
level = port1[0][1]
|
||||
self.assertGreater(level, -30)
|
||||
self.assertLess(level, -10)
|
||||
# check tracking generator signal
|
||||
self.assertTrace_dB(port1, level, 5)
|
||||
|
||||
# Enable normalization
|
||||
self.vna.cmd(":SA:TRACK:NORM:LVL -10")
|
||||
self.vna.cmd(":SA:TRACK:NORM:EN TRUE")
|
||||
# wait for normalization to finish
|
||||
self.waitSweepTimeout(10)
|
||||
|
||||
# trigger the next sweep
|
||||
self.vna.cmd(":SA:ACQ:SINGLE TRUE")
|
||||
self.waitSweepTimeout(10)
|
||||
|
||||
# Reported level on port1 should match normalization very closely now
|
||||
port1 = self.vna.parse_SA_trace_data(self.vna.query(":SA:TRACE:DATA? PORT1"))
|
||||
self.assertTrace_dB(port1, -10, 1)
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import re
|
||||
from tests.TestBase import TestBase
|
||||
|
||||
|
||||
class TestStatusRegisters(TestBase):
|
||||
def query_stb(self):
|
||||
resp = self.vna.query("*STB?")
|
||||
self.assertTrue(re.match(r"^\d+$", resp))
|
||||
value = int(resp)
|
||||
self.assertTrue(value >= 0 and value <= 255)
|
||||
return value
|
||||
|
||||
def test_invalid_command(self):
|
||||
status = self.vna.get_status()
|
||||
self.assertEqual(status, 0)
|
||||
self.vna.default_check_cmds = False
|
||||
self.vna.cmd("INVALID:COMMAND")
|
||||
status = self.vna.get_status()
|
||||
self.assertEqual(status & 0x3C, 0x20)
|
||||
status = self.vna.get_status()
|
||||
self.assertEqual(status, 0)
|
||||
|
||||
def test_invalid_query(self):
|
||||
status = self.vna.get_status()
|
||||
self.assertEqual(status, 0)
|
||||
self.vna.default_check_cmds = False
|
||||
self.vna.cmd("INVALID:QUERY?") # send as cmd to avoid timeout
|
||||
status = self.vna.get_status()
|
||||
self.assertTrue(status & 0x20) # expect CME
|
||||
status = self.vna.get_status()
|
||||
self.assertEqual(status, 0)
|
||||
|
||||
def test_stb(self):
|
||||
self.vna.default_check_cmds = False
|
||||
self.vna.cmd("*SRE 0")
|
||||
status = self.vna.get_status()
|
||||
if status & 0x20:
|
||||
self.skipTest("Skipping test: *SRE, *SRE?, *STB? not implemented")
|
||||
self.vna.cmd("*RST")
|
||||
self.vna.cmd("VNA:ACQ:SINGLE TRUE")
|
||||
self.vna.cmd("*WAI")
|
||||
status = self.vna.get_status()
|
||||
self.assertEqual(status, 0)
|
||||
self.vna.cmd("OPC") # should set OPC
|
||||
self.vna.cmd(f"*ESE {0x21:d}") # mask is CME|OPC
|
||||
self.assertEqual(self.query_stb() & 0x60, 0x20) # expect !MSS, ESB
|
||||
self.assertEqual(self.query_stb() & 0x60, 0x20) # shouldn't clear
|
||||
self.vna.cmd(f"*SRE {0x20:d}") # unmask ESB
|
||||
self.assertEqual(self.query_stb() & 0x60, 0x60) # expect MSS, ESB
|
||||
self.vna.cmd(f"*ESE {0x20:d}") # mask is CME only
|
||||
self.assertEqual(self.query_stb() & 0x60, 0) # expect !MSS, !ESB
|
||||
self.vna.cmd("INVALID:COMMAND") # should set CME
|
||||
self.assertEqual(self.query_stb() & 0x60, 0x60) # expect MSS, ESB
|
||||
status = self.get_status()
|
||||
self.assertEqual(status, 0x21) # expect CMD|OPC, clears
|
||||
self.assertEqual(self.query_stb() & 0x60, 0) # expect !MSS, !ESB
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
from tests.TestBase import TestBase
|
||||
import time
|
||||
|
||||
|
||||
class TestSync(TestBase):
|
||||
def test_wai(self):
|
||||
self.vna.cmd("*RST")
|
||||
self.vna.cmd("VNA:ACQ:SINGLE TRUE")
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "FALSE")
|
||||
self.vna.cmd("*WAI", timeout=3)
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "TRUE")
|
||||
|
||||
def test_opc_query(self):
|
||||
self.vna.cmd("*RST")
|
||||
self.vna.cmd("VNA:ACQ:SINGLE TRUE")
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "FALSE")
|
||||
resp = self.vna.query("*OPC?", timeout=3)
|
||||
self.assertEqual(resp, "1")
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "TRUE")
|
||||
|
||||
def test_opc_poll(self):
|
||||
self.vna.cmd("*RST")
|
||||
self.vna.cmd("VNA:ACQ:SINGLE TRUE")
|
||||
self.vna.cmd("*OPC")
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "FALSE")
|
||||
time_limit = time.time() + 4
|
||||
while True:
|
||||
status = self.vna.get_status()
|
||||
if status & 0x01:
|
||||
break
|
||||
if time.time() >= time_limit:
|
||||
raise Exception("Timeout waiting for OPC")
|
||||
time.sleep(0.05)
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "TRUE")
|
||||
self.assertEqual(self.vna.get_status(), 0)
|
||||
|
||||
def test_idle_waits(self):
|
||||
'''
|
||||
Test that *WAI and *OPC? don't hang when device is idle. Test
|
||||
that *OPC query sets the OPC status bit immediately.
|
||||
'''
|
||||
self.vna.cmd("*RST")
|
||||
self.vna.cmd("VNA:ACQ:SINGLE TRUE")
|
||||
self.vna.cmd("*WAI", timeout=3)
|
||||
self.assertEqual(self.vna.get_status(), 0)
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "TRUE")
|
||||
self.vna.cmd("*WAI")
|
||||
resp = self.vna.query("*OPC?")
|
||||
self.assertEqual(resp, "1")
|
||||
self.assertEqual(self.vna.get_status(), 0)
|
||||
self.assertEqual(self.vna.cmd("*OPC"), 0x01) # should return OPC
|
||||
self.assertEqual(self.vna.get_status(), 0)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
from tests.TestBase import TestBase
|
||||
|
||||
class TestConnect(TestBase):
|
||||
def test_temperature(self):
|
||||
res = self.vna.query(":DEV:INF:TEMP?")
|
||||
self.assertEqual(len(res.split("/")), 3)
|
||||
|
||||
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
from tests.TestBase import TestBase
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
class TestUpdate(TestBase):
|
||||
def test_Update(self):
|
||||
# first update: actually update to the firmware version we want to test
|
||||
self.vna.cmd("DEV:UPDATE ../../combined.vnafw", timeout=60)
|
||||
# second update: check that we still have a working firmware update with this version
|
||||
self.vna.cmd("DEV:UPDATE ../../combined.vnafw", timeout=60)
|
||||
|
||||
reported = self.vna.query("DEV:INF:FWREVISION?")
|
||||
major = subprocess.check_output("grep -oP '(?<=FW_MAJOR=)[0-9]+' ../VNA_embedded/Makefile", shell=True).strip()
|
||||
minor = subprocess.check_output("grep -oP '(?<=FW_MINOR=)[0-9]+' ../VNA_embedded/Makefile", shell=True).strip()
|
||||
patch = subprocess.check_output("grep -oP '(?<=FW_PATCH=)[0-9]+' ../VNA_embedded/Makefile", shell=True).strip()
|
||||
expected = major.decode("utf-8") + "." + minor.decode("utf-8") + "." + patch.decode("utf-8")
|
||||
self.assertEqual(reported, expected)
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
from tests.TestBase import TestBase
|
||||
import time
|
||||
|
||||
class TestVNASweep(TestBase):
|
||||
def waitSweepTimeout(self, timeout = 1):
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "FALSE")
|
||||
self.vna.cmd("*WAI", timeout=timeout)
|
||||
self.assertEqual(self.vna.query(":VNA:ACQ:FIN?"), "TRUE")
|
||||
|
||||
def test_sweep_frequency(self):
|
||||
self.vna.cmd(":DEV:MODE VNA")
|
||||
self.vna.cmd(":VNA:SWEEP FREQUENCY")
|
||||
self.vna.cmd(":VNA:STIM:LVL -10")
|
||||
self.vna.cmd(":VNA:ACQ:IFBW 10000")
|
||||
self.vna.cmd(":VNA:ACQ:AVG 1")
|
||||
self.vna.cmd(":VNA:ACQ:POINTS 501")
|
||||
self.vna.cmd(":VNA:FREQuency:START 1000000")
|
||||
self.vna.cmd(":VNA:FREQuency:STOP 6000000000")
|
||||
self.waitSweepTimeout(2)
|
||||
|
||||
S11 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
|
||||
self.assertEqual(S11[0][0], 1000000)
|
||||
self.assertEqual(S11[-1][0], 6000000000)
|
||||
|
||||
def test_sweep_zerospan(self):
|
||||
self.vna.cmd(":DEV:MODE VNA")
|
||||
self.vna.cmd(":VNA:SWEEP FREQUENCY")
|
||||
self.vna.cmd(":VNA:STIM:LVL -10")
|
||||
self.vna.cmd(":VNA:ACQ:IFBW 10000")
|
||||
self.vna.cmd(":VNA:ACQ:AVG 1")
|
||||
self.vna.cmd(":VNA:ACQ:POINTS 501")
|
||||
self.vna.cmd(":VNA:FREQuency:START 500000000")
|
||||
self.vna.cmd(":VNA:FREQuency:STOP 1500000000")
|
||||
self.vna.cmd(":VNA:FREQuency:ZERO 1500000000")
|
||||
self.waitSweepTimeout(2)
|
||||
|
||||
S11 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
|
||||
self.assertEqual(S11[0][0], 0.0)
|
||||
# Sweep should take about 0.125 seconds
|
||||
self.assertGreater(S11[-1][0], 0.1)
|
||||
self.assertLess(S11[-1][0], 0.5)
|
||||
|
||||
def test_sweep_power(self):
|
||||
self.vna.cmd(":DEV:MODE VNA")
|
||||
self.vna.cmd(":VNA:SWEEP POWER")
|
||||
self.vna.cmd(":VNA:STIM:LVL -10")
|
||||
self.vna.cmd(":VNA:ACQ:IFBW 10000")
|
||||
self.vna.cmd(":VNA:ACQ:AVG 1")
|
||||
self.vna.cmd(":VNA:ACQ:POINTS 501")
|
||||
self.vna.cmd(":VNA:POWER:START -30")
|
||||
self.vna.cmd(":VNA:POWER:STOP -10")
|
||||
self.waitSweepTimeout(2)
|
||||
|
||||
S11 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
|
||||
self.assertEqual(S11[0][0], -30)
|
||||
self.assertEqual(S11[-1][0], -10)
|
||||
|
||||
def test_fast_single_sweeps(self):
|
||||
self.vna.cmd(":DEV:MODE VNA")
|
||||
self.vna.cmd(":VNA:SWEEP FREQUENCY")
|
||||
self.vna.cmd(":VNA:STIM:LVL -10")
|
||||
self.vna.cmd(":VNA:ACQ:IFBW 50000")
|
||||
self.vna.cmd(":VNA:ACQ:AVG 1")
|
||||
self.vna.cmd(":VNA:ACQ:POINTS 501")
|
||||
self.vna.cmd(":VNA:FREQuency:START 1000000")
|
||||
self.vna.cmd(":VNA:FREQuency:STOP 6000000000")
|
||||
|
||||
for i in range(10):
|
||||
# Change something irrelevant (to force reconfiguration of device)
|
||||
self.vna.cmd(":VNA:FREQuency:START "+str(1000000+i))
|
||||
self.waitSweepTimeout(2)
|
||||
|
||||
def test_segmented_sweep(self):
|
||||
self.vna.cmd(":DEV:MODE VNA")
|
||||
self.vna.cmd(":VNA:SWEEP FREQUENCY")
|
||||
self.vna.cmd(":VNA:STIM:LVL -10")
|
||||
self.vna.cmd(":VNA:ACQ:IFBW 50000")
|
||||
self.vna.cmd(":VNA:ACQ:AVG 1")
|
||||
self.vna.cmd(":VNA:ACQ:POINTS 10000")
|
||||
self.vna.cmd(":VNA:FREQuency:START 1000000")
|
||||
self.vna.cmd(":VNA:FREQuency:STOP 6000000000")
|
||||
self.waitSweepTimeout(10)
|
||||
|
||||
S11 = self.vna.parse_VNA_trace_data(self.vna.query(":VNA:TRACE:DATA? S11"))
|
||||
self.assertEqual(len(S11), 10000)
|
||||
+1
@@ -0,0 +1 @@
|
||||
GUI_PATH = "../PC_Application/LibreVNA-GUI/LibreVNA-GUI"
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../Documentation/UserManual/SCPI_Examples/libreVNA.py
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
Application
|
||||
/build-*
|
||||
/LibreVNA-GUI/LibreVNA-GUI
|
||||
/LibreVNA-Test/LibreVNA-Test
|
||||
/LibreVNA-GUI/users*appdatalocaltemp*
|
||||
/LibreVNA/build
|
||||
LibreVNA-GUI/build
|
||||
|
||||
# qmake.stash
|
||||
LibreVNA-GUI/.qmake.stash
|
||||
|
||||
|
||||
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="564e", MODE:="0666"
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="4121", MODE:="0666"
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="4121", MODE:="0666"
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalStandardLineEditDialog</class>
|
||||
<widget class="QDialog" name="CalStandardLineEditDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>383</width>
|
||||
<height>202</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Line Standard</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<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="SIUnitEdit" name="Z0">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Delay:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="delay"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Minimum usable frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="minFreq">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Maximum usable frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="maxFreq">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</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::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>CalStandardLineEditDialog</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>CalStandardLineEditDialog</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="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalStandardLoadEditDialog</class>
|
||||
<widget class="QDialog" name="CalStandardLoadEditDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>344</width>
|
||||
<height>386</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Load Standard</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="coefficients">
|
||||
<property name="text">
|
||||
<string>Coefficients</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="measurement">
|
||||
<property name="text">
|
||||
<string>Measurement file</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_29">
|
||||
<property name="text">
|
||||
<string>Resistance:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="SIUnitEdit" name="resistance"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_30">
|
||||
<property name="text">
|
||||
<string>Z0:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="Z0">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Offset delay [ps]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="delay"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Offset loss [GΩ/s]: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="loss"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Parallel C:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="SIUnitEdit" name="parC"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>Series L:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="SIUnitEdit" name="serL"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Load Parameter Model</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_15">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="L_first">
|
||||
<property name="text">
|
||||
<string>Series L first</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_2</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="C_first">
|
||||
<property name="text">
|
||||
<string>Shunt C first</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_2</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="measurementLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="updateFile">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update from file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="TouchstoneImport" name="touchstoneImport" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TouchstoneImport</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>CustomWidgets/touchstoneimport.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CalStandardLoadEditDialog</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>CalStandardLoadEditDialog</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="buttonGroup_2"/>
|
||||
<buttongroup name="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalStandardOpenEditDialog</class>
|
||||
<widget class="QDialog" name="CalStandardOpenEditDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>358</width>
|
||||
<height>342</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Open Standard</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="coefficients">
|
||||
<property name="text">
|
||||
<string>Coefficients</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="measurement">
|
||||
<property name="text">
|
||||
<string>Measurement file</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Z0:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="SIUnitEdit" name="Z0">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Offset delay [ps]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="delay"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Offset loss [GΩ/s]: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="loss"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>C0 [10<sup>-15</sup>F]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="C0"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>C1 [10<sup>-27</sup>F/Hz]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="SIUnitEdit" name="C1"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>C2 [10<sup>-36</sup>F/Hz<sup>2</sup>]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="SIUnitEdit" name="C2"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>C3 [10<span style=" vertical-align:super;">-45</span>F/Hz<span style=" vertical-align:super;">3</span>]:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="SIUnitEdit" name="C3"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="measurementLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="updateFile">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update from file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="TouchstoneImport" name="touchstoneImport" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TouchstoneImport</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>CustomWidgets/touchstoneimport.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CalStandardOpenEditDialog</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>CalStandardOpenEditDialog</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="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalStandardReflectEditDialog</class>
|
||||
<widget class="QDialog" name="CalStandardReflectEditDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>291</width>
|
||||
<height>109</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Reflect Standard</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Reflection type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="type">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Short</string>
|
||||
</property>
|
||||
</item>
|
||||
</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::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CalStandardReflectEditDialog</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>CalStandardReflectEditDialog</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>
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalStandardShortEditDialog</class>
|
||||
<widget class="QDialog" name="CalStandardShortEditDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>358</width>
|
||||
<height>342</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Short Standard</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="coefficients">
|
||||
<property name="text">
|
||||
<string>Coefficients</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="measurement">
|
||||
<property name="text">
|
||||
<string>Measurement file</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Z0:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="SIUnitEdit" name="Z0">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Offset delay [ps]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="delay"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Offset loss [GΩ/s]: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="loss"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>L0 [10<span style=" vertical-align:super;">-12</span>H]:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="L0"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>L1 [10<span style=" vertical-align:super;">-24</span>H/Hz]:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="SIUnitEdit" name="L1"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>L2 [10<span style=" vertical-align:super;">-33</span>H/Hz<span style=" vertical-align:super;">2</span>]:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="SIUnitEdit" name="L2"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>L3 [10<span style=" vertical-align:super;">-42</span>H/Hz<span style=" vertical-align:super;">3</span>]:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="SIUnitEdit" name="L3"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="measurementLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="updateFile">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update from file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="TouchstoneImport" name="touchstoneImport" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TouchstoneImport</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>CustomWidgets/touchstoneimport.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CalStandardShortEditDialog</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>CalStandardShortEditDialog</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="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalStandardThroughEditDialog</class>
|
||||
<widget class="QDialog" name="CalStandardThroughEditDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>358</width>
|
||||
<height>342</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Through Standard</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="coefficients">
|
||||
<property name="text">
|
||||
<string>Coefficients</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="measurement">
|
||||
<property name="text">
|
||||
<string>Measurement file</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<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="SIUnitEdit" name="Z0">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Delay [ps]:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="delay"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Loss [GΩ/s]: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="loss"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="measurementLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="updateFile">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update from file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="TouchstoneImport" name="touchstoneImport" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TouchstoneImport</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>CustomWidgets/touchstoneimport.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CalStandardThroughEditDialog</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>CalStandardThroughEditDialog</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="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
+1017
File diff suppressed because it is too large
Load Diff
+128
@@ -0,0 +1,128 @@
|
||||
#ifndef CALDEVICE_H
|
||||
#define CALDEVICE_H
|
||||
|
||||
#include "usbdevice.h"
|
||||
#include "touchstone.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
#include <thread>
|
||||
|
||||
class CalDevice : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CalDevice(QString serial);
|
||||
~CalDevice();
|
||||
|
||||
class Standard {
|
||||
public:
|
||||
enum class Type {
|
||||
Open,
|
||||
Short,
|
||||
Load,
|
||||
Through,
|
||||
None
|
||||
};
|
||||
Standard(Type type) : type(type), throughDest(0){}
|
||||
Standard(int throughDest) : type(Type::Through), throughDest(throughDest){}
|
||||
Type type;
|
||||
int throughDest;
|
||||
};
|
||||
|
||||
static QString StandardToString(Standard s);
|
||||
static Standard StandardFromString(QString s);
|
||||
|
||||
Standard getStandard(int port);
|
||||
bool setStandard(int port, Standard s);
|
||||
static std::vector<Standard> availableStandards();
|
||||
|
||||
double getTemperature();
|
||||
bool stabilized();
|
||||
double getHeaterPower();
|
||||
|
||||
QString serial();
|
||||
QString getFirmware() const;
|
||||
unsigned int getNumPorts() const;
|
||||
|
||||
QString getDateTimeUTC();
|
||||
|
||||
bool enterBootloader();
|
||||
|
||||
class CoefficientSet {
|
||||
public:
|
||||
QString name;
|
||||
int ports;
|
||||
class Coefficient {
|
||||
public:
|
||||
Coefficient() : t(Touchstone(1)), modified(false) {}
|
||||
Touchstone t;
|
||||
bool modified;
|
||||
};
|
||||
|
||||
Coefficient *getOpen(int port);
|
||||
Coefficient *getShort(int port);
|
||||
Coefficient *getLoad(int port);
|
||||
Coefficient *getThrough(int port1, int port2);
|
||||
|
||||
std::map<int, Coefficient*> opens;
|
||||
std::map<int, Coefficient*> shorts;
|
||||
std::map<int, Coefficient*> loads;
|
||||
std::map<int, Coefficient*> throughs;
|
||||
|
||||
int portsToThroughIndex(int port1, int port2);
|
||||
void portsFromThroughIndex(int &port1, int &port2, int index);
|
||||
|
||||
void createEmptyCoefficients();
|
||||
bool isEmpty();
|
||||
};
|
||||
|
||||
// Extracts the coefficients from the device. This is done with a dedicated thread.
|
||||
// Do not call any other functions until the update is finished. Process can be
|
||||
// monitored through the updateCoefficientsPercent and updateCoefficientsDone signals
|
||||
void loadCoefficientSets(QStringList names = QStringList(), QList<int> ports = {}, bool fast=true);
|
||||
|
||||
void abortCoefficientLoading();
|
||||
// Writes coefficient sets to the device. This will only write modified files to save
|
||||
// time. This is done with a dedicated thread.
|
||||
// Do not call any other functions until the update is finished. Process can be
|
||||
// monitored through the updateCoefficientsPercent and updateCoefficientsDone signals
|
||||
void saveCoefficientSets();
|
||||
std::vector<CoefficientSet> getCoefficientSets() const;
|
||||
|
||||
void addCoefficientSet(QString name);
|
||||
|
||||
QStringList getCoefficientSetNames();
|
||||
|
||||
bool hasModifiedCoefficients();
|
||||
|
||||
bool coefficientTransferActive() { return transferActive; }
|
||||
|
||||
public slots:
|
||||
void factoryUpdateDialog();
|
||||
|
||||
signals:
|
||||
void updateCoefficientsPercent(int percent);
|
||||
// emitted when all coefficients have been received and it is safe to call all functions again
|
||||
void updateCoefficientsDone(bool success);
|
||||
|
||||
void disconnected();
|
||||
|
||||
private:
|
||||
void loadCoefficientSetsThreadSlow(QStringList names, QList<int> ports);
|
||||
void loadCoefficientSetsThreadFast(QStringList names, QList<int> ports);
|
||||
void saveCoefficientSetsThread();
|
||||
|
||||
USBDevice *usb;
|
||||
QString firmware;
|
||||
int numPorts;
|
||||
std::thread *loadThread;
|
||||
bool abortLoading;
|
||||
bool transferActive;
|
||||
|
||||
float firmware_major_minor;
|
||||
|
||||
std::vector<CoefficientSet> coeffSets;
|
||||
};
|
||||
|
||||
#endif // CALDEVICE_H
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FactoryUpdateDialog</class>
|
||||
<widget class="QDialog" name="FactoryUpdateDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>635</width>
|
||||
<height>448</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Factory Coefficient Update</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Updating the factory coefficients is normally not necessary. But in case they have been lost or modified accidentally, they can be set to the original state again.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="updateServer">
|
||||
<property name="text">
|
||||
<string>Update coefficients from manufacturing server</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="updateFile">
|
||||
<property name="text">
|
||||
<string>Update from file</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="file"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browse">
|
||||
<property name="toolTip">
|
||||
<string>Select file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="msg">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progress">
|
||||
<property name="value">
|
||||
<number>24</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="startUpdate">
|
||||
<property name="text">
|
||||
<string>Update Factory Coefficients</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
+688
@@ -0,0 +1,688 @@
|
||||
#include "librecaldialog.h"
|
||||
#include "ui_librecaldialog.h"
|
||||
|
||||
#include "caldevice.h"
|
||||
#include "usbdevice.h"
|
||||
#include "CustomWidgets/informationbox.h"
|
||||
#include "preferences.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <QFormLayout>
|
||||
|
||||
using namespace std;
|
||||
|
||||
LibreCALDialog::LibreCALDialog(Calibration *cal) :
|
||||
QDialog(nullptr),
|
||||
ui(new Ui::LibreCALDialog),
|
||||
cal(cal),
|
||||
device(nullptr),
|
||||
busy(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
createPortAssignmentUI();
|
||||
|
||||
connect(this, &LibreCALDialog::portAssignmentChanged, this, &LibreCALDialog::updateCalibrationStartStatus);
|
||||
|
||||
connect(ui->cbDevice, &QComboBox::currentTextChanged, [=](QString text) {
|
||||
if(device) {
|
||||
delete device;
|
||||
device = nullptr;
|
||||
}
|
||||
try {
|
||||
device = new CalDevice(text);
|
||||
} catch (exception &e) {
|
||||
device = nullptr;
|
||||
InformationBox::ShowError("Failed to connect", e.what(), this);
|
||||
}
|
||||
if(device) {
|
||||
createPortAssignmentUI();
|
||||
connect(device, &CalDevice::updateCoefficientsPercent, ui->progressCoeff, &QProgressBar::setValue);
|
||||
connect(device, &CalDevice::updateCoefficientsDone, this, [=](bool success){
|
||||
busy = false;
|
||||
updateCalibrationStartStatus();
|
||||
if(success) {
|
||||
ui->progressCoeff->setValue(100);
|
||||
ui->lCalibrationStatus->setText("Coefficients loaded.");
|
||||
coeffSet = device->getCoefficientSets()[0];
|
||||
|
||||
if(validateCoefficients()) {
|
||||
startCalibration();
|
||||
} else {
|
||||
enableUI();
|
||||
}
|
||||
} else {
|
||||
ui->progressCal->setValue(0);
|
||||
ui->lCalibrationStatus->setText("Failed to load coefficients");
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
ui->cbCoefficients->clear();
|
||||
ui->cbCoefficients->addItem("Select...");
|
||||
for(auto c : device->getCoefficientSetNames()) {
|
||||
ui->cbCoefficients->addItem(c);
|
||||
}
|
||||
ui->cbCoefficients->setEnabled(true);
|
||||
// select first available coefficient set
|
||||
if(ui->cbCoefficients->count() > 1) {
|
||||
ui->cbCoefficients->setCurrentIndex(1);
|
||||
}
|
||||
} else {
|
||||
ui->cbCoefficients->clear();
|
||||
ui->cbCoefficients->setEnabled(false);
|
||||
ui->start->setEnabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->cbCoefficients, &QComboBox::currentTextChanged, [=](){
|
||||
// no coefficient set selected
|
||||
ui->progressCoeff->setValue(0);
|
||||
coeffSet = CalDevice::CoefficientSet();
|
||||
ui->lCalibrationStatus->setText("No coefficients loaded");
|
||||
updateCalibrationStartStatus();
|
||||
});
|
||||
|
||||
auto deviceList = USBDevice::GetDevices();
|
||||
for(auto device : deviceList) {
|
||||
ui->cbDevice->addItem(device);
|
||||
}
|
||||
|
||||
connect(ui->start, &QPushButton::clicked, this, &LibreCALDialog::determineAutoPorts);
|
||||
connect(this, &LibreCALDialog::autoPortComplete, this, &LibreCALDialog::loadCoefficients);
|
||||
|
||||
updateCalibrationStartStatus();
|
||||
updateDeviceStatus();
|
||||
connect(&updateTimer, &QTimer::timeout, this, &LibreCALDialog::updateDeviceStatus);
|
||||
updateTimer.start(1000);
|
||||
}
|
||||
|
||||
LibreCALDialog::~LibreCALDialog()
|
||||
{
|
||||
if(device) {
|
||||
device->abortCoefficientLoading();
|
||||
}
|
||||
delete device;
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool LibreCALDialog::validatePortSelection(bool autoAllowed)
|
||||
{
|
||||
set<int> usedCalPorts;
|
||||
QString status;
|
||||
bool canStart = true;
|
||||
// Check port mapping for duplicate entries (and at least one used port)
|
||||
for(auto port : portAssignment) {
|
||||
if(autoAllowed && port == -1) {
|
||||
// auto port, to be determined later
|
||||
usedCalPorts.insert(port);
|
||||
// skip duplicate selection
|
||||
continue;
|
||||
} else if(port < 1) {
|
||||
// skip unused ports
|
||||
continue;
|
||||
}
|
||||
if(usedCalPorts.count(port)) {
|
||||
status = "LibreCAL port "+QString::number(port)+" is assigned to multiple VNA ports.";
|
||||
canStart = false;
|
||||
break;
|
||||
} else {
|
||||
usedCalPorts.insert(port);
|
||||
}
|
||||
}
|
||||
if(canStart) {
|
||||
// at least one port must be used
|
||||
if(usedCalPorts.size() == 0) {
|
||||
status = "At least one port must be assigned.";
|
||||
canStart = false;
|
||||
}
|
||||
}
|
||||
if(!canStart) {
|
||||
ui->lCalibrationStatus->setText(status);
|
||||
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
|
||||
}
|
||||
return canStart;
|
||||
}
|
||||
|
||||
bool LibreCALDialog::validateCoefficients()
|
||||
{
|
||||
bool canStart = true;
|
||||
QString status;
|
||||
|
||||
double coeffMinFreq = numeric_limits<double>::max();
|
||||
double coeffMaxFreq = numeric_limits<double>::lowest();
|
||||
|
||||
auto checkCoefficient = [&](CalDevice::CoefficientSet::Coefficient *c) -> bool {
|
||||
if(!c) {
|
||||
return false;
|
||||
}
|
||||
if(c->t.points() == 0) {
|
||||
return false;
|
||||
} else {
|
||||
if(c->t.minFreq() < coeffMinFreq) {
|
||||
coeffMinFreq = c->t.minFreq();
|
||||
}
|
||||
if(c->t.maxFreq() > coeffMaxFreq) {
|
||||
coeffMaxFreq = c->t.maxFreq();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
set<int> usedCalPorts;
|
||||
// Check port mapping for duplicate entries (and at least one used port)
|
||||
for(auto port : portAssignment) {
|
||||
if(port > 0) {
|
||||
usedCalPorts.insert(port);
|
||||
}
|
||||
}
|
||||
|
||||
// check if coefficients for all ports are available
|
||||
for(auto i : usedCalPorts) {
|
||||
// Check if OSL coefficients are there
|
||||
if(!checkCoefficient(coeffSet.getOpen(i))) {
|
||||
status = "Open coefficient for LibreCAL port "+QString::number(i)+" is missing.";
|
||||
canStart = false;
|
||||
break;
|
||||
}
|
||||
if(!checkCoefficient(coeffSet.getShort(i))) {
|
||||
status = "Short coefficient for LibreCAL port "+QString::number(i)+" is missing.";
|
||||
canStart = false;
|
||||
break;
|
||||
}
|
||||
if(!checkCoefficient(coeffSet.getLoad(i))) {
|
||||
status = "Load coefficient for LibreCAL port "+QString::number(i)+" is missing.";
|
||||
canStart = false;
|
||||
break;
|
||||
}
|
||||
for(auto j : usedCalPorts) {
|
||||
if(j <= i) {
|
||||
continue;
|
||||
}
|
||||
if(!checkCoefficient(coeffSet.getThrough(i,j))) {
|
||||
status = "Through coefficient for LibreCAL port "+QString::number(i)+" to "+QString::number(j)+" is missing.";
|
||||
canStart = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!canStart) {
|
||||
ui->lCalibrationStatus->setText(status);
|
||||
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
|
||||
}
|
||||
return canStart;
|
||||
}
|
||||
|
||||
void LibreCALDialog::updateCalibrationStartStatus()
|
||||
{
|
||||
bool canStart = true;
|
||||
if(!device) {
|
||||
canStart = false;
|
||||
ui->lCalibrationStatus->setText("Not connected to a LibreCAL device");
|
||||
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
|
||||
}
|
||||
|
||||
if(canStart) {
|
||||
if(ui->cbCoefficients->currentIndex() == 0) {
|
||||
canStart = false;
|
||||
ui->lCalibrationStatus->setText("No coefficient set selected");
|
||||
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
|
||||
}
|
||||
}
|
||||
|
||||
if(canStart) {
|
||||
canStart = validatePortSelection(true);
|
||||
}
|
||||
|
||||
if(canStart) {
|
||||
if(!Preferences::getInstance().Acquisition.allowUseOfUnstableLibreCALTemp && !device->stabilized()) {
|
||||
canStart = false;
|
||||
ui->lCalibrationStatus->setText("LibreCAL temperature unstable");
|
||||
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
|
||||
}
|
||||
}
|
||||
|
||||
ui->start->setEnabled(canStart);
|
||||
if(canStart) {
|
||||
ui->lCalibrationStatus->setText("Ready to start");
|
||||
ui->lCalibrationStatus->setStyleSheet("");
|
||||
}
|
||||
}
|
||||
|
||||
void LibreCALDialog::updateDeviceStatus()
|
||||
{
|
||||
if(!device) {
|
||||
ui->lDeviceStatus->setText("No LibreCAL connected");
|
||||
ui->lDeviceStatus->setStyleSheet("QLabel { color : red; }");
|
||||
return;
|
||||
}
|
||||
if(busy) {
|
||||
// can't update while busy reading coefficients
|
||||
return;
|
||||
}
|
||||
if(device->stabilized()) {
|
||||
ui->lDeviceStatus->setText("LibreCAL ready for calibration");
|
||||
ui->lDeviceStatus->setStyleSheet("");
|
||||
} else {
|
||||
ui->lDeviceStatus->setText("Heating up, please wait with calibration");
|
||||
ui->lDeviceStatus->setStyleSheet("QLabel { color : orange; }");
|
||||
}
|
||||
updateCalibrationStartStatus();
|
||||
}
|
||||
|
||||
void LibreCALDialog::determineAutoPorts()
|
||||
{
|
||||
disableUI();
|
||||
ui->progressCal->setValue(0);
|
||||
ui->lCalibrationStatus->setText("Autodetecting port connections...");
|
||||
ui->lCalibrationStatus->setStyleSheet("QLabel { color : green; }");
|
||||
|
||||
// check if any ports are set to auto
|
||||
bool usesAuto = false;
|
||||
for(auto port : portAssignment) {
|
||||
if(port == -1) {
|
||||
usesAuto = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(usesAuto) {
|
||||
driver = DeviceDriver::getActiveDriver();
|
||||
emit driver->acquireControl();
|
||||
// Determine ports by setting all ports to open and then switching them to short one at a time while observing the change in the VNA measurement
|
||||
for(unsigned int i=1;i<=device->getNumPorts();i++) {
|
||||
device->setStandard(i, CalDevice::Standard::Type::Open);
|
||||
}
|
||||
autoPortMeasurements.clear();
|
||||
startSweep();
|
||||
} else {
|
||||
// no auto ports
|
||||
emit autoPortComplete();
|
||||
}
|
||||
}
|
||||
|
||||
void LibreCALDialog::loadCoefficients()
|
||||
{
|
||||
// validate ports again, at this point no "auto" selection is allowed anymore
|
||||
if(!validatePortSelection(false)) {
|
||||
enableUI();
|
||||
updateCalibrationStartStatus();
|
||||
} else {
|
||||
// can continue with loading coefficients
|
||||
// they might already be loaded from a previous calibration run, check first
|
||||
if(!validateCoefficients()) {
|
||||
// only load required coefficients
|
||||
ui->progressCal->setValue(0);
|
||||
ui->lCalibrationStatus->setText("Loading coefficients...");
|
||||
ui->lCalibrationStatus->setStyleSheet("QLabel { color : green; }");
|
||||
|
||||
// determine the used ports of the LibreCAL
|
||||
QList<int> usedPorts;
|
||||
for(auto port : portAssignment) {
|
||||
if(port > 0) {
|
||||
usedPorts.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
busy = true;
|
||||
device->loadCoefficientSets({ui->cbCoefficients->currentText()}, usedPorts);
|
||||
} else {
|
||||
// can proceed with calibration directly
|
||||
startCalibration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LibreCALDialog::handleIncomingMeasurement(DeviceDriver::VNAMeasurement m)
|
||||
{
|
||||
stopSweep();
|
||||
autoPortMeasurements.push_back(m);
|
||||
auto nextPort = autoPortMeasurements.size();
|
||||
// switch LibreCAL to next port
|
||||
if(nextPort <= device->getNumPorts()) {
|
||||
device->setStandard(nextPort, CalDevice::Standard::Type::Short);
|
||||
}
|
||||
if(nextPort > 1) {
|
||||
device->setStandard(nextPort-1, CalDevice::Standard::Type::Open);
|
||||
}
|
||||
if(nextPort > device->getNumPorts()) {
|
||||
// auto port measurements complete
|
||||
|
||||
// reset LibreCAL ports
|
||||
for(unsigned int i=1;i<=device->getNumPorts();i++) {
|
||||
device->setStandard(i, CalDevice::Standard::Type::None);
|
||||
}
|
||||
// evaluate results
|
||||
for(unsigned int p=0;p<portAssignment.size();p++) {
|
||||
auto port = portAssignment[p];
|
||||
if(port != -1) {
|
||||
// not set to auto, ignore
|
||||
continue;
|
||||
}
|
||||
auto param = "S"+QString::number(p+1)+QString::number(p+1);
|
||||
std::vector<std::complex<double>> measurements;
|
||||
for(auto m : autoPortMeasurements) {
|
||||
if(m.measurements.count(param)) {
|
||||
measurements.push_back(m.measurements[param]);
|
||||
}
|
||||
}
|
||||
if(measurements.size() != device->getNumPorts()+1) {
|
||||
// not all measurements available (maybe the port has no stimulus?), set to unused
|
||||
portAssignmentComboBoxes[p]->setCurrentIndex(0);
|
||||
continue;
|
||||
}
|
||||
// got all required measurements, determine which one deviates the most from the baseline
|
||||
double maxDeviation = 0.0;
|
||||
int maxDevIndex = 0;
|
||||
for(unsigned int i=1;i<=device->getNumPorts();i++) {
|
||||
auto diff = abs(measurements[i] - measurements[0]);
|
||||
if (diff > maxDeviation) {
|
||||
maxDeviation = diff;
|
||||
maxDevIndex = i;
|
||||
}
|
||||
}
|
||||
constexpr double minRequiredDeviation = 0.25;
|
||||
if(maxDeviation > minRequiredDeviation) {
|
||||
portAssignmentComboBoxes[p]->setCurrentIndex(maxDevIndex+1);
|
||||
} else {
|
||||
// not enough deviation, probably unused
|
||||
portAssignmentComboBoxes[p]->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
emit driver->releaseControl();
|
||||
emit autoPortComplete();
|
||||
} else {
|
||||
// trigger the next measurement
|
||||
startSweep();
|
||||
}
|
||||
}
|
||||
|
||||
void LibreCALDialog::startSweep()
|
||||
{
|
||||
// set up a sweep with a single measurement point at the start frequency
|
||||
auto info = driver->getInfo();
|
||||
DeviceDriver::VNASettings s = {};
|
||||
s.dBmStart = info.Limits.VNA.maxdBm;
|
||||
s.dBmStop = info.Limits.VNA.maxdBm;
|
||||
|
||||
auto freq = info.Limits.VNA.minFreq == 0 ? 100000 : info.Limits.VNA.minFreq;
|
||||
|
||||
s.freqStart = freq;
|
||||
s.freqStop = freq;
|
||||
s.IFBW = 100;
|
||||
s.logSweep = false;
|
||||
s.points = 1;
|
||||
|
||||
for(unsigned int i=1;i<=info.Limits.VNA.ports;i++) {
|
||||
s.excitedPorts.push_back(i);
|
||||
}
|
||||
driver->setVNA(s, [=](bool){
|
||||
connect(driver, &DeviceDriver::VNAmeasurementReceived, this, &LibreCALDialog::handleIncomingMeasurement, Qt::DirectConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void LibreCALDialog::stopSweep()
|
||||
{
|
||||
disconnect(driver, &DeviceDriver::VNAmeasurementReceived, this, &LibreCALDialog::handleIncomingMeasurement);
|
||||
driver->setIdle();
|
||||
}
|
||||
|
||||
void LibreCALDialog::startCalibration()
|
||||
{
|
||||
disableUI();
|
||||
busy = true;
|
||||
|
||||
ui->progressCal->setValue(0);
|
||||
ui->lCalibrationStatus->setText("Creating calibration kit from coefficients...");
|
||||
ui->lCalibrationStatus->setStyleSheet("");
|
||||
auto& kit = cal->getKit();
|
||||
kit.clearStandards();
|
||||
kit.manufacturer = "LibreCAL ("+coeffSet.name+")";
|
||||
kit.serialnumber = device->serial();
|
||||
kit.description = "Automatically created from LibreCAL module";
|
||||
|
||||
// determine the used ports of the LibreCAL
|
||||
QList<int> usedPorts;
|
||||
for(auto port : portAssignment) {
|
||||
if(port > 0) {
|
||||
usedPorts.append(port);
|
||||
}
|
||||
}
|
||||
std::sort(usedPorts.begin(), usedPorts.end());
|
||||
|
||||
std::map<int, CalStandard::Virtual*> openStandards;
|
||||
std::map<int, CalStandard::Virtual*> shortStandards;
|
||||
std::map<int, CalStandard::Virtual*> loadStandards;
|
||||
std::map<int, CalStandard::Virtual*> throughStandards;
|
||||
for(unsigned int idx=0;idx<usedPorts.size();idx++) {
|
||||
int i=usedPorts[idx];
|
||||
if(coeffSet.getOpen(i)->t.points() > 0) {
|
||||
auto o = new CalStandard::Open();
|
||||
o->setName("Port "+QString::number(i));
|
||||
o->setMeasurement(coeffSet.getOpen(i)->t);
|
||||
openStandards[i] = o;
|
||||
kit.addStandard(o);
|
||||
}
|
||||
if(coeffSet.getShort(i)->t.points() > 0) {
|
||||
auto o = new CalStandard::Short();
|
||||
o->setName("Port "+QString::number(i));
|
||||
o->setMeasurement(coeffSet.getShort(i)->t);
|
||||
shortStandards[i] = o;
|
||||
kit.addStandard(o);
|
||||
}
|
||||
if(coeffSet.getLoad(i)->t.points() > 0) {
|
||||
auto o = new CalStandard::Load();
|
||||
o->setName("Port "+QString::number(i));
|
||||
o->setMeasurement(coeffSet.getLoad(i)->t);
|
||||
loadStandards[i] = o;
|
||||
kit.addStandard(o);
|
||||
}
|
||||
for(unsigned int jdx=idx+1;jdx<usedPorts.size();jdx++) {
|
||||
int j=usedPorts[jdx];
|
||||
auto c = coeffSet.getThrough(i,j);
|
||||
if(!c) {
|
||||
continue;
|
||||
}
|
||||
if(c->t.points() > 0) {
|
||||
auto o = new CalStandard::Through();
|
||||
o->setName("Port "+QString::number(i)+" to "+QString::number(j));
|
||||
o->setMeasurement(c->t);
|
||||
throughStandards[coeffSet.portsToThroughIndex(i, j)] = o;
|
||||
kit.addStandard(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
ui->lCalibrationStatus->setText("Creating calibration measurements...");
|
||||
cal->reset();
|
||||
auto vnaPorts = DeviceDriver::getInfo(DeviceDriver::getActiveDriver()).Limits.VNA.ports;
|
||||
set<CalibrationMeasurement::Base*> openMeasurements;
|
||||
set<CalibrationMeasurement::Base*> shortMeasurements;
|
||||
set<CalibrationMeasurement::Base*> loadMeasurements;
|
||||
vector<CalibrationMeasurement::TwoPort*> throughMeasurements;
|
||||
for(unsigned int p=0;p<vnaPorts;p++) {
|
||||
if(portAssignment[p] == 0) {
|
||||
continue;
|
||||
}
|
||||
// Create SOL measurements with correct port of calkit
|
||||
auto open = new CalibrationMeasurement::Open(cal);
|
||||
open->setPort(p+1);
|
||||
open->setStandard(openStandards[portAssignment[p]]);
|
||||
openMeasurements.insert(open);
|
||||
cal->measurements.push_back(open);
|
||||
|
||||
auto _short = new CalibrationMeasurement::Short(cal);
|
||||
_short->setPort(p+1);
|
||||
_short->setStandard(shortStandards[portAssignment[p]]);
|
||||
shortMeasurements.insert(_short);
|
||||
cal->measurements.push_back(_short);
|
||||
|
||||
auto load = new CalibrationMeasurement::Load(cal);
|
||||
load->setPort(p+1);
|
||||
load->setStandard(loadStandards[portAssignment[p]]);
|
||||
loadMeasurements.insert(load);
|
||||
cal->measurements.push_back(load);
|
||||
for(unsigned int p2=p+1;p2<vnaPorts;p2++) {
|
||||
if(portAssignment[p2] == 0) {
|
||||
continue;
|
||||
}
|
||||
auto through = new CalibrationMeasurement::Through(cal);
|
||||
through->setPort1(p+1);
|
||||
through->setPort2(p2+1);
|
||||
// find correct through standard
|
||||
int libreCALp1 = portAssignment[p];
|
||||
int libreCALp2 = portAssignment[p2];
|
||||
QString forwardName = "Port "+QString::number(libreCALp1)+" to "+QString::number(libreCALp2);
|
||||
QString reverseName = "Port "+QString::number(libreCALp2)+" to "+QString::number(libreCALp1);
|
||||
for(auto ts : throughStandards) {
|
||||
if(ts.second->getName() == forwardName) {
|
||||
through->setStandard(ts.second);
|
||||
through->setReverseStandard(false);
|
||||
} else if(ts.second->getName() == reverseName) {
|
||||
through->setStandard(ts.second);
|
||||
through->setReverseStandard(true);
|
||||
}
|
||||
}
|
||||
throughMeasurements.push_back(through);
|
||||
cal->measurements.push_back(through);
|
||||
}
|
||||
}
|
||||
|
||||
ui->lCalibrationStatus->setText("Taking calibration measurements...");
|
||||
|
||||
measurementsTaken = 0;
|
||||
|
||||
auto setTerminationOnAllUsedPorts = [=](CalDevice::Standard s) {
|
||||
for(auto p : portAssignment) {
|
||||
if(p > 0) {
|
||||
device->setStandard(p, s);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto startNextCalibrationStep = [=]() {
|
||||
// indicate calibration percentage
|
||||
auto totalMeasurements = 3 + throughMeasurements.size();
|
||||
ui->progressCal->setValue(measurementsTaken * 100 / totalMeasurements);
|
||||
switch(measurementsTaken) {
|
||||
case 0:
|
||||
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::Open));
|
||||
emit cal->startMeasurements(openMeasurements);
|
||||
break;
|
||||
case 1:
|
||||
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::Short));
|
||||
emit cal->startMeasurements(shortMeasurements);
|
||||
break;
|
||||
case 2:
|
||||
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::Load));
|
||||
emit cal->startMeasurements(loadMeasurements);
|
||||
break;
|
||||
default: {
|
||||
// into through measurements now
|
||||
unsigned int throughIndex = measurementsTaken - 3;
|
||||
if(throughIndex >= throughMeasurements.size()) {
|
||||
// this was the last measurement
|
||||
// Try to apply the calibration
|
||||
Calibration::CalType type;
|
||||
type.type = Calibration::Type::SOLT;
|
||||
for(unsigned int i=0;i<vnaPorts;i++) {
|
||||
if(portAssignment[i] > 0) {
|
||||
// this VNA port was used in the calibration
|
||||
type.usedPorts.push_back(i+1);
|
||||
}
|
||||
}
|
||||
auto res = cal->compute(type);
|
||||
if(res) {
|
||||
ui->progressCal->setValue(100);
|
||||
ui->lCalibrationStatus->setText("Calibration activated.");
|
||||
} else {
|
||||
ui->progressCal->setValue(0);
|
||||
ui->lCalibrationStatus->setText("Failed to activate calibration.");
|
||||
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
|
||||
}
|
||||
// sever connection to this function
|
||||
disconnect(cal, &Calibration::measurementsUpdated, this, nullptr);
|
||||
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None));
|
||||
enableUI();
|
||||
busy = false;
|
||||
break;
|
||||
}
|
||||
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None));
|
||||
auto m = throughMeasurements[throughIndex];
|
||||
device->setStandard(portAssignment[m->getPort1()-1], CalDevice::Standard(portAssignment[m->getPort2()-1]));
|
||||
emit cal->startMeasurements({m});
|
||||
}
|
||||
break;
|
||||
}
|
||||
measurementsTaken++;
|
||||
};
|
||||
|
||||
disconnect(cal, &Calibration::measurementsUpdated, this, nullptr);
|
||||
connect(cal, &Calibration::measurementsUpdated, this, startNextCalibrationStep, Qt::QueuedConnection);
|
||||
connect(cal, &Calibration::measurementsAborted, this, [=](){
|
||||
setTerminationOnAllUsedPorts(CalDevice::Standard(CalDevice::Standard::Type::None));
|
||||
enableUI();
|
||||
busy = false;
|
||||
ui->lCalibrationStatus->setText("Ready to start");
|
||||
});
|
||||
|
||||
startNextCalibrationStep();
|
||||
}
|
||||
|
||||
void LibreCALDialog::disableUI()
|
||||
{
|
||||
ui->cbDevice->setEnabled(false);
|
||||
ui->cbCoefficients->setEnabled(false);
|
||||
ui->start->setEnabled(false);
|
||||
for(auto cb : portAssignmentComboBoxes) {
|
||||
cb->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void LibreCALDialog::enableUI()
|
||||
{
|
||||
ui->cbDevice->setEnabled(true);
|
||||
ui->cbCoefficients->setEnabled(true);
|
||||
ui->start->setEnabled(true);
|
||||
for(auto cb : portAssignmentComboBoxes) {
|
||||
cb->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void LibreCALDialog::createPortAssignmentUI()
|
||||
{
|
||||
auto layout = static_cast<QFormLayout*>(ui->assignmentBox->layout());
|
||||
// Clear any possible previous elements
|
||||
portAssignment.clear();
|
||||
portAssignmentComboBoxes.clear();
|
||||
while(layout->rowCount() > 1) {
|
||||
layout->removeRow(1);
|
||||
}
|
||||
auto vnaPorts = DeviceDriver::getInfo(DeviceDriver::getActiveDriver()).Limits.VNA.ports;
|
||||
portAssignment.resize(vnaPorts, 0);
|
||||
auto calPorts = 0;
|
||||
if(device) {
|
||||
calPorts = device->getNumPorts();
|
||||
}
|
||||
QStringList choices = {"Unused", "Auto"};
|
||||
for(int i=1;i<=calPorts;i++) {
|
||||
choices.push_back("Port "+QString::number(i));
|
||||
}
|
||||
for(unsigned int p = 1;p<=vnaPorts;p++) {
|
||||
auto label = new QLabel("Port "+QString::number(p)+":");
|
||||
auto comboBox = new QComboBox();
|
||||
comboBox->addItems(choices);
|
||||
connect(comboBox, qOverload<int>(&QComboBox::currentIndexChanged), [=](){
|
||||
if(comboBox->currentText().startsWith("Unused")) {
|
||||
portAssignment[p-1] = 0;
|
||||
} else if(comboBox->currentText().startsWith("Auto")) {
|
||||
portAssignment[p-1] = -1;
|
||||
} else if(comboBox->currentText().startsWith("Port")) {
|
||||
portAssignment[p-1] = comboBox->currentText().back().digitValue();
|
||||
}
|
||||
emit portAssignmentChanged();
|
||||
});
|
||||
comboBox->setCurrentIndex(1);
|
||||
layout->addRow(label, comboBox);
|
||||
portAssignmentComboBoxes.push_back(comboBox);
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
#ifndef LIBRECALDIALOG_H
|
||||
#define LIBRECALDIALOG_H
|
||||
|
||||
#include "Calibration/calibration.h"
|
||||
#include "caldevice.h"
|
||||
#include "Device/devicedriver.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTimer>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace Ui {
|
||||
class LibreCALDialog;
|
||||
}
|
||||
|
||||
class LibreCALDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LibreCALDialog(Calibration *cal);
|
||||
~LibreCALDialog();
|
||||
|
||||
private:
|
||||
signals:
|
||||
void portAssignmentChanged();
|
||||
void autoPortComplete();
|
||||
private slots:
|
||||
bool validatePortSelection(bool autoAllowed);
|
||||
bool validateCoefficients();
|
||||
void updateCalibrationStartStatus();
|
||||
void updateDeviceStatus();
|
||||
void determineAutoPorts();
|
||||
void loadCoefficients();
|
||||
void startCalibration();
|
||||
|
||||
// auto port slots
|
||||
void handleIncomingMeasurement(DeviceDriver::VNAMeasurement m);
|
||||
void startSweep();
|
||||
void stopSweep();
|
||||
private:
|
||||
void disableUI();
|
||||
void enableUI();
|
||||
void createPortAssignmentUI();
|
||||
Ui::LibreCALDialog *ui;
|
||||
Calibration *cal;
|
||||
CalDevice *device;
|
||||
CalDevice::CoefficientSet coeffSet;
|
||||
QTimer updateTimer;
|
||||
bool busy;
|
||||
// 0 for unused port, -1 for auto port, otherwise the port number
|
||||
std::vector<int> portAssignment;
|
||||
std::vector<QComboBox*> portAssignmentComboBoxes;
|
||||
std::vector<DeviceDriver::VNAMeasurement> autoPortMeasurements;
|
||||
DeviceDriver *driver;
|
||||
|
||||
int measurementsTaken;
|
||||
};
|
||||
|
||||
#endif // LIBRECALDIALOG_H
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LibreCALDialog</class>
|
||||
<widget class="QDialog" name="LibreCALDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>399</width>
|
||||
<height>405</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Electronic Calibration Dialog</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Device Selection</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Device:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="cbDevice"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lDeviceStatus">
|
||||
<property name="text">
|
||||
<string>DEVICE_STATUS_PLACEHOLDER</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Coefficients:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="cbCoefficients"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="assignmentBox">
|
||||
<property name="title">
|
||||
<string>Port Assignments</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>LibreVNA</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>LibreCAL</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Calibration</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="start">
|
||||
<property name="text">
|
||||
<string>Start</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="media-playback-start">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Coefficients:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressCoeff">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Calibration:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressCal">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lCalibrationStatus">
|
||||
<property name="text">
|
||||
<string>CALIBRATION_STATUS_PLACEHOLDER</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
#include "usbdevice.h"
|
||||
|
||||
#include "CustomWidgets/informationbox.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <QMessageBox>
|
||||
#include <QDateTime>
|
||||
#include <mutex>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using USBID = struct {
|
||||
int VID;
|
||||
int PID;
|
||||
};
|
||||
static constexpr USBID IDs[] = {
|
||||
{0x0483, 0x4122},
|
||||
{0x1209, 0x4122},
|
||||
};
|
||||
|
||||
USBDevice::USBDevice(QString serial)
|
||||
{
|
||||
m_handle = nullptr;
|
||||
libusb_init(&m_context);
|
||||
#if LIBUSB_API_VERSION >= 0x01000106
|
||||
libusb_set_option(m_context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
|
||||
#endif
|
||||
|
||||
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, false);
|
||||
|
||||
if(!m_handle) {
|
||||
QString message = "No device found";
|
||||
if(!serial.isEmpty()) {
|
||||
// only show error message if specific device was requested
|
||||
InformationBox::ShowError("Error opening device", message);
|
||||
}
|
||||
libusb_exit(m_context);
|
||||
throw std::runtime_error(message.toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Found the correct device, now connect
|
||||
/* claim the interface */
|
||||
int ret = libusb_claim_interface(m_handle, 2);
|
||||
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;
|
||||
InformationBox::ShowError("Error opening device", message);
|
||||
libusb_exit(m_context);
|
||||
throw std::runtime_error(message.toStdString());
|
||||
}
|
||||
qInfo() << "USB connection established" << Qt::flush;
|
||||
|
||||
connected = true;
|
||||
m_receiveThread = new std::thread(&USBDevice::USBHandleThread, this);
|
||||
usbBuffer = new USBInBuffer(m_handle, LIBUSB_ENDPOINT_IN | 0x03, 65536);
|
||||
connect(usbBuffer, &USBInBuffer::DataReceived, this, &USBDevice::ReceivedData, Qt::DirectConnection);
|
||||
|
||||
// clear any possible queued data in USB device
|
||||
QString dummy;
|
||||
while(receive(&dummy, 10));
|
||||
}
|
||||
|
||||
USBDevice::~USBDevice()
|
||||
{
|
||||
delete usbBuffer;
|
||||
connected = false;
|
||||
libusb_release_interface(m_handle, 2);
|
||||
libusb_close(m_handle);
|
||||
m_receiveThread->join();
|
||||
libusb_exit(m_context);
|
||||
delete m_receiveThread;
|
||||
}
|
||||
|
||||
bool USBDevice::Cmd(QString cmd, unsigned int timeout)
|
||||
{
|
||||
QString rcv;
|
||||
flushReceived();
|
||||
bool success = send(cmd) && receive(&rcv, timeout);
|
||||
if(success && rcv == "") {
|
||||
// empty response expected by commad
|
||||
return true;
|
||||
} else {
|
||||
// failed to send/receive
|
||||
emit communicationFailure();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString USBDevice::Query(QString query, unsigned int timeout)
|
||||
{
|
||||
flushReceived();
|
||||
if(send(query)) {
|
||||
QString rcv;
|
||||
if(receive(&rcv, timeout)) {
|
||||
return rcv;
|
||||
} else {
|
||||
emit communicationFailure();
|
||||
}
|
||||
} else {
|
||||
emit communicationFailure();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
std::set<QString> USBDevice::GetDevices()
|
||||
{
|
||||
std::set<QString> serials;
|
||||
|
||||
libusb_context *ctx;
|
||||
libusb_init(&ctx);
|
||||
#if LIBUSB_API_VERSION >= 0x01000106
|
||||
libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
|
||||
#endif
|
||||
|
||||
SearchDevices([&serials](libusb_device_handle *, QString serial) -> bool {
|
||||
serials.insert(serial);
|
||||
return true;
|
||||
}, ctx, true);
|
||||
|
||||
libusb_exit(ctx);
|
||||
|
||||
return serials;
|
||||
}
|
||||
|
||||
void USBDevice::SearchDevices(std::function<bool (libusb_device_handle *, QString)> foundCallback, libusb_context *context, bool ignoreOpenError)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
bool correctID = false;
|
||||
int numIDs = sizeof(IDs)/sizeof(IDs[0]);
|
||||
for(int i=0;i<numIDs;i++) {
|
||||
if(desc.idVendor == IDs[i].VID && desc.idProduct == IDs[i].PID) {
|
||||
correctID = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!correctID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to open the device */
|
||||
libusb_device_handle *handle = nullptr;
|
||||
ret = libusb_open(device, &handle);
|
||||
if (ret) {
|
||||
qDebug() << libusb_strerror((enum libusb_error) ret);
|
||||
/* Failed to open */
|
||||
if(!ignoreOpenError) {
|
||||
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 this most likely means that you are already connected to "
|
||||
"this device (is another instance of the application already runnning?)");
|
||||
qWarning() << message;
|
||||
InformationBox::ShowError("Error opening device", message);
|
||||
}
|
||||
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);
|
||||
if (product == "LibreCAL") {
|
||||
// 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);
|
||||
}
|
||||
|
||||
bool USBDevice::send(const QString &s)
|
||||
{
|
||||
lineBuffer.clear();
|
||||
qDebug() << "USB send:" << s;
|
||||
unsigned char data[s.size()+2];
|
||||
memcpy(data, s.toLatin1().data(), s.size());
|
||||
memcpy(&data[s.size()], "\r\n", 2);
|
||||
int actual;
|
||||
auto r = libusb_bulk_transfer(m_handle, LIBUSB_ENDPOINT_OUT | 0x03, data, s.size() + 2, &actual, 0);
|
||||
if(r == 0 && actual == s.size() + 2) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool USBDevice::receive(QString *s, unsigned int timeout)
|
||||
{
|
||||
// check if we already have a line queued
|
||||
unique_lock<mutex> lck(mtx);
|
||||
while(lineBuffer.size() == 0) {
|
||||
// needs to wait for an incoming line
|
||||
using namespace std::chrono_literals;
|
||||
if(cv.wait_for(lck, std::chrono::milliseconds(timeout)) == cv_status::timeout) {
|
||||
qWarning() << "Timed out while waiting for received line";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*s = lineBuffer.takeFirst();
|
||||
qDebug() << "USB recv:" << *s;
|
||||
return true;
|
||||
}
|
||||
|
||||
void USBDevice::ReceivedData()
|
||||
{
|
||||
uint16_t handled_len;
|
||||
unique_lock<mutex> lck(mtx);
|
||||
do {
|
||||
handled_len = 0;
|
||||
auto firstLinebreak = (uint8_t*) memchr(usbBuffer->getBuffer(), '\n', usbBuffer->getReceived());
|
||||
if(firstLinebreak) {
|
||||
handled_len = firstLinebreak - usbBuffer->getBuffer();
|
||||
auto line = QString::fromLatin1((const char*) usbBuffer->getBuffer(), handled_len - 1);
|
||||
|
||||
// add received line to buffer
|
||||
lineBuffer.append(line);
|
||||
|
||||
usbBuffer->removeBytes(handled_len + 1);
|
||||
}
|
||||
} while(handled_len > 0);
|
||||
if(lineBuffer.size() > 0) {
|
||||
cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void USBDevice::flushReceived()
|
||||
{
|
||||
unique_lock<mutex> lck(mtx);
|
||||
lineBuffer.clear();
|
||||
}
|
||||
|
||||
void USBDevice::USBHandleThread()
|
||||
{
|
||||
qDebug() << "Receive thread started";
|
||||
while (connected) {
|
||||
libusb_handle_events(m_context);
|
||||
}
|
||||
qDebug() << "Disconnected, receive thread exiting";
|
||||
}
|
||||
|
||||
QString USBDevice::serial() const
|
||||
{
|
||||
return m_serial;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
#ifndef USBDEVICE_H
|
||||
#define USBDEVICE_H
|
||||
|
||||
#include "Util/usbinbuffer.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#ifdef Q_OS_MACOS
|
||||
#include <libusb.h>
|
||||
#else
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#endif
|
||||
|
||||
#include <QString>
|
||||
#include <set>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class USBDevice : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// connect to a CAL device. If serial is specified only connecting to this device, otherwise to the first one found
|
||||
USBDevice(QString serial = QString());
|
||||
~USBDevice();
|
||||
|
||||
bool Cmd(QString cmd, unsigned int timeout = 2000);
|
||||
QString Query(QString query, unsigned int timeout = 2000);
|
||||
QString serial() const;
|
||||
|
||||
// Returns serial numbers of all connected devices
|
||||
static std::set<QString> GetDevices();
|
||||
|
||||
bool send(const QString &s);
|
||||
bool receive(QString *s, unsigned int timeout = 2000);
|
||||
void flushReceived();
|
||||
signals:
|
||||
void communicationFailure();
|
||||
|
||||
private slots:
|
||||
void ReceivedData();
|
||||
private:
|
||||
void USBHandleThread();
|
||||
bool connected;
|
||||
std::thread *m_receiveThread;
|
||||
|
||||
static void SearchDevices(std::function<bool (libusb_device_handle *, QString)> foundCallback, libusb_context *context, bool ignoreOpenError);
|
||||
libusb_device_handle *m_handle;
|
||||
libusb_context *m_context;
|
||||
|
||||
USBInBuffer *usbBuffer;
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
QStringList lineBuffer;
|
||||
|
||||
QString m_serial;
|
||||
};
|
||||
|
||||
#endif // DEVICE_H
|
||||
+2229
File diff suppressed because it is too large
Load Diff
+186
@@ -0,0 +1,186 @@
|
||||
#ifndef CALIBRATION2_H
|
||||
#define CALIBRATION2_H
|
||||
|
||||
#include "savable.h"
|
||||
#include "calibrationmeasurement.h"
|
||||
#include "calkit.h"
|
||||
#include "Traces/trace.h"
|
||||
#include "scpi.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
class Calibration : public QObject, public Savable, public SCPINode
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class LibreCALDialog;
|
||||
friend class CalibrationTests;
|
||||
public:
|
||||
Calibration();
|
||||
|
||||
enum class Type {
|
||||
None,
|
||||
OSL,
|
||||
SOLT,
|
||||
SOLTwithoutRxMatch,
|
||||
ThroughNormalization,
|
||||
TRL,
|
||||
Last,
|
||||
};
|
||||
class CalType {
|
||||
public:
|
||||
Type type;
|
||||
std::vector<unsigned int> usedPorts; // port count starts at 1
|
||||
QString getReadableDescription();
|
||||
QString getShortString();
|
||||
|
||||
static CalType fromShortString(QString s);
|
||||
|
||||
friend bool operator==(const CalType &lhs, const CalType &rhs);
|
||||
};
|
||||
|
||||
static QString TypeToString(Type type);
|
||||
static Type TypeFromString(QString s);
|
||||
|
||||
// Applies calculated calibration coefficients to measurement data
|
||||
void correctMeasurement(DeviceDriver::VNAMeasurement &d);
|
||||
void correctTraces(std::map<QString, Trace*> traceSet);
|
||||
|
||||
// Starts the calibration edit dialog, allowing the user to make/delete measurements
|
||||
void edit(TraceModel *traceModel = nullptr);
|
||||
|
||||
Calkit& getKit();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
bool toFile(QString filename = QString());
|
||||
bool fromFile(QString filename = QString());
|
||||
|
||||
// Returns all possible calibration types/port permutations for the currently connected device.
|
||||
// If no device is connected, a two-port device is assumed
|
||||
static std::vector<CalType> getAvailableCalibrations();
|
||||
|
||||
// Returns vector of all calibration types (without 'Last')
|
||||
static std::vector<Type> getTypes();
|
||||
// Checks whether all measurements for a specific calibration are available.
|
||||
// If pointer to the frequency/points variables are given, the start/stop frequency and number of points the calibration will have after the calculation is stored there
|
||||
bool canCompute(CalType type, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr, bool *isLog = nullptr);
|
||||
// Resets the calibration (deletes all measurements and calculated coefficients)
|
||||
void reset();
|
||||
// Returns the minimum number of ports for a given calibration type.
|
||||
// E.g. the SOL(T) calibration can work with only one port, a through normalization requires at least two
|
||||
static int minimumPorts(Type type);
|
||||
|
||||
// Adds a new measurement point (data) to all calibration measurements (m)
|
||||
void addMeasurements(std::set<CalibrationMeasurement::Base*> m, const DeviceDriver::VNAMeasurement &data);
|
||||
// Deletes all datapoints in the calibration measurements (m)
|
||||
void clearMeasurements(std::set<CalibrationMeasurement::Base*> m);
|
||||
CalType getCaltype() const;
|
||||
|
||||
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(double f_start, double f_stop, int npoints);
|
||||
|
||||
std::vector<Trace*> getErrorTermTraces();
|
||||
std::vector<Trace*> getMeasurementTraces();
|
||||
|
||||
QString getCurrentCalibrationFile();
|
||||
double getMinFreq();
|
||||
double getMaxFreq();
|
||||
|
||||
int getNumPoints();
|
||||
bool hasUnsavedChanges() const;
|
||||
|
||||
QString getValidDevice() const;
|
||||
bool validForDevice(QString serial) const;
|
||||
|
||||
// query whether error terms coefficients are available. Port count starts at 1
|
||||
bool hasDirectivity(unsigned int port);
|
||||
bool hasReflectionTracking(unsigned int port);
|
||||
bool hasSourceMatch(unsigned int port);
|
||||
bool hasReceiverMatch(unsigned int sourcePort, unsigned int receivePort);
|
||||
bool hasTransmissionTracking(unsigned int sourcePort, unsigned int receivePort);
|
||||
bool hasIsolation(unsigned int sourcePort, unsigned int receivePort);
|
||||
|
||||
public slots:
|
||||
// Call once all datapoints of the current span have been added
|
||||
void measurementsComplete();
|
||||
// Call once when a measurement is aborted before all points have been captured
|
||||
void measurementsAbort();
|
||||
// Attempts to calculate the calibration coefficients. If not enough measurements are available, false is returned and the currently used coefficients are not changed
|
||||
bool compute(CalType type);
|
||||
// Deactivates the calibration, resets the calibration coefficients. Calibration measurements are NOT deleted.
|
||||
void deactivate();
|
||||
signals:
|
||||
// emitted when the measurement of a set of calibration measurements should be started
|
||||
void startMeasurements(std::set<CalibrationMeasurement::Base*> m);
|
||||
// emitted whenever a measurement is complete (triggered by calling measurementsComplete())
|
||||
void measurementsUpdated();
|
||||
// emitted when taking a calibration measurement is aborted
|
||||
void measurementsAborted();
|
||||
// emitted when calibration coefficients were calculated/updated successfully
|
||||
void activated(CalType type);
|
||||
// emitted when the calibrationo coefficients were reset
|
||||
void deactivated();
|
||||
private:
|
||||
enum class DefaultMeasurements {
|
||||
SOL1Port,
|
||||
SOLT2Port,
|
||||
SOLT3Port,
|
||||
SOLT4Port,
|
||||
Last
|
||||
};
|
||||
static QString DefaultMeasurementsToString(DefaultMeasurements dm);
|
||||
void createDefaultMeasurements(DefaultMeasurements dm);
|
||||
void deleteMeasurements();
|
||||
|
||||
static bool hasFrequencyOverlap(std::vector<CalibrationMeasurement::Base*> m, double *startFreq = nullptr, double *stopFreq = nullptr, int *points = nullptr, bool *isLog = nullptr);
|
||||
// returns all measurements that match the paramaters
|
||||
std::vector<CalibrationMeasurement::Base*> findMeasurements(CalibrationMeasurement::Base::Type type, int port1 = 0, int port2 = 0);
|
||||
// returns the first measurement in the list that matches the parameters
|
||||
CalibrationMeasurement::Base* findMeasurement(CalibrationMeasurement::Base::Type type, int port1 = 0, int port2 = 0);
|
||||
CalibrationMeasurement::Base *newMeasurement(CalibrationMeasurement::Base::Type type);
|
||||
|
||||
class Point {
|
||||
public:
|
||||
double frequency;
|
||||
std::vector<std::complex<double>> D; // Directivity
|
||||
std::vector<std::complex<double>> R; // Reflection tracking
|
||||
std::vector<std::complex<double>> S; // Source Match
|
||||
std::vector<std::vector<std::complex<double>>> L; // Receiver Match
|
||||
std::vector<std::vector<std::complex<double>>> T; // Transmission tracking
|
||||
std::vector<std::vector<std::complex<double>>> I; // Transmission isolation
|
||||
Point interpolate(const Point &to, double alpha);
|
||||
};
|
||||
std::vector<Point> points;
|
||||
|
||||
Point createInitializedPoint(double f);
|
||||
Point computeOSL(double f);
|
||||
Point computeSOLT(double f);
|
||||
Point computeSOLTwithoutRxMatch(double f);
|
||||
Point computeThroughNormalization(double f);
|
||||
Point computeTRL(double f);
|
||||
|
||||
std::vector<CalibrationMeasurement::Base*> measurements;
|
||||
|
||||
Calkit kit;
|
||||
CalType caltype;
|
||||
|
||||
QString descriptiveCalName();
|
||||
QString currentCalFile;
|
||||
|
||||
QString validDevice;
|
||||
|
||||
bool unsavedChanges;
|
||||
|
||||
std::recursive_mutex access;
|
||||
};
|
||||
|
||||
#endif // CALIBRATION2_H
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalibrationDialog</class>
|
||||
<widget class="QDialog" name="CalibrationDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1119</width>
|
||||
<height>650</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Calibration Measurements</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Measurements</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<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>Create default measurements for:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="createDefault"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="table">
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="bAdd">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add" resource="../icons.qrc">
|
||||
<normaloff>:/icons/add.png</normaloff>:/icons/add.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bDelete">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-remove" resource="../icons.qrc">
|
||||
<normaloff>:/icons/remove.png</normaloff>:/icons/remove.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bMoveUp">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Move up</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-up" resource="../icons.qrc">
|
||||
<normaloff>:/icons/up.png</normaloff>:/icons/up.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bMoveDown">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Move down</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-down" resource="../icons.qrc">
|
||||
<normaloff>:/icons/down.png</normaloff>:/icons/down.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="measure">
|
||||
<property name="text">
|
||||
<string>Measure</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="media-playback-start" resource="../icons.qrc">
|
||||
<normaloff>:/icons/play.png</normaloff>:/icons/play.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectMeasurement">
|
||||
<property name="text">
|
||||
<string>Select
|
||||
Measurement</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clearMeasurement">
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-delete" resource="../icons.qrc">
|
||||
<normaloff>:/icons/trash.png</normaloff>:/icons/trash.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="eCal">
|
||||
<property name="text">
|
||||
<string>Electronic
|
||||
Calibration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="editCalkit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Edit
|
||||
Calibration
|
||||
Kit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>18</width>
|
||||
<height>186</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Calibration</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Available calibrations:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="calibrationList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Active calibration:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="activeCalibration">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Minimum frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="calMinFreq">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Maximum frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="calMaxFreq">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Points:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="calPoints">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="deactivate">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Deactivate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="activate">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Activate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
+729
@@ -0,0 +1,729 @@
|
||||
#include "calibrationmeasurement.h"
|
||||
#include "unit.h"
|
||||
#include "calibration.h"
|
||||
#include "Util/util.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QCheckBox>
|
||||
|
||||
using namespace std;
|
||||
|
||||
CalibrationMeasurement::Base::Base(Calibration *cal)
|
||||
: cal(cal)
|
||||
{
|
||||
standard = nullptr;
|
||||
timestamp = QDateTime();
|
||||
}
|
||||
|
||||
std::vector<CalStandard::Virtual *> CalibrationMeasurement::Base::supportedStandards()
|
||||
{
|
||||
vector<CalStandard::Virtual*> ret;
|
||||
for(auto s : cal->getKit().getStandards()) {
|
||||
if(supportedStandardTypes().count(s->getType())) {
|
||||
ret.push_back(s);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CalibrationMeasurement::Base::setFirstSupportedStandard()
|
||||
{
|
||||
// assign first valid standard
|
||||
auto supported = supportedStandards();
|
||||
if(supported.size() > 0) {
|
||||
setStandard(supported[0]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CalibrationMeasurement::Base::setStandard(CalStandard::Virtual *standard)
|
||||
{
|
||||
if(standard) {
|
||||
if(supportedStandardTypes().count(standard->getType())) {
|
||||
// can use this standard
|
||||
this->standard = standard;
|
||||
connect(standard, &CalStandard::Virtual::deleted, this, [=](){
|
||||
setStandard(nullptr);
|
||||
});
|
||||
emit standardChanged(standard);
|
||||
return true;
|
||||
} else {
|
||||
// can't use this standard, leave unchanged
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// nullptr passed, remove currently used standard
|
||||
if(this->standard) {
|
||||
disconnect(this->standard, &CalStandard::Virtual::deleted, this, nullptr);
|
||||
}
|
||||
this->standard = nullptr;
|
||||
emit standardChanged(nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
QTableWidgetItem *CalibrationMeasurement::Base::getStatisticsItem()
|
||||
{
|
||||
return new QTableWidgetItem(getStatistics());
|
||||
}
|
||||
|
||||
QString CalibrationMeasurement::Base::getStatistics()
|
||||
{
|
||||
if(numPoints() > 0) {
|
||||
QString data = QString::number(numPoints());
|
||||
data.append(" points from ");
|
||||
data.append(Unit::ToString(minFreq(), "Hz", " kMG"));
|
||||
data.append(" to ");
|
||||
data.append(Unit::ToString(maxFreq(), "Hz", " kMG"));
|
||||
return data;
|
||||
} else {
|
||||
return "Not available";
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CalibrationMeasurement::Base::Type> CalibrationMeasurement::Base::availableTypes()
|
||||
{
|
||||
std::vector<Type> ret;
|
||||
for(int i=0;i<(int) Type::Last;i++) {
|
||||
ret.push_back((Type) i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString CalibrationMeasurement::Base::TypeToString(CalibrationMeasurement::Base::Type type)
|
||||
{
|
||||
switch(type) {
|
||||
case Type::Open: return "Open";
|
||||
case Type::Short: return "Short";
|
||||
case Type::Load: return "Load";
|
||||
case Type::SlidingLoad: return "SlidingLoad";
|
||||
case Type::Reflect: return "Reflect";
|
||||
case Type::Through: return "Through";
|
||||
case Type::Isolation: return "Isolation";
|
||||
case Type::Line: return "Line";
|
||||
case Type::Last: return "Invalid";
|
||||
}
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
CalibrationMeasurement::Base::Type CalibrationMeasurement::Base::TypeFromString(QString s)
|
||||
{
|
||||
for(int i=0;i<(int) Type::Last;i++) {
|
||||
if(TypeToString((Type) i).compare(s, Qt::CaseInsensitive) == 0) {
|
||||
return (Type) i;
|
||||
}
|
||||
}
|
||||
return Type::Last;
|
||||
}
|
||||
|
||||
QWidget *CalibrationMeasurement::Base::createStandardWidget()
|
||||
{
|
||||
auto cbStandard = new QComboBox();
|
||||
for(auto s : supportedStandards()) {
|
||||
cbStandard->addItem(s->getDescription(), QVariant::fromValue((void*) s));
|
||||
if(standard == s) {
|
||||
cbStandard->setCurrentText(s->getDescription());
|
||||
}
|
||||
}
|
||||
if(standard == 0 && cbStandard->count() > 0) {
|
||||
// no standard was selected but no there is one available
|
||||
setStandard((CalStandard::Virtual*) cbStandard->itemData(0, Qt::UserRole).value<void*>());
|
||||
}
|
||||
|
||||
connect(cbStandard, qOverload<int>(&QComboBox::currentIndexChanged), [=](){
|
||||
auto s = (CalStandard::Virtual*) cbStandard->itemData(cbStandard->currentIndex(), Qt::UserRole).value<void*>();
|
||||
setStandard(s);
|
||||
});
|
||||
connect(this, &CalibrationMeasurement::Base::standardChanged, cbStandard, [=](){
|
||||
for(int i=0;i<cbStandard->count();i++) {
|
||||
if((CalStandard::Virtual*) cbStandard->itemData(i, Qt::UserRole).value<void*>() == standard) {
|
||||
cbStandard->setCurrentIndex(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
return cbStandard;
|
||||
}
|
||||
|
||||
nlohmann::json CalibrationMeasurement::Base::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
if(standard) {
|
||||
j["standard"] = standard->getID();
|
||||
}
|
||||
j["timestamp"] = timestamp.toSecsSinceEpoch();
|
||||
return j;
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::Base::fromJSON(nlohmann::json j)
|
||||
{
|
||||
if(j.contains("standard")) {
|
||||
auto standards = cal->getKit().getStandards();
|
||||
standard = nullptr;
|
||||
unsigned long long id = j.value("standard", 0ULL);
|
||||
for(auto s : standards) {
|
||||
if(s->getID() == id) {
|
||||
setStandard(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
timestamp = QDateTime::fromSecsSinceEpoch(j.value("timestamp", 0));
|
||||
}
|
||||
|
||||
bool CalibrationMeasurement::Base::canMeasureSimultaneously(std::set<CalibrationMeasurement::Base *> measurements)
|
||||
{
|
||||
std::set<int> usedPorts;
|
||||
for(auto m : measurements) {
|
||||
std::vector<int> ports;
|
||||
switch(m->getType()) {
|
||||
case Type::Open:
|
||||
case Type::Short:
|
||||
case Type::Load:
|
||||
case Type::SlidingLoad:
|
||||
case Type::Reflect:
|
||||
// Uses one port
|
||||
ports.push_back(static_cast<OnePort*>(m)->getPort());
|
||||
break;
|
||||
case Type::Through:
|
||||
case Type::Line:
|
||||
// Uses two ports
|
||||
ports.push_back(static_cast<TwoPort*>(m)->getPort1());
|
||||
ports.push_back(static_cast<TwoPort*>(m)->getPort2());
|
||||
break;
|
||||
case Type::Isolation:
|
||||
// Uses all ports, unable to measure simultaneously
|
||||
if(measurements.size() > 1) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Type::Last:
|
||||
// invalid
|
||||
return false;
|
||||
}
|
||||
for(auto p : ports) {
|
||||
if(usedPorts.count(p)) {
|
||||
// port already used for another measurement
|
||||
return false;
|
||||
} else {
|
||||
usedPorts.insert(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we get here, no port collisions occurred
|
||||
return true;
|
||||
}
|
||||
|
||||
QDateTime CalibrationMeasurement::Base::getTimestamp() const
|
||||
{
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
CalStandard::Virtual* CalibrationMeasurement::Base::getStandard() const
|
||||
{
|
||||
return standard;
|
||||
}
|
||||
|
||||
QTableWidgetItem *CalibrationMeasurement::OnePort::getStatisticsItem()
|
||||
{
|
||||
auto ret = Base::getStatisticsItem();
|
||||
if(numPoints() > 0) {
|
||||
if(!standard) {
|
||||
ret->setBackground(Qt::red);
|
||||
ret->setToolTip("No calibration standard assigned, unable to use this measurement");
|
||||
} else if(standard->minFrequency() > points.front().frequency || standard->maxFrequency() < points.back().frequency) {
|
||||
ret->setBackground(Qt::yellow);
|
||||
ret->setToolTip("Usable frequency range constrained by calibration standard to "+Unit::ToString(minUsableFreq(), "Hz", " kMG", 4)+" - "+Unit::ToString(maxUsableFreq(), "Hz", " kMG", 4));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
double CalibrationMeasurement::OnePort::minUsableFreq()
|
||||
{
|
||||
if(points.size() > 0 && standard) {
|
||||
return max(points.front().frequency, standard->minFrequency());
|
||||
} else {
|
||||
return numeric_limits<double>::max();
|
||||
}
|
||||
}
|
||||
|
||||
double CalibrationMeasurement::OnePort::maxUsableFreq()
|
||||
{
|
||||
if(points.size() > 0 && standard) {
|
||||
return min(points.back().frequency, standard->maxFrequency());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::OnePort::clearPoints()
|
||||
{
|
||||
points.clear();
|
||||
timestamp = QDateTime();
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::OnePort::addPoint(const DeviceDriver::VNAMeasurement &m)
|
||||
{
|
||||
QString measurementName = "S"+QString::number(port)+QString::number(port);
|
||||
if(m.measurements.count(measurementName) > 0) {
|
||||
Point p;
|
||||
p.frequency = m.frequency;
|
||||
p.S = m.measurements.at(measurementName);
|
||||
points.push_back(p);
|
||||
timestamp = QDateTime::currentDateTimeUtc();
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *CalibrationMeasurement::OnePort::createSettingsWidget()
|
||||
{
|
||||
auto label = new QLabel("Port:");
|
||||
auto cbPort = new QComboBox();
|
||||
auto dev = DeviceDriver::getActiveDriver();
|
||||
if(dev) {
|
||||
if(port == 0) {
|
||||
setPort(1);
|
||||
}
|
||||
for(unsigned int i=1;i<=dev->getInfo().Limits.VNA.ports;i++) {
|
||||
cbPort->addItem(QString::number(i));
|
||||
if(port == i) {
|
||||
cbPort->setCurrentText(QString::number(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(port > 0 && cbPort->findText(QString::number(port)) < 0) {
|
||||
// set already selected port, even if device is not connected or does not have this port
|
||||
cbPort->addItem(QString::number(port));
|
||||
cbPort->setCurrentText(QString::number(port));
|
||||
}
|
||||
connect(cbPort, qOverload<int>(&QComboBox::currentIndexChanged), [=](){
|
||||
setPort(cbPort->currentText().toInt());
|
||||
});
|
||||
connect(this, &OnePort::portChanged, cbPort, [=](){
|
||||
auto string = QString::number(port);
|
||||
if(cbPort->findText(string) < 0) {
|
||||
// setting does not exist yet, create (should not happen)
|
||||
cbPort->addItem(string);
|
||||
}
|
||||
cbPort->setCurrentText(string);
|
||||
});
|
||||
auto ret = new QWidget();
|
||||
auto layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(0,0,0,0);
|
||||
layout->addWidget(label);
|
||||
layout->addWidget(cbPort);
|
||||
layout->setStretch(1, 1);
|
||||
ret->setLayout(layout);
|
||||
return ret;
|
||||
}
|
||||
|
||||
nlohmann::json CalibrationMeasurement::OnePort::toJSON()
|
||||
{
|
||||
auto j = Base::toJSON();
|
||||
j["port"] = port;
|
||||
nlohmann::json jpoints;
|
||||
for(auto &p : points) {
|
||||
nlohmann::json jpoint;
|
||||
jpoint["frequency"] = p.frequency;
|
||||
jpoint["real"] = p.S.real();
|
||||
jpoint["imag"] = p.S.imag();
|
||||
jpoints.push_back(jpoint);
|
||||
}
|
||||
j["points"] = jpoints;
|
||||
return j;
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::OnePort::fromJSON(nlohmann::json j)
|
||||
{
|
||||
clearPoints();
|
||||
Base::fromJSON(j);
|
||||
port = j.value("port", 0);
|
||||
if(j.contains("points")) {
|
||||
for(auto jpoint : j["points"]) {
|
||||
Point p;
|
||||
p.frequency = jpoint.value("frequency", 0.0);
|
||||
p.S = complex<double>(jpoint.value("real", 0.0), jpoint.value("imag", 0.0));
|
||||
points.push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::complex<double> CalibrationMeasurement::OnePort::getMeasured(double frequency)
|
||||
{
|
||||
if(points.size() == 0 || frequency < points.front().frequency || frequency > points.back().frequency) {
|
||||
return numeric_limits<complex<double>>::quiet_NaN();
|
||||
}
|
||||
// frequency within points, interpolate
|
||||
Point interp = Util::interpolate<Point, double>(points, frequency, [](const Point &p)->double{
|
||||
return p.frequency;
|
||||
});
|
||||
return interp.S;
|
||||
}
|
||||
|
||||
std::complex<double> CalibrationMeasurement::OnePort::getActual(double frequency)
|
||||
{
|
||||
return static_cast<CalStandard::OnePort*>(standard)->toS11(frequency);
|
||||
}
|
||||
|
||||
int CalibrationMeasurement::OnePort::getPort() const
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::OnePort::setPort(unsigned int p)
|
||||
{
|
||||
if(port != p) {
|
||||
port = p;
|
||||
emit portChanged(p);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CalibrationMeasurement::OnePort::Point> CalibrationMeasurement::OnePort::getPoints() const
|
||||
{
|
||||
return points;
|
||||
}
|
||||
|
||||
QTableWidgetItem *CalibrationMeasurement::TwoPort::getStatisticsItem()
|
||||
{
|
||||
auto ret = Base::getStatisticsItem();
|
||||
if(numPoints() > 0) {
|
||||
if(!standard) {
|
||||
ret->setBackground(Qt::red);
|
||||
ret->setToolTip("No calibration standard assigned, unable to use this measurement");
|
||||
} else if(standard->minFrequency() > points.front().frequency || standard->maxFrequency() < points.back().frequency) {
|
||||
ret->setBackground(Qt::yellow);
|
||||
ret->setToolTip("Usable frequency range constrained by calibration standard to "+Unit::ToString(minUsableFreq(), "Hz", " kMG", 4)+" - "+Unit::ToString(maxUsableFreq(), "Hz", " kMG", 4));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
double CalibrationMeasurement::TwoPort::minUsableFreq()
|
||||
{
|
||||
if(points.size() > 0 && standard) {
|
||||
return max(points.front().frequency, standard->minFrequency());
|
||||
} else {
|
||||
return numeric_limits<double>::max();
|
||||
}
|
||||
}
|
||||
|
||||
double CalibrationMeasurement::TwoPort::maxUsableFreq()
|
||||
{
|
||||
if(points.size() > 0 && standard) {
|
||||
return min(points.back().frequency, standard->maxFrequency());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::TwoPort::clearPoints()
|
||||
{
|
||||
points.clear();
|
||||
timestamp = QDateTime();
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::TwoPort::addPoint(const DeviceDriver::VNAMeasurement &m)
|
||||
{
|
||||
Point p;
|
||||
p.frequency = m.frequency;
|
||||
p.S = m.toSparam().reduceTo({port1, port2});
|
||||
points.push_back(p);
|
||||
timestamp = QDateTime::currentDateTimeUtc();
|
||||
}
|
||||
|
||||
QWidget *CalibrationMeasurement::TwoPort::createSettingsWidget()
|
||||
{
|
||||
auto label1 = new QLabel("From ");
|
||||
auto cbPort1 = new QComboBox();
|
||||
auto label2 = new QLabel(" to ");
|
||||
auto cbPort2 = new QComboBox();
|
||||
auto cbReverse = new QCheckBox("Reversed");
|
||||
cbReverse->setToolTip("Enable this option if the calibration standard is defined with the port order swapped");
|
||||
auto dev = DeviceDriver::getActiveDriver();
|
||||
if(dev) {
|
||||
if(port1 == 0) {
|
||||
setPort1(1);
|
||||
}
|
||||
if(port2 == 0) {
|
||||
setPort2(2);
|
||||
}
|
||||
for(unsigned int i=1;i<=dev->getInfo().Limits.VNA.ports;i++) {
|
||||
cbPort1->addItem(QString::number(i));
|
||||
cbPort2->addItem(QString::number(i));
|
||||
if(port1 == i) {
|
||||
cbPort1->setCurrentText(QString::number(i));
|
||||
}
|
||||
if(port2 == i) {
|
||||
cbPort2->setCurrentText(QString::number(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(port1 > 0 && cbPort1->findText(QString::number(port1)) < 0) {
|
||||
// set already selected port, even if device is not connected or does not have this port
|
||||
cbPort1->addItem(QString::number(port1));
|
||||
cbPort1->setCurrentText(QString::number(port1));
|
||||
}
|
||||
if(port2 > 0 && cbPort2->findText(QString::number(port2)) < 0) {
|
||||
// set already selected port, even if device is not connected or does not have this port
|
||||
cbPort2->addItem(QString::number(port2));
|
||||
cbPort2->setCurrentText(QString::number(port2));
|
||||
}
|
||||
cbReverse->setChecked(reverseStandard);
|
||||
connect(cbPort1, qOverload<int>(&QComboBox::currentIndexChanged), [=](){
|
||||
setPort1(cbPort1->currentText().toInt());
|
||||
});
|
||||
connect(cbPort2, qOverload<int>(&QComboBox::currentIndexChanged), [=](){
|
||||
setPort2(cbPort2->currentText().toInt());
|
||||
});
|
||||
connect(cbReverse, &QCheckBox::toggled, [=](){
|
||||
setReverseStandard(cbReverse->isChecked());
|
||||
});
|
||||
connect(this, &TwoPort::port1Changed, cbPort1, [=](){
|
||||
auto string = QString::number(port1);
|
||||
if(cbPort1->findText(string) < 0) {
|
||||
// setting does not exist yet, create (should not happen)
|
||||
cbPort1->addItem(string);
|
||||
}
|
||||
cbPort1->setCurrentText(string);
|
||||
});
|
||||
connect(this, &TwoPort::port2Changed, cbPort2, [=](){
|
||||
auto string = QString::number(port2);
|
||||
if(cbPort2->findText(string) < 0) {
|
||||
// setting does not exist yet, create (should not happen)
|
||||
cbPort2->addItem(string);
|
||||
}
|
||||
cbPort2->setCurrentText(string);
|
||||
});
|
||||
connect(this, &TwoPort::reverseStandardChanged, cbReverse, [=](){
|
||||
cbReverse->setChecked(reverseStandard);
|
||||
});
|
||||
auto ret = new QWidget();
|
||||
auto layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(0,0,0,0);
|
||||
layout->addWidget(label1);
|
||||
layout->addWidget(cbPort1);
|
||||
layout->addWidget(label2);
|
||||
layout->addWidget(cbPort2);
|
||||
layout->addWidget(cbReverse);
|
||||
layout->setStretch(1, 1);
|
||||
layout->setStretch(3, 1);
|
||||
ret->setLayout(layout);
|
||||
return ret;
|
||||
}
|
||||
|
||||
nlohmann::json CalibrationMeasurement::TwoPort::toJSON()
|
||||
{
|
||||
auto j = Base::toJSON();
|
||||
j["port1"] = port1;
|
||||
j["port2"] = port2;
|
||||
j["reverseStandard"] = reverseStandard;
|
||||
nlohmann::json jpoints;
|
||||
for(auto &p : points) {
|
||||
nlohmann::json jpoint;
|
||||
jpoint["frequency"] = p.frequency;
|
||||
jpoint["Sparam"] = p.S.toJSON();
|
||||
jpoints.push_back(jpoint);
|
||||
}
|
||||
j["points"] = jpoints;
|
||||
return j;
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::TwoPort::fromJSON(nlohmann::json j)
|
||||
{
|
||||
clearPoints();
|
||||
Base::fromJSON(j);
|
||||
port1 = j.value("port1", 0);
|
||||
port2 = j.value("port2", 0);
|
||||
reverseStandard = j.value("reverseStandard", false);
|
||||
if(j.contains("points")) {
|
||||
for(auto jpoint : j["points"]) {
|
||||
Point p;
|
||||
p.frequency = jpoint.value("frequency", 0.0);
|
||||
p.S.fromJSON(jpoint["Sparam"]);
|
||||
points.push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Sparam CalibrationMeasurement::TwoPort::getMeasured(double frequency)
|
||||
{
|
||||
if(points.size() == 0 || frequency < points.front().frequency || frequency > points.back().frequency) {
|
||||
return Sparam();
|
||||
}
|
||||
// frequency within points, interpolate
|
||||
Point interp = Util::interpolate<Point, double>(points, frequency, [](const Point &p)->double{
|
||||
return p.frequency;
|
||||
});
|
||||
return interp.S;
|
||||
}
|
||||
|
||||
Sparam CalibrationMeasurement::TwoPort::getActual(double frequency)
|
||||
{
|
||||
auto param = static_cast<CalStandard::TwoPort*>(standard)->toSparam(frequency);
|
||||
if(reverseStandard) {
|
||||
param.swapPorts(1, 2);
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
int CalibrationMeasurement::TwoPort::getPort2() const
|
||||
{
|
||||
return port2;
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::TwoPort::setPort1(unsigned int p)
|
||||
{
|
||||
if(port1 != p) {
|
||||
port1 = p;
|
||||
emit port1Changed(p);
|
||||
}
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::TwoPort::setPort2(unsigned int p)
|
||||
{
|
||||
if(port2 != p) {
|
||||
port2 = p;
|
||||
emit port2Changed(p);
|
||||
}
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::TwoPort::setReverseStandard(bool reverse)
|
||||
{
|
||||
if(reverseStandard != reverse) {
|
||||
reverseStandard = reverse;
|
||||
emit reverseStandardChanged(reverse);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CalibrationMeasurement::TwoPort::Point> CalibrationMeasurement::TwoPort::getPoints() const
|
||||
{
|
||||
return points;
|
||||
}
|
||||
|
||||
int CalibrationMeasurement::TwoPort::getPort1() const
|
||||
{
|
||||
return port1;
|
||||
}
|
||||
|
||||
unsigned int CalibrationMeasurement::Isolation::numPoints()
|
||||
{
|
||||
return points.size();
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::Isolation::clearPoints()
|
||||
{
|
||||
points.clear();
|
||||
timestamp = QDateTime();
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::Isolation::addPoint(const DeviceDriver::VNAMeasurement &m)
|
||||
{
|
||||
Point p;
|
||||
p.frequency = m.frequency;
|
||||
for(auto &meas : m.measurements) {
|
||||
QString name = meas.first;
|
||||
unsigned int rcv = name.mid(1, 1).toInt() - 1;
|
||||
unsigned int src = name.mid(2, 1).toInt() - 1;
|
||||
if(rcv > 8 || src > 8) {
|
||||
// skip
|
||||
continue;
|
||||
}
|
||||
if(rcv >= p.S.size()) {
|
||||
p.S.resize(rcv + 1);
|
||||
}
|
||||
if(src >= p.S[rcv].size()) {
|
||||
p.S[rcv].resize(src + 1);
|
||||
}
|
||||
p.S[rcv][src] = meas.second;
|
||||
}
|
||||
points.push_back(p);
|
||||
timestamp = QDateTime::currentDateTimeUtc();
|
||||
}
|
||||
|
||||
QWidget *CalibrationMeasurement::Isolation::createStandardWidget()
|
||||
{
|
||||
return new QLabel("Terminate all ports");
|
||||
}
|
||||
|
||||
QWidget *CalibrationMeasurement::Isolation::createSettingsWidget()
|
||||
{
|
||||
return new QLabel("No settings available");
|
||||
}
|
||||
|
||||
nlohmann::json CalibrationMeasurement::Isolation::toJSON()
|
||||
{
|
||||
auto j = Base::toJSON();
|
||||
nlohmann::json jpoints;
|
||||
for(auto &p : points) {
|
||||
nlohmann::json jpoint;
|
||||
jpoint["frequency"] = p.frequency;
|
||||
nlohmann::json jdest;
|
||||
for(auto dst : p.S) {
|
||||
nlohmann::json jsrc;
|
||||
for(auto src : dst) {
|
||||
nlohmann::json jiso;
|
||||
jiso["real"] = src.real();
|
||||
jiso["imag"] = src.imag();
|
||||
jsrc.push_back(jiso);
|
||||
}
|
||||
jdest.push_back(jsrc);
|
||||
}
|
||||
jpoint["S"] = jdest;
|
||||
jpoints.push_back(jpoint);
|
||||
}
|
||||
j["points"] = jpoints;
|
||||
return j;
|
||||
}
|
||||
|
||||
void CalibrationMeasurement::Isolation::fromJSON(nlohmann::json j)
|
||||
{
|
||||
clearPoints();
|
||||
Base::fromJSON(j);
|
||||
if(j.contains("points")) {
|
||||
for(auto jpoint : j["points"]) {
|
||||
Point p;
|
||||
p.frequency = jpoint.value("frequency", 0.0);
|
||||
if(jpoint.contains("S")) {
|
||||
for(auto jdest : jpoint["S"]) {
|
||||
p.S.push_back(vector<complex<double>>());
|
||||
for(auto jsrc : jdest) {
|
||||
auto S = complex<double>(jsrc.value("real", 0.0), jsrc.value("imag", 0.0));
|
||||
p.S.back().push_back(S);
|
||||
}
|
||||
}
|
||||
}
|
||||
points.push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::complex<double> CalibrationMeasurement::Isolation::getMeasured(double frequency, unsigned int portRcv, unsigned int portSrc)
|
||||
{
|
||||
if(points.size() == 0 || frequency < points.front().frequency || frequency > points.back().frequency) {
|
||||
return numeric_limits<complex<double>>::quiet_NaN();
|
||||
}
|
||||
portRcv--;
|
||||
portSrc--;
|
||||
// find correct point, interpolate
|
||||
Point interp = Util::interpolate<Point, double>(points, frequency, [](const Point &p)->double{
|
||||
return p.frequency;
|
||||
});
|
||||
if(portRcv >= interp.S.size() || portSrc >= interp.S[portRcv].size()) {
|
||||
return numeric_limits<complex<double>>::quiet_NaN();
|
||||
} else {
|
||||
return interp.S[portRcv][portSrc];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CalibrationMeasurement::Isolation::Point> CalibrationMeasurement::Isolation::getPoints() const
|
||||
{
|
||||
return points;
|
||||
}
|
||||
|
||||
QWidget *CalibrationMeasurement::SlidingLoad::createStandardWidget()
|
||||
{
|
||||
return new QLabel("Connect sliding load");
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
#ifndef CALIBRATIONMEASUREMENT_H
|
||||
#define CALIBRATIONMEASUREMENT_H
|
||||
|
||||
#include "calstandard.h"
|
||||
#include "Device/devicedriver.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QTableWidgetItem>
|
||||
|
||||
class Calibration;
|
||||
|
||||
namespace CalibrationMeasurement {
|
||||
|
||||
class Base : public QObject, public Savable
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Base(Calibration *cal);
|
||||
|
||||
enum class Type {
|
||||
Open,
|
||||
Short,
|
||||
Load,
|
||||
SlidingLoad,
|
||||
Reflect,
|
||||
Through,
|
||||
Isolation,
|
||||
Line,
|
||||
Last,
|
||||
};
|
||||
|
||||
std::vector<CalStandard::Virtual*> supportedStandards();
|
||||
virtual bool setFirstSupportedStandard();
|
||||
virtual bool setStandard(CalStandard::Virtual *standard);
|
||||
|
||||
virtual QTableWidgetItem *getStatisticsItem();
|
||||
QString getStatistics();
|
||||
|
||||
virtual double minUsableFreq() = 0;
|
||||
virtual double maxUsableFreq() = 0;
|
||||
virtual double minFreq() = 0;
|
||||
virtual double maxFreq() = 0;
|
||||
virtual double getPointFreq(unsigned int p) = 0;
|
||||
virtual unsigned int numPoints() = 0;
|
||||
virtual bool readyForMeasurement() {return false;}
|
||||
virtual bool readyForCalculation() {return false;}
|
||||
|
||||
static std::vector<Type> availableTypes();
|
||||
static QString TypeToString(Type type);
|
||||
static Type TypeFromString(QString s);
|
||||
virtual std::set<CalStandard::Virtual::Type> supportedStandardTypes() = 0;
|
||||
virtual Type getType() = 0;
|
||||
|
||||
virtual void clearPoints() = 0;
|
||||
virtual void addPoint(const DeviceDriver::VNAMeasurement &m) = 0;
|
||||
|
||||
virtual QWidget* createStandardWidget();
|
||||
virtual QWidget* createSettingsWidget() = 0;
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
virtual std::vector<unsigned int> getPorts() = 0;
|
||||
|
||||
static bool canMeasureSimultaneously(std::set<Base *> measurements);
|
||||
QDateTime getTimestamp() const;
|
||||
|
||||
CalStandard::Virtual *getStandard() const;
|
||||
|
||||
protected:
|
||||
signals:
|
||||
void standardChanged(CalStandard::Virtual* newStandard);
|
||||
protected:
|
||||
CalStandard::Virtual *standard;
|
||||
QDateTime timestamp;
|
||||
Calibration *cal;
|
||||
};
|
||||
|
||||
class OnePort : public Base
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OnePort(Calibration *cal) :
|
||||
Base(cal),
|
||||
port(0) {}
|
||||
|
||||
virtual QTableWidgetItem *getStatisticsItem() override;
|
||||
|
||||
virtual double minUsableFreq() override;
|
||||
virtual double maxUsableFreq() override;
|
||||
virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();}
|
||||
virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;}
|
||||
virtual double getPointFreq(unsigned int p) override { return p < points.size() ? points[p].frequency : 0;}
|
||||
virtual unsigned int numPoints() override {return points.size();}
|
||||
virtual bool readyForMeasurement() override {return standard != nullptr;}
|
||||
virtual bool readyForCalculation() override {return standard && points.size() > 0;}
|
||||
|
||||
virtual void clearPoints() override;
|
||||
virtual void addPoint(const DeviceDriver::VNAMeasurement &m) override;
|
||||
|
||||
virtual QWidget* createSettingsWidget() override;
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
virtual std::vector<unsigned int> getPorts() override {return {port};}
|
||||
|
||||
class Point {
|
||||
public:
|
||||
double frequency;
|
||||
std::complex<double> S;
|
||||
|
||||
Point operator*(double scalar) const {
|
||||
Point ret;
|
||||
ret.frequency = frequency * scalar;
|
||||
ret.S = S * scalar;
|
||||
return ret;
|
||||
}
|
||||
Point operator+(const Point &p) const {
|
||||
Point ret;
|
||||
ret.frequency = frequency + p.frequency;
|
||||
ret.S = S + p.S;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
std::vector<Point> getPoints() const;
|
||||
|
||||
std::complex<double> getMeasured(double frequency);
|
||||
std::complex<double> getActual(double frequency);
|
||||
|
||||
int getPort() const;
|
||||
|
||||
public slots:
|
||||
void setPort(unsigned int p);
|
||||
protected:
|
||||
signals:
|
||||
void portChanged(int p);
|
||||
protected:
|
||||
unsigned int port; // starts at 1
|
||||
std::vector<Point> points;
|
||||
};
|
||||
|
||||
class Open : public OnePort
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Open(Calibration *cal) :
|
||||
OnePort(cal){setFirstSupportedStandard();}
|
||||
|
||||
virtual std::set<CalStandard::Virtual::Type> supportedStandardTypes() override {return {CalStandard::Virtual::Type::Open};}
|
||||
virtual Type getType() override {return Type::Open;}
|
||||
};
|
||||
|
||||
class Short : public OnePort
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Short(Calibration *cal) :
|
||||
OnePort(cal){setFirstSupportedStandard();}
|
||||
virtual std::set<CalStandard::Virtual::Type> supportedStandardTypes() override {return {CalStandard::Virtual::Type::Short};}
|
||||
virtual Type getType() override {return Type::Short;}
|
||||
};
|
||||
|
||||
class Load : public OnePort
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Load(Calibration *cal) :
|
||||
OnePort(cal){setFirstSupportedStandard();}
|
||||
virtual std::set<CalStandard::Virtual::Type> supportedStandardTypes() override {return {CalStandard::Virtual::Type::Load};}
|
||||
virtual Type getType() override {return Type::Load;}
|
||||
};
|
||||
|
||||
class SlidingLoad : public OnePort
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SlidingLoad(Calibration *cal) :
|
||||
OnePort(cal){setFirstSupportedStandard();}
|
||||
|
||||
virtual QWidget* createStandardWidget() override;
|
||||
|
||||
virtual std::set<CalStandard::Virtual::Type> supportedStandardTypes() override {return {};}
|
||||
virtual Type getType() override {return Type::SlidingLoad;}
|
||||
};
|
||||
|
||||
class Reflect : public OnePort
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Reflect(Calibration *cal) :
|
||||
OnePort(cal){setFirstSupportedStandard();}
|
||||
|
||||
virtual std::set<CalStandard::Virtual::Type> supportedStandardTypes() override {return {CalStandard::Virtual::Type::Open,
|
||||
CalStandard::Virtual::Type::Short, CalStandard::Virtual::Type::Reflect};}
|
||||
virtual Type getType() override {return Type::Reflect;}
|
||||
};
|
||||
|
||||
class TwoPort : public Base
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TwoPort(Calibration *cal) :
|
||||
Base(cal),
|
||||
port1(0),
|
||||
port2(0),
|
||||
reverseStandard(false){}
|
||||
|
||||
virtual QTableWidgetItem *getStatisticsItem() override;
|
||||
|
||||
virtual double minUsableFreq() override;
|
||||
virtual double maxUsableFreq() override;
|
||||
virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();}
|
||||
virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;}
|
||||
virtual double getPointFreq(unsigned int p) override { return p < points.size() ? points[p].frequency : 0;}
|
||||
virtual unsigned int numPoints() override {return points.size();}
|
||||
virtual bool readyForMeasurement() override {return standard != nullptr;}
|
||||
virtual bool readyForCalculation() override {return standard && points.size() > 0;}
|
||||
|
||||
virtual void clearPoints() override;
|
||||
virtual void addPoint(const DeviceDriver::VNAMeasurement &m) override;
|
||||
|
||||
virtual QWidget* createSettingsWidget() override;
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
virtual std::vector<unsigned int> getPorts() override {return {port1, port2};}
|
||||
|
||||
class Point {
|
||||
public:
|
||||
double frequency;
|
||||
Sparam S;
|
||||
|
||||
Point operator*(double scalar) const {
|
||||
Point ret;
|
||||
ret.frequency = frequency * scalar;
|
||||
ret.S = S * scalar;
|
||||
return ret;
|
||||
}
|
||||
Point operator+(const Point &p) const {
|
||||
Point ret;
|
||||
ret.frequency = frequency + p.frequency;
|
||||
ret.S = S + p.S;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
std::vector<Point> getPoints() const;
|
||||
|
||||
Sparam getMeasured(double frequency);
|
||||
Sparam getActual(double frequency);
|
||||
|
||||
int getPort1() const;
|
||||
int getPort2() const;
|
||||
|
||||
|
||||
public slots:
|
||||
void setPort1(unsigned int p);
|
||||
void setPort2(unsigned int p);
|
||||
void setReverseStandard(bool reverse);
|
||||
protected:
|
||||
signals:
|
||||
void port1Changed(int p);
|
||||
void port2Changed(int p);
|
||||
void reverseStandardChanged(bool r);
|
||||
protected:
|
||||
unsigned int port1, port2; // starts at 1
|
||||
bool reverseStandard; // Set to true if standard is defined with ports swapped
|
||||
std::vector<Point> points;
|
||||
};
|
||||
|
||||
class Through : public TwoPort
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Through(Calibration *cal) :
|
||||
TwoPort(cal){setFirstSupportedStandard();}
|
||||
virtual std::set<CalStandard::Virtual::Type> supportedStandardTypes() override {return {CalStandard::Virtual::Type::Through};}
|
||||
virtual Type getType() override {return Type::Through;}
|
||||
};
|
||||
|
||||
class Isolation : public Base
|
||||
{
|
||||
public:
|
||||
Isolation(Calibration *cal) :
|
||||
Base(cal){}
|
||||
|
||||
virtual double minUsableFreq() override {return minFreq();}
|
||||
virtual double maxUsableFreq() override {return maxFreq();}
|
||||
virtual double minFreq() override {return points.size() > 0 ? points.front().frequency : std::numeric_limits<double>::max();}
|
||||
virtual double maxFreq() override {return points.size() > 0 ? points.back().frequency : 0;}
|
||||
virtual double getPointFreq(unsigned int p) override { return p < points.size() ? points[p].frequency : 0;}
|
||||
virtual unsigned int numPoints() override;
|
||||
virtual bool readyForMeasurement() override {return true;}
|
||||
virtual bool readyForCalculation() override {return points.size() > 0;}
|
||||
|
||||
virtual void clearPoints() override;
|
||||
virtual void addPoint(const DeviceDriver::VNAMeasurement &m) override;
|
||||
|
||||
virtual QWidget* createStandardWidget() override;
|
||||
virtual QWidget* createSettingsWidget() override;
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
virtual std::vector<unsigned int> getPorts() override {return {};}
|
||||
|
||||
class Point {
|
||||
public:
|
||||
double frequency;
|
||||
std::vector<std::vector<std::complex<double>>> S;
|
||||
|
||||
Point operator*(double scalar) const {
|
||||
Point ret;
|
||||
ret.frequency = frequency * scalar;
|
||||
for(const auto &v1 : S) {
|
||||
std::vector<std::complex<double>> v;
|
||||
for(const auto &s : v1) {
|
||||
v.push_back(s * scalar);
|
||||
}
|
||||
ret.S.push_back(v);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Point operator+(const Point &p) const {
|
||||
Point ret;
|
||||
ret.frequency = frequency + p.frequency;
|
||||
if(S.size() != p.S.size()) {
|
||||
throw std::runtime_error("Points to not have the same number of measurements");
|
||||
}
|
||||
for(unsigned int i=0;i<S.size();i++) {
|
||||
std::vector<std::complex<double>> v;
|
||||
if(S[i].size() != p.S[i].size()) {
|
||||
throw std::runtime_error("Points to not have the same number of measurements");
|
||||
}
|
||||
for(unsigned int j=0;j<S[i].size();j++) {
|
||||
v.push_back(S[i][j] + p.S[i][j]);
|
||||
}
|
||||
ret.S.push_back(v);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
std::vector<Point> getPoints() const;
|
||||
|
||||
std::complex<double> getMeasured(double frequency, unsigned int portRcv, unsigned int portSrc);
|
||||
|
||||
virtual std::set<CalStandard::Virtual::Type> supportedStandardTypes() override {return {};}
|
||||
virtual Type getType() override {return Type::Isolation;}
|
||||
|
||||
protected:
|
||||
std::vector<Point> points;
|
||||
};
|
||||
|
||||
class Line : public TwoPort
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Line(Calibration *cal) :
|
||||
TwoPort(cal){setFirstSupportedStandard();}
|
||||
virtual std::set<CalStandard::Virtual::Type> supportedStandardTypes() override {return {CalStandard::Virtual::Type::Line};}
|
||||
virtual Type getType() override {return Type::Line;}
|
||||
};
|
||||
|
||||
}
|
||||
#endif // CALIBRATIONMEASUREMENT_H
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
#include "calibrationviewdialog.h"
|
||||
#include "ui_calibrationviewdialog.h"
|
||||
|
||||
#include <QGraphicsSimpleTextItem>
|
||||
#include <QVector2D>
|
||||
|
||||
const QColor CalibrationViewDialog::colorNoCal = Qt::darkRed;
|
||||
const QColor CalibrationViewDialog::colorHasCal = Qt::darkGreen;
|
||||
|
||||
CalibrationViewDialog::CalibrationViewDialog(Calibration *cal, unsigned int ports, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::CalibrationViewDialog)
|
||||
, cal(cal)
|
||||
, ports(ports)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
ui->setupUi(this);
|
||||
ui->port->setMaximum(ports);
|
||||
scene = new QGraphicsScene();
|
||||
populateScene();
|
||||
ui->view->setScene(scene);
|
||||
|
||||
connect(ui->port, &QSpinBox::valueChanged, this, &CalibrationViewDialog::populateScene);
|
||||
}
|
||||
|
||||
CalibrationViewDialog::~CalibrationViewDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void CalibrationViewDialog::populateScene()
|
||||
{
|
||||
scene->clear();
|
||||
auto colorF = QApplication::palette().text().color();
|
||||
auto colorB = QApplication::palette().base().color();
|
||||
auto pen = QPen(colorF);
|
||||
|
||||
auto drawDot = [this](float x, float y, float size, QPen pen = QPen(), QBrush brush = QBrush()) {
|
||||
scene->addEllipse(x - size/2, y - size/2, size, size, pen, brush);
|
||||
};
|
||||
|
||||
auto drawText = [this](float x, float y, QString s, QColor color, auto alignH = Qt::AlignCenter, auto alignV = Qt::AlignCenter, float rotation = 0.0f) {
|
||||
auto text = scene->addText(s);
|
||||
text->setRotation(rotation);
|
||||
switch(alignH) {
|
||||
default:
|
||||
case Qt::AlignLeft: break;
|
||||
case Qt::AlignCenter:
|
||||
case Qt::AlignHCenter: x -= text->boundingRect().bottomRight().x()/2; break;
|
||||
case Qt::AlignRight: x -= text->boundingRect().bottomRight().x(); break;
|
||||
}
|
||||
switch(alignV) {
|
||||
default:
|
||||
case Qt::AlignTop: break;
|
||||
case Qt::AlignCenter:
|
||||
case Qt::AlignVCenter: y -= text->boundingRect().bottomRight().y()/2; break;
|
||||
case Qt::AlignBottom: y -= text->boundingRect().bottomRight().y(); break;
|
||||
}
|
||||
text->setPos(QPointF(x, y));
|
||||
text->setDefaultTextColor(color);
|
||||
};
|
||||
|
||||
auto drawPath = [this, drawText](QList<QPointF> vertices, QColor color, QString label = QString(), bool arrow = false) {
|
||||
// draw lines
|
||||
for(unsigned int i=1;i<vertices.size();i++) {
|
||||
|
||||
scene->addLine(QLineF(vertices[i-1], vertices[i]), color);
|
||||
if(i == vertices.size() / 2) {
|
||||
// this is the middle segment, add label and arrow if required
|
||||
auto midPoint = (vertices[i-1]+vertices[i])/2;
|
||||
QVector2D direction = QVector2D(vertices[i] - vertices[i-1]);
|
||||
direction.normalize();
|
||||
auto ortho = QVector2D(-direction.y(), direction.x());
|
||||
if(arrow) {
|
||||
auto poly = QPolygonF({
|
||||
QPointF(midPoint + direction.toPointF()*arrowLength/2),
|
||||
QPointF(midPoint - direction.toPointF()*arrowLength/2 + ortho.toPointF()*arrowWidth/2),
|
||||
QPointF(midPoint - direction.toPointF()*arrowLength/2 - ortho.toPointF()*arrowWidth/2)
|
||||
});
|
||||
scene->addPolygon(poly, color, color);
|
||||
}
|
||||
if(!label.isEmpty()) {
|
||||
auto pos = midPoint;
|
||||
if(label.startsWith("_")) {
|
||||
label.remove(0, 1);
|
||||
pos -= ortho.toPointF() * labelDistance;
|
||||
} else {
|
||||
pos += ortho.toPointF() * labelDistance;
|
||||
}
|
||||
auto alignH = abs(direction.x()) > abs(direction.y()) ? Qt::AlignCenter : Qt::AlignLeft;
|
||||
auto alignV = abs(direction.y()) > abs(direction.x()) ? Qt::AlignCenter : Qt::AlignTop;
|
||||
drawText(pos.x(), pos.y(), label, color, alignH, alignV);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto DUTwidth = 2 * ports * pathSpacing;
|
||||
auto DUTstartX = ports * pathSpacing + 2*boxSpacing;
|
||||
|
||||
// set the overall scene size
|
||||
scene->setSceneRect(0, 0, marginLeft + DUTstartX + DUTwidth + marginRight, marginTop + ports * portHeight + marginBottom);
|
||||
|
||||
// create the DUT
|
||||
// rectangle
|
||||
scene->addRect(marginLeft + DUTstartX, marginTop, DUTwidth, ports * portHeight, pen);
|
||||
drawText(marginLeft + DUTstartX + DUTwidth/2, marginTop, "DUT", colorF, Qt::AlignCenter, Qt::AlignTop);
|
||||
// ports
|
||||
for(unsigned int i=1;i<=ports;i++) {
|
||||
// input marker
|
||||
drawDot(marginLeft + DUTstartX, marginTop + i*portHeight - portHeight/2 + portForwardYOffset, portSize, colorF, colorB);
|
||||
// output marker
|
||||
drawDot(marginLeft + DUTstartX, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, portSize, colorF, colorB);
|
||||
}
|
||||
// the reflection path
|
||||
drawPath({
|
||||
QPointF(marginLeft + DUTstartX + portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(marginLeft + DUTstartX + portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, colorF, "_S"+QString::number(ui->port->value())+QString::number(ui->port->value()), true);
|
||||
// the forward transmission paths
|
||||
float xstart = marginLeft + DUTstartX + pathSpacing;
|
||||
for(unsigned int i=1;i<=ports;i++) {
|
||||
if((int) i == ui->port->value()) {
|
||||
// skip, this is the reflection path
|
||||
continue;
|
||||
}
|
||||
drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
|
||||
drawPath({
|
||||
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(marginLeft + DUTstartX + portSize/2, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, colorF, QString((int) i > ui->port->value() ? "_" : "") + "S"+QString::number(i)+QString::number(ui->port->value()), true);
|
||||
xstart += pathSpacing;
|
||||
}
|
||||
// the reverse transmission paths
|
||||
bool first = true;
|
||||
for(unsigned int i=1;i<=ports;i++) {
|
||||
if((int) i == ui->port->value()) {
|
||||
// skip, this is the reflection path
|
||||
continue;
|
||||
}
|
||||
if(first) {
|
||||
first = false;
|
||||
drawDot(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
|
||||
drawPath({
|
||||
QPointF(marginLeft + DUTstartX + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, colorF, "", false);
|
||||
} else {
|
||||
drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
|
||||
}
|
||||
drawPath({
|
||||
QPointF(marginLeft + DUTstartX + portSize/2, marginTop + i*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, colorF, QString((int) i > ui->port->value() ? "" : "_") + "S"+QString::number(ui->port->value())+QString::number(i), true);
|
||||
xstart += pathSpacing;
|
||||
}
|
||||
|
||||
// isolation, transmission and receiver match paths
|
||||
xstart = marginLeft;
|
||||
for(unsigned int i=1;i<=ports;i++) {
|
||||
if((int) i == ui->port->value()) {
|
||||
// skip, this is the reflection path
|
||||
continue;
|
||||
}
|
||||
// isolation
|
||||
drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
|
||||
drawPath({
|
||||
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
}, colorF);
|
||||
drawPath({
|
||||
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, cal->hasIsolation(ui->port->value(), i) ? colorHasCal : colorNoCal, QString((int) i > ui->port->value() ? "_" : "") + "I"+QString::number(i)+QString::number(ui->port->value()), true);
|
||||
drawPath({
|
||||
QPointF(xstart + pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(marginLeft, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, colorF);
|
||||
xstart += pathSpacing;
|
||||
// transmission
|
||||
drawDot(xstart, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
|
||||
drawPath({
|
||||
QPointF(marginLeft + DUTstartX - portSize / 2, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, colorF);
|
||||
drawPath({
|
||||
QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(xstart, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, cal->hasTransmissionTracking(ui->port->value(), i) ? colorHasCal : colorNoCal, "_T"+QString::number(i)+QString::number(ui->port->value()), true);
|
||||
// Reveicer match
|
||||
drawDot(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
|
||||
drawPath({
|
||||
QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(marginLeft + DUTstartX - pathSpacing, marginTop + i*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(marginLeft + DUTstartX - portSize/2, marginTop + i*portHeight - portHeight/2 + portForwardYOffset),
|
||||
}, cal->hasReceiverMatch(ui->port->value(), i) ? colorHasCal : colorNoCal, "L"+QString::number(i)+QString::number(ui->port->value()), true);
|
||||
}
|
||||
|
||||
// reflection error box
|
||||
drawDot(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
|
||||
drawDot(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
|
||||
drawDot(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
|
||||
drawDot(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset, junctionSize, colorF, colorF);
|
||||
drawDot(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset, junctionSize, colorF, colorF);
|
||||
// unity paths
|
||||
drawPath({
|
||||
QPointF(xstart, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(marginLeft + DUTstartX - portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
}, colorF, "", true);
|
||||
drawPath({
|
||||
QPointF(marginLeft + DUTstartX - portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, colorF);
|
||||
drawPath({
|
||||
QPointF(marginLeft + portSize/2, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, colorF);
|
||||
|
||||
// directivity
|
||||
drawPath({
|
||||
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, cal->hasDirectivity(ui->port->value()) ? colorHasCal : colorNoCal, "_D"+QString::number(ui->port->value()), true);
|
||||
|
||||
// reflection tracking
|
||||
drawPath({
|
||||
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(xstart + pathSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
}, cal->hasReflectionTracking(ui->port->value()) ? colorHasCal : colorNoCal, "_R"+QString::number(ui->port->value()), true);
|
||||
|
||||
// source match
|
||||
drawPath({
|
||||
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portReverseYOffset),
|
||||
QPointF(xstart + pathSpacing + boxSpacing, marginTop + ui->port->value()*portHeight - portHeight/2 + portForwardYOffset),
|
||||
}, cal->hasSourceMatch(ui->port->value()) ? colorHasCal : colorNoCal, "S"+QString::number(ui->port->value()), true);
|
||||
|
||||
// create the VNA ports
|
||||
for(unsigned int i=1;i<=ports;i++) {
|
||||
// stimulus port
|
||||
if(i == (unsigned int) ui->port->value()) {
|
||||
drawDot(marginLeft, marginTop + i*portHeight - portHeight/2 + portForwardYOffset, portSize, colorF, colorB);
|
||||
drawText(marginLeft - portSize/2, marginTop + i*portHeight - portHeight/2 + portForwardYOffset, "a"+QString::number(i), colorF, Qt::AlignRight, Qt::AlignCenter);
|
||||
}
|
||||
// output marker
|
||||
drawDot(marginLeft, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, portSize, colorF, colorB);
|
||||
drawText(marginLeft - portSize/2, marginTop + i*portHeight - portHeight/2 + portReverseYOffset, "b"+QString::number(i), colorF, Qt::AlignRight, Qt::AlignCenter);
|
||||
}
|
||||
|
||||
// Fill the measurement correction table
|
||||
ui->table->clear();
|
||||
ui->table->setRowCount(ports*ports);
|
||||
ui->table->setColumnCount(2);
|
||||
ui->table->setHorizontalHeaderLabels({"Parameter", "Calibration Status"});
|
||||
for(unsigned int i=1;i<=ports;i++) {
|
||||
for(unsigned int j=1;j<=ports;j++) {
|
||||
auto row = (i-1)*ports+j-1;
|
||||
// add parameter
|
||||
ui->table->setItem(row, 0, new QTableWidgetItem("S"+QString::number(j)+QString::number(i)));
|
||||
// check the calibration status
|
||||
QString status = "Uncalibrated";
|
||||
if(i == j) {
|
||||
// check reflection parameters
|
||||
if(cal->hasSourceMatch(i) && cal->hasDirectivity(i) && cal->hasReflectionTracking(i)) {
|
||||
// we are calibrated
|
||||
status = "Calibrated";
|
||||
// check if we have enhanced responses
|
||||
QList<int> enhanced;
|
||||
for(unsigned int k=1;k<=ports;k++) {
|
||||
if(k==i) {
|
||||
continue;
|
||||
}
|
||||
if(cal->hasReceiverMatch(i, k)) {
|
||||
enhanced.append(k);
|
||||
}
|
||||
}
|
||||
if(enhanced.size() == 1) {
|
||||
status += " with enhanced response from port "+QString::number(enhanced[0]);
|
||||
} else if(enhanced.size() > 1) {
|
||||
status += " with enhanced response from ports ";
|
||||
for(unsigned int k=0;k<enhanced.size();k++) {
|
||||
if(k == enhanced.size() - 1) {
|
||||
status += " and "+QString::number(enhanced[k]);
|
||||
} else if(k > 0) {
|
||||
status += ", ";
|
||||
}
|
||||
status += QString::number(enhanced[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// check transmission calibration
|
||||
if(cal->hasTransmissionTracking(i, j)) {
|
||||
// we are calibrated
|
||||
status = "Calibrated";
|
||||
// check if we have isolation terms
|
||||
if(cal->hasIsolation(i, j)) {
|
||||
status += " with isolation measurement";
|
||||
}
|
||||
}
|
||||
}
|
||||
// add calibration status
|
||||
ui->table->setItem(row, 1, new QTableWidgetItem(status));
|
||||
}
|
||||
}
|
||||
ui->table->resizeColumnsToContents();
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#ifndef CALIBRATIONVIEWDIALOG_H
|
||||
#define CALIBRATIONVIEWDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "calibration.h"
|
||||
#include <QGraphicsScene>
|
||||
|
||||
namespace Ui {
|
||||
class CalibrationViewDialog;
|
||||
}
|
||||
|
||||
class CalibrationViewDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CalibrationViewDialog(Calibration *cal, unsigned int ports, QWidget *parent = nullptr);
|
||||
~CalibrationViewDialog();
|
||||
|
||||
private slots:
|
||||
void populateScene();
|
||||
private:
|
||||
static constexpr int marginTop = 10;
|
||||
static constexpr int marginBottom = 10;
|
||||
static constexpr int marginLeft = 30;
|
||||
static constexpr int marginRight = 10;
|
||||
|
||||
static constexpr int portHeight = 170;
|
||||
static constexpr int portForwardYOffset = -50;
|
||||
static constexpr int portReverseYOffset = 50;
|
||||
static constexpr int boxSpacing = portReverseYOffset - portForwardYOffset;
|
||||
|
||||
static constexpr int portSize = 10;
|
||||
static constexpr int arrowLength = 15;
|
||||
static constexpr int arrowWidth = 10;
|
||||
static constexpr int junctionSize = 6;
|
||||
static constexpr int labelDistance = 6;
|
||||
|
||||
static constexpr int pathSpacing = 40;
|
||||
|
||||
static const QColor colorNoCal;
|
||||
static const QColor colorHasCal;
|
||||
|
||||
Ui::CalibrationViewDialog *ui;
|
||||
Calibration *cal;
|
||||
unsigned int ports;
|
||||
QGraphicsScene *scene;
|
||||
};
|
||||
|
||||
#endif // CALIBRATIONVIEWDIALOG_H
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalibrationViewDialog</class>
|
||||
<widget class="QDialog" name="CalibrationViewDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1106</width>
|
||||
<height>665</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Calibration Error Term Model</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>View error term model when stimulus is at port</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="port">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>8</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style='color: darkgreen;'>Green</span> error terms have been calculated from calibration measurements.</p><p><span style='color: darkred;'>Red</span> error terms are at their default values (either 1 or 0).</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGraphicsView" name="view">
|
||||
<property name="dragMode">
|
||||
<enum>QGraphicsView::DragMode::ScrollHandDrag</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>D: Directivity, R: Reflection tracking, S: Source match, L: Receiver match, T: Transmission tracking, I: Isolation</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Which measurements are being corrected?</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="table">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SelectionMode::NoSelection</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CalibrationViewDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>374</x>
|
||||
<y>509</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>374</x>
|
||||
<y>265</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+550
@@ -0,0 +1,550 @@
|
||||
#include "calkit.h"
|
||||
|
||||
#include "calkitdialog.h"
|
||||
#include "json.hpp"
|
||||
#include "CustomWidgets/informationbox.h"
|
||||
#include "appwindow.h"
|
||||
#include "Util/app_common.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
#include <math.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using namespace std;
|
||||
|
||||
Calkit::Calkit()
|
||||
: SCPINode("KIT"), scpi_std("STAndard")
|
||||
{
|
||||
// set default values
|
||||
filename = "";
|
||||
for(auto e : descr) {
|
||||
e.var.setValue(e.def);
|
||||
}
|
||||
|
||||
add(new SCPICommand("MANufacturer", [=](QStringList params) -> QString {
|
||||
if(params.size() != 1 ) {
|
||||
// no new value given
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
manufacturer = params[0];
|
||||
return SCPI::getResultName(SCPI::Result::Empty);
|
||||
}, [=](QStringList) -> QString {
|
||||
return manufacturer;
|
||||
}, false));
|
||||
add(new SCPICommand("SERial", [=](QStringList params) -> QString {
|
||||
if(params.size() != 1 ) {
|
||||
// no new value given
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
serialnumber = params[0];
|
||||
return SCPI::getResultName(SCPI::Result::Empty);
|
||||
}, [=](QStringList) -> QString {
|
||||
return serialnumber;
|
||||
}, false));
|
||||
add(new SCPICommand("DESCription", [=](QStringList params) -> QString {
|
||||
if(params.size() != 1 ) {
|
||||
// no new value given
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
description = params[0];
|
||||
return SCPI::getResultName(SCPI::Result::Empty);
|
||||
}, [=](QStringList) -> QString {
|
||||
return description;
|
||||
}, false));
|
||||
add(new SCPICommand("FILEname", nullptr, [=](QStringList) -> QString {
|
||||
return filename;
|
||||
}));
|
||||
add(new SCPICommand("SAVE", [=](QStringList params) -> QString {
|
||||
if(params.size() != 1 ) {
|
||||
// no filename given or no calibration active
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
toFile(params[0]);
|
||||
return SCPI::getResultName(SCPI::Result::Empty);
|
||||
}, nullptr, false));
|
||||
add(new SCPICommand("LOAD", nullptr, [=](QStringList params) -> QString {
|
||||
if(params.size() != 1) {
|
||||
// no filename given or no calibration active
|
||||
return SCPI::getResultName(SCPI::Result::False);
|
||||
}
|
||||
if(this->fromFile(params[0])) {
|
||||
return SCPI::getResultName(SCPI::Result::True);
|
||||
} else {
|
||||
return SCPI::getResultName(SCPI::Result::False);
|
||||
}
|
||||
}, false));
|
||||
scpi_std.add(new SCPICommand("CLEAR", [=](QStringList params) -> QString {
|
||||
Q_UNUSED(params);
|
||||
setIdealDefault();
|
||||
return SCPI::getResultName(SCPI::Result::Empty);
|
||||
}, nullptr));
|
||||
scpi_std.add(new SCPICommand("NUMber", nullptr, [=](QStringList params) -> QString {
|
||||
Q_UNUSED(params);
|
||||
return QString::number(standards.size());
|
||||
}));
|
||||
scpi_std.add(new SCPICommand("NEW", [=](QStringList params) -> QString {
|
||||
if(params.size() != 2) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
auto type = CalStandard::Virtual::TypeFromString(params[0]);
|
||||
if(type == CalStandard::Virtual::Type::Last) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
auto s = CalStandard::Virtual::create(type);
|
||||
if(!s) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
s->setName(params[1]);
|
||||
addStandard(s);
|
||||
return SCPI::getResultName(SCPI::Result::Empty);
|
||||
}, nullptr, false));
|
||||
scpi_std.add(new SCPICommand("DELete", [=](QStringList params) -> QString {
|
||||
unsigned long long index;
|
||||
if(!SCPI::paramToULongLong(params, 0, index)) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
if(index < 1 || index > standards.size()) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
removeStandard(standards[index-1]);
|
||||
return SCPI::getResultName(SCPI::Result::Empty);
|
||||
}, nullptr));
|
||||
scpi_std.add(new SCPICommand("TYPE", nullptr, [=](QStringList params) -> QString {
|
||||
unsigned long long index = 0;
|
||||
if(!SCPI::paramToULongLong(params, 0, index)) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
if(index < 1 || index > standards.size()) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
return CalStandard::Virtual::TypeToString(standards[index-1]->getType()).replace(" ", "_");
|
||||
}));
|
||||
add(&scpi_std);
|
||||
}
|
||||
|
||||
void Calkit::toFile(QString filename)
|
||||
{
|
||||
if(!filename.endsWith(".calkit")) {
|
||||
filename.append(".calkit");
|
||||
}
|
||||
|
||||
qDebug() << "Saving calkit to file" << filename;
|
||||
|
||||
ofstream file;
|
||||
file.open(filename.toStdString());
|
||||
file << setw(4) << toJSON() << endl;
|
||||
file.close();
|
||||
this->filename = filename;
|
||||
}
|
||||
|
||||
static QString readLine(ifstream &file) {
|
||||
string line;
|
||||
getline(file, line);
|
||||
return QString::fromStdString(line).simplified();
|
||||
}
|
||||
|
||||
bool Calkit::fromFile(QString filename)
|
||||
{
|
||||
qDebug() << "Opening calkit from file" << filename;
|
||||
|
||||
ifstream file;
|
||||
file.open(filename.toStdString());
|
||||
if(!file.is_open()) {
|
||||
qWarning() << "Unable to open file: " << filename;
|
||||
return false;
|
||||
}
|
||||
|
||||
json j;
|
||||
try {
|
||||
file >> j;
|
||||
} catch (exception &e) {
|
||||
qWarning() << "JSON parsing error: " << QString(e.what());
|
||||
return false;
|
||||
}
|
||||
clearStandards();
|
||||
this->filename = "";
|
||||
if(j.contains("standards")) {
|
||||
qDebug() << "new JSON format detected";
|
||||
fromJSON(j);
|
||||
} else {
|
||||
// older format is used
|
||||
struct {
|
||||
using Open = struct {
|
||||
double Z0, delay, loss, C0, C1, C2, C3;
|
||||
QString file;
|
||||
bool useMeasurements;
|
||||
int Sparam;
|
||||
};
|
||||
Open open_m, open_f;
|
||||
using Short = struct {
|
||||
double Z0, delay, loss, L0, L1, L2, L3;
|
||||
QString file;
|
||||
bool useMeasurements;
|
||||
int Sparam;
|
||||
};
|
||||
Short short_m, short_f;
|
||||
using Load = struct {
|
||||
double resistance, Z0, delay, Cparallel, Lseries;
|
||||
QString file;
|
||||
bool useMeasurements;
|
||||
int Sparam;
|
||||
};
|
||||
Load load_m, load_f;
|
||||
bool loadModelCFirst;
|
||||
struct {
|
||||
double Z0, delay, loss;
|
||||
QString file;
|
||||
bool useMeasurements;
|
||||
int Sparam1, Sparam2;
|
||||
} Through;
|
||||
bool separate_male_female;
|
||||
} SOLT;
|
||||
struct {
|
||||
struct {
|
||||
double Z0;
|
||||
} Through;
|
||||
struct {
|
||||
bool isShort;
|
||||
} Reflection;
|
||||
struct {
|
||||
double delay, minFreq, maxFreq;
|
||||
} Line;
|
||||
} TRL;
|
||||
if(j.contains("SOLT")) {
|
||||
qDebug() << "old JSON format detected";
|
||||
// calkit file uses json format, parse
|
||||
Savable::parseJSON(j, descr);
|
||||
const std::vector<Savable::SettingDescription> descr_deprecated = {{
|
||||
{&SOLT.open_m.Z0, "SOLT.Open.Param.Z0", 50.0},
|
||||
{&SOLT.open_m.delay, "SOLT.Open.Param.Delay", 0.0},
|
||||
{&SOLT.open_m.loss, "SOLT.Open.Param.Loss", 0.0},
|
||||
{&SOLT.open_m.C0, "SOLT.Open.Param.C0", 0.0},
|
||||
{&SOLT.open_m.C1, "SOLT.Open.Param.C1", 0.0},
|
||||
{&SOLT.open_m.C2, "SOLT.Open.Param.C2", 0.0},
|
||||
{&SOLT.open_m.C3, "SOLT.Open.Param.C3", 0.0},
|
||||
{&SOLT.open_m.useMeasurements, "SOLT.Open.Measurements.Use", false},
|
||||
{&SOLT.open_m.file, "SOLT.Open.Measurements.File", ""},
|
||||
{&SOLT.open_m.Sparam, "SOLT.Open.Measurements.Port", 0},
|
||||
{&SOLT.open_f.Z0, "SOLT.Open.Param.Z0_Female", 50.0},
|
||||
{&SOLT.open_f.delay, "SOLT.Open.Param.Delay_Female", 0.0},
|
||||
{&SOLT.open_f.loss, "SOLT.Open.Param.Loss_Female", 0.0},
|
||||
{&SOLT.open_f.C0, "SOLT.Open.Param.C0_Female", 0.0},
|
||||
{&SOLT.open_f.C1, "SOLT.Open.Param.C1_Female", 0.0},
|
||||
{&SOLT.open_f.C2, "SOLT.Open.Param.C2_Female", 0.0},
|
||||
{&SOLT.open_f.C3, "SOLT.Open.Param.C3_Female", 0.0},
|
||||
{&SOLT.open_f.useMeasurements, "SOLT.Open.Measurements.Use_Female", false},
|
||||
{&SOLT.open_f.file, "SOLT.Open.Measurements.File_Female", ""},
|
||||
{&SOLT.open_f.Sparam, "SOLT.Open.Measurements.Port_Female", 0},
|
||||
|
||||
{&SOLT.short_m.Z0, "SOLT.Short.Param.Z0", 50.0},
|
||||
{&SOLT.short_m.delay, "SOLT.Short.Param.Delay", 0.0},
|
||||
{&SOLT.short_m.loss, "SOLT.Short.Param.Loss", 0.0},
|
||||
{&SOLT.short_m.L0, "SOLT.Short.Param.L0", 0.0},
|
||||
{&SOLT.short_m.L1, "SOLT.Short.Param.L1", 0.0},
|
||||
{&SOLT.short_m.L2, "SOLT.Short.Param.L2", 0.0},
|
||||
{&SOLT.short_m.L3, "SOLT.Short.Param.L3", 0.0},
|
||||
{&SOLT.short_m.useMeasurements, "SOLT.Short.Measurements.Use", false},
|
||||
{&SOLT.short_m.file, "SOLT.Short.Measurements.File", ""},
|
||||
{&SOLT.short_m.Sparam, "SOLT.Short.Measurements.Port", 0},
|
||||
{&SOLT.short_f.Z0, "SOLT.Short.Param.Z0_Female", 50.0},
|
||||
{&SOLT.short_f.delay, "SOLT.Short.Param.Delay_Female", 0.0},
|
||||
{&SOLT.short_f.loss, "SOLT.Short.Param.Loss_Female", 0.0},
|
||||
{&SOLT.short_f.L0, "SOLT.Short.Param.L0_Female", 0.0},
|
||||
{&SOLT.short_f.L1, "SOLT.Short.Param.L1_Female", 0.0},
|
||||
{&SOLT.short_f.L2, "SOLT.Short.Param.L2_Female", 0.0},
|
||||
{&SOLT.short_f.L3, "SOLT.Short.Param.L3_Female", 0.0},
|
||||
{&SOLT.short_f.useMeasurements, "SOLT.Short.Measurements.Use_Female", false},
|
||||
{&SOLT.short_f.file, "SOLT.Short.Measurements.File_Female", ""},
|
||||
{&SOLT.short_f.Sparam, "SOLT.Short.Measurements.Port_Female", 0},
|
||||
|
||||
{&SOLT.loadModelCFirst, "SOLT.loadModelCFirst", true},
|
||||
{&SOLT.load_m.resistance, "SOLT.Load.Param.Resistance", 50.0},
|
||||
{&SOLT.load_m.Z0, "SOLT.Load.Param.Z0", 50.0},
|
||||
{&SOLT.load_m.delay, "SOLT.Load.Param.Delay", 0.0},
|
||||
{&SOLT.load_m.Cparallel, "SOLT.Load.Param.C", 0.0},
|
||||
{&SOLT.load_m.Lseries, "SOLT.Load.Param.L", 0.0},
|
||||
{&SOLT.load_m.useMeasurements, "SOLT.Load.Measurements.Use", false},
|
||||
{&SOLT.load_m.file, "SOLT.Load.Measurements.File", ""},
|
||||
{&SOLT.load_m.Sparam, "SOLT.Load.Measurements.Port", 0},
|
||||
{&SOLT.load_f.resistance, "SOLT.Load.Param.Resistance_Female", 50.0},
|
||||
{&SOLT.load_f.Z0, "SOLT.Load.Param.Z0_Female", 50.0},
|
||||
{&SOLT.load_f.delay, "SOLT.Load.Param.Delay_Female", 0.0},
|
||||
{&SOLT.load_f.Cparallel, "SOLT.Load.Param.C_Female", 0.0},
|
||||
{&SOLT.load_f.Lseries, "SOLT.Load.Param.L_Female", 0.0},
|
||||
{&SOLT.load_f.useMeasurements, "SOLT.Load.Measurements.Use_Female", false},
|
||||
{&SOLT.load_f.file, "SOLT.Load.Measurements.File_Female", ""},
|
||||
{&SOLT.load_f.Sparam, "SOLT.Load.Measurements.Port_Female", 0},
|
||||
|
||||
{&SOLT.Through.Z0, "SOLT.Through.Param.Z0", 50.0},
|
||||
{&SOLT.Through.delay, "SOLT.Through.Param.Delay", 0.0},
|
||||
{&SOLT.Through.loss, "SOLT.Through.Param.Loss", 0.0},
|
||||
{&SOLT.Through.useMeasurements, "SOLT.Through.Measurements.Use", false},
|
||||
{&SOLT.Through.file, "SOLT.Through.Measurements.File", ""},
|
||||
{&SOLT.Through.Sparam1, "SOLT.Through.Measurements.Port1", 0},
|
||||
{&SOLT.Through.Sparam2, "SOLT.Through.Measurements.Port2", 1},
|
||||
|
||||
{&SOLT.separate_male_female, "SOLT.SeparateMaleFemale", false},
|
||||
|
||||
{&TRL.Through.Z0, "TRL.Through.Z0", 50.0},
|
||||
{&TRL.Reflection.isShort, "TRL.Reflect.isShort", false},
|
||||
{&TRL.Line.delay, "TRL.Line.Delay", 74.0},
|
||||
{&TRL.Line.minFreq, "TRL.Line.minFreq", 751000000.0},
|
||||
{&TRL.Line.maxFreq, "TRL.Line.maxFreq", 6000000000.0},
|
||||
}};
|
||||
Savable::parseJSON(j, descr_deprecated);
|
||||
} else {
|
||||
qDebug() << "Legacy format detected";
|
||||
// legacy file format, return to beginning of file
|
||||
file.clear();
|
||||
file.seekg(0);
|
||||
SOLT.open_m.useMeasurements = readLine(file).toInt();
|
||||
SOLT.short_m.useMeasurements = readLine(file).toInt();
|
||||
SOLT.load_m.useMeasurements = readLine(file).toInt();
|
||||
SOLT.Through.useMeasurements = readLine(file).toInt();
|
||||
SOLT.open_m.Z0 = readLine(file).toDouble();
|
||||
SOLT.open_m.delay = readLine(file).toDouble();
|
||||
SOLT.open_m.loss = readLine(file).toDouble();
|
||||
SOLT.open_m.C0 = readLine(file).toDouble();
|
||||
SOLT.open_m.C1 = readLine(file).toDouble();
|
||||
SOLT.open_m.C2 = readLine(file).toDouble();
|
||||
SOLT.open_m.C3 = readLine(file).toDouble();
|
||||
SOLT.short_m.Z0 = readLine(file).toDouble();
|
||||
SOLT.short_m.delay = readLine(file).toDouble();
|
||||
SOLT.short_m.loss = readLine(file).toDouble();
|
||||
SOLT.short_m.L0 = readLine(file).toDouble();
|
||||
SOLT.short_m.L1 = readLine(file).toDouble();
|
||||
SOLT.short_m.L2 = readLine(file).toDouble();
|
||||
SOLT.short_m.L3 = readLine(file).toDouble();
|
||||
SOLT.load_m.resistance = readLine(file).toDouble();
|
||||
SOLT.Through.Z0 = readLine(file).toDouble();
|
||||
SOLT.Through.delay = readLine(file).toDouble();
|
||||
SOLT.Through.loss = readLine(file).toDouble();
|
||||
if(SOLT.open_m.useMeasurements) {
|
||||
SOLT.open_m.file = readLine(file);
|
||||
SOLT.open_m.Sparam = readLine(file).toInt();
|
||||
}
|
||||
if(SOLT.short_m.useMeasurements) {
|
||||
SOLT.short_m.file = readLine(file);
|
||||
SOLT.short_m.Sparam = readLine(file).toInt();
|
||||
}
|
||||
if(SOLT.load_m.useMeasurements) {
|
||||
SOLT.load_m.file = readLine(file);
|
||||
SOLT.load_m.Sparam = readLine(file).toInt();
|
||||
}
|
||||
if(SOLT.Through.useMeasurements) {
|
||||
SOLT.Through.file = readLine(file);
|
||||
SOLT.Through.Sparam1 = readLine(file).toInt();
|
||||
SOLT.Through.Sparam2 = readLine(file).toInt();
|
||||
}
|
||||
TRL.Through.Z0 = readLine(file).toDouble();
|
||||
TRL.Reflection.isShort = readLine(file).toDouble();
|
||||
TRL.Line.delay = readLine(file).toDouble();
|
||||
TRL.Line.minFreq = readLine(file).toDouble();
|
||||
TRL.Line.maxFreq = readLine(file).toDouble();
|
||||
|
||||
SOLT.separate_male_female = false;
|
||||
SOLT.loadModelCFirst = false;
|
||||
}
|
||||
|
||||
vector<QString*> filenames = {&SOLT.short_m.file, &SOLT.open_m.file, &SOLT.load_m.file, &SOLT.short_f.file, &SOLT.open_f.file, &SOLT.load_f.file, &SOLT.Through.file};
|
||||
QFileInfo d(filename);
|
||||
for(auto f : filenames) {
|
||||
if(f->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if(QFileInfo(*f).isRelative()) {
|
||||
auto absDir = QDir(d.dir().path() + "/" + *f);
|
||||
QString buf = *f;
|
||||
*f = absDir.absolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
// create standards from old calkit forma
|
||||
auto open_m = new CalStandard::Open(SOLT.separate_male_female ? "Default male standard" : "Default standard", SOLT.open_m.Z0, SOLT.open_m.delay, SOLT.open_m.loss, SOLT.open_m.C0, SOLT.open_m.C1, SOLT.open_m.C2, SOLT.open_m.C3);
|
||||
if(SOLT.open_m.useMeasurements) {
|
||||
auto ts = Touchstone(1);
|
||||
ts.fromFile(SOLT.open_m.file.toStdString());
|
||||
open_m->setMeasurement(ts, SOLT.open_m.Sparam);
|
||||
}
|
||||
addStandard(open_m);
|
||||
if(SOLT.separate_male_female) {
|
||||
auto open_f = new CalStandard::Open("Default female standard", SOLT.open_f.Z0, SOLT.open_f.delay, SOLT.open_f.loss, SOLT.open_f.C0, SOLT.open_f.C1, SOLT.open_f.C2, SOLT.open_f.C3);
|
||||
if(SOLT.open_f.useMeasurements) {
|
||||
auto ts = Touchstone(1);
|
||||
ts.fromFile(SOLT.open_f.file.toStdString());
|
||||
open_m->setMeasurement(ts, SOLT.open_f.Sparam);
|
||||
}
|
||||
addStandard(open_f);
|
||||
}
|
||||
|
||||
auto short_m = new CalStandard::Short(SOLT.separate_male_female ? "Default male standard" : "Default standard", SOLT.short_m.Z0, SOLT.short_m.delay, SOLT.short_m.loss, SOLT.short_m.L0, SOLT.short_m.L1, SOLT.short_m.L2, SOLT.short_m.L3);
|
||||
if(SOLT.short_m.useMeasurements) {
|
||||
auto ts = Touchstone(1);
|
||||
ts.fromFile(SOLT.short_m.file.toStdString());
|
||||
short_m->setMeasurement(ts, SOLT.short_m.Sparam);
|
||||
}
|
||||
addStandard(short_m);
|
||||
if(SOLT.separate_male_female) {
|
||||
auto short_f = new CalStandard::Short("Default female standard", SOLT.short_f.Z0, SOLT.short_f.delay, SOLT.short_f.loss, SOLT.short_f.L0, SOLT.short_f.L1, SOLT.short_f.L2, SOLT.short_f.L3);
|
||||
if(SOLT.short_f.useMeasurements) {
|
||||
auto ts = Touchstone(1);
|
||||
ts.fromFile(SOLT.short_f.file.toStdString());
|
||||
short_m->setMeasurement(ts, SOLT.short_f.Sparam);
|
||||
}
|
||||
addStandard(short_f);
|
||||
}
|
||||
|
||||
auto load_m = new CalStandard::Load(SOLT.separate_male_female ? "Default male standard" : "Default standard", SOLT.load_m.Z0, SOLT.load_m.delay, 0.0, SOLT.load_m.resistance, SOLT.load_m.Cparallel, SOLT.load_m.Lseries, SOLT.loadModelCFirst);
|
||||
if(SOLT.load_m.useMeasurements) {
|
||||
auto ts = Touchstone(1);
|
||||
ts.fromFile(SOLT.load_m.file.toStdString());
|
||||
load_m->setMeasurement(ts, SOLT.load_m.Sparam);
|
||||
}
|
||||
addStandard(load_m);
|
||||
if(SOLT.separate_male_female) {
|
||||
auto load_f = new CalStandard::Load("Default female standard", SOLT.load_m.Z0, SOLT.load_f.delay, 0.0, SOLT.load_f.resistance, SOLT.load_f.Cparallel, SOLT.load_f.Lseries, SOLT.loadModelCFirst);
|
||||
if(SOLT.load_f.useMeasurements) {
|
||||
auto ts = Touchstone(1);
|
||||
ts.fromFile(SOLT.load_f.file.toStdString());
|
||||
load_m->setMeasurement(ts, SOLT.load_f.Sparam);
|
||||
}
|
||||
addStandard(load_f);
|
||||
}
|
||||
|
||||
auto through = new CalStandard::Through("Default standard", SOLT.Through.Z0, SOLT.Through.delay, SOLT.Through.loss);
|
||||
if(SOLT.Through.useMeasurements) {
|
||||
auto ts = Touchstone(2);
|
||||
ts.fromFile(SOLT.Through.file.toStdString());
|
||||
through->setMeasurement(ts, SOLT.Through.Sparam1, SOLT.Through.Sparam2);
|
||||
}
|
||||
addStandard(through);
|
||||
|
||||
InformationBox::ShowMessage("Loading calkit file", "The file \"" + filename + "\" is stored in a deprecated"
|
||||
" calibration kit format. Future versions of this application might not support"
|
||||
" it anymore. Please save the calibration kit to update to the new format");
|
||||
}
|
||||
|
||||
file.close();
|
||||
this->filename = filename;
|
||||
updateSCPINames();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Calkit::edit(std::function<void (void)> updateCal)
|
||||
{
|
||||
auto dialog = new CalkitDialog(*this);
|
||||
if(updateCal) {
|
||||
QObject::connect(dialog, &CalkitDialog::settingsChanged, [=](){
|
||||
updateCal();
|
||||
});
|
||||
}
|
||||
if(AppWindow::showGUI()) {
|
||||
dialog->show();
|
||||
}
|
||||
}
|
||||
|
||||
void Calkit::clearStandards()
|
||||
{
|
||||
while(standards.size() > 0) {
|
||||
removeStandard(standards[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void Calkit::updateSCPINames()
|
||||
{
|
||||
// Need to remove all standards from the subnode list first, otherwise
|
||||
// name changes wouldn't work due to temporarily name collisions
|
||||
for(auto &s : standards) {
|
||||
scpi_std.remove(s);
|
||||
}
|
||||
unsigned int i=1;
|
||||
for(auto s : standards) {
|
||||
s->changeName(QString::number(i));
|
||||
scpi_std.add(s);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CalStandard::Virtual *> Calkit::getStandards() const
|
||||
{
|
||||
return standards;
|
||||
}
|
||||
|
||||
void Calkit::addStandard(CalStandard::Virtual *s)
|
||||
{
|
||||
// check for ID collisions. This should never happen but would mess up further
|
||||
// usage of the standards. Better to catch this now and throw an error
|
||||
for(auto comp : standards) {
|
||||
if(comp->getID() == s->getID()) {
|
||||
// collision, do not add
|
||||
InformationBox::ShowError("Calibration standard ID collision", "New standard \""
|
||||
+s->getName()+"\" of type \""+CalStandard::Virtual::TypeToString(s->getType())
|
||||
+"\" has the same ID as already existing standard \""
|
||||
+comp->getName()+"\" of type \""+CalStandard::Virtual::TypeToString(comp->getType())
|
||||
+"\" (ID="+QString::number(s->getID())+"). It will be removed from the calibration kit");
|
||||
return;
|
||||
}
|
||||
}
|
||||
standards.push_back(s);
|
||||
updateSCPINames();
|
||||
}
|
||||
|
||||
void Calkit::removeStandard(CalStandard::Virtual *s)
|
||||
{
|
||||
standards.erase(std::remove(standards.begin(), standards.end(), s), standards.end());
|
||||
delete s;
|
||||
updateSCPINames();
|
||||
}
|
||||
|
||||
nlohmann::json Calkit::toJSON()
|
||||
{
|
||||
json j = Savable::createJSON(descr);
|
||||
nlohmann::json jstandards;
|
||||
for(auto s : standards) {
|
||||
nlohmann::json jstandard;
|
||||
jstandard["type"] = CalStandard::Virtual::TypeToString(s->getType()).toStdString();
|
||||
jstandard["params"] = s->toJSON();
|
||||
jstandards.push_back(jstandard);
|
||||
}
|
||||
j["standards"] = jstandards;
|
||||
j["version"] = qlibrevnaApp->applicationVersion().toStdString();
|
||||
return j;
|
||||
}
|
||||
|
||||
void Calkit::fromJSON(nlohmann::json j)
|
||||
{
|
||||
clearStandards();
|
||||
filename = "";
|
||||
Savable::parseJSON(j, descr);
|
||||
for(auto js : j["standards"]) {
|
||||
if(!js.contains("type") || !js.contains("params")) {
|
||||
// missing fields
|
||||
continue;
|
||||
}
|
||||
auto type = CalStandard::Virtual::TypeFromString(QString::fromStdString(js.value("type", "")));
|
||||
if(type == CalStandard::Virtual::Type::Last) {
|
||||
// failed to parse type
|
||||
continue;
|
||||
}
|
||||
auto s = CalStandard::Virtual::create(type);
|
||||
s->fromJSON(js["params"]);
|
||||
addStandard(s);
|
||||
}
|
||||
updateSCPINames();
|
||||
}
|
||||
|
||||
void Calkit::setIdealDefault()
|
||||
{
|
||||
manufacturer = "LibreVNA";
|
||||
description = "Default calibration kit with ideal standards";
|
||||
clearStandards();
|
||||
addStandard(new CalStandard::Open("Ideal Open Standard", 50.0, 0, 0, 0, 0, 0, 0));
|
||||
addStandard(new CalStandard::Short("Ideal Short Standard", 50.0, 0, 0, 0, 0, 0, 0));
|
||||
addStandard(new CalStandard::Load("Ideal Load Standard", 50.0, 0, 0, 50.0, 0, 0));
|
||||
addStandard(new CalStandard::Through("Ideal Through Standard", 50.0, 0, 0));
|
||||
updateSCPINames();
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#ifndef CALKIT_H
|
||||
#define CALKIT_H
|
||||
|
||||
#include "touchstone.h"
|
||||
#include "Util/qpointervariant.h"
|
||||
#include "calstandard.h"
|
||||
#include "savable.h"
|
||||
#include "scpi.h"
|
||||
|
||||
#include "LibreCAL/caldevice.h"
|
||||
|
||||
#include <string>
|
||||
#include <complex>
|
||||
#include <QDir>
|
||||
|
||||
class Calkit : public Savable, public SCPINode
|
||||
{
|
||||
friend class CalkitDialog;
|
||||
friend class LibreCALDialog;
|
||||
public:
|
||||
Calkit();
|
||||
Calkit(const Calkit&) = delete;
|
||||
Calkit& operator= (const Calkit&) = delete;
|
||||
|
||||
class SOLT {
|
||||
public:
|
||||
std::complex<double> Open;
|
||||
std::complex<double> Short;
|
||||
std::complex<double> Load;
|
||||
std::complex<double> ThroughS11, ThroughS12, ThroughS21, ThroughS22;
|
||||
};
|
||||
|
||||
class TRL {
|
||||
public:
|
||||
bool reflectionIsNegative;
|
||||
std::complex<double> ThroughS11, ThroughS12, ThroughS21, ThroughS22;
|
||||
};
|
||||
|
||||
void toFile(QString filename);
|
||||
bool fromFile(QString filename);
|
||||
void edit(std::function<void(void)> updateCal = nullptr);
|
||||
|
||||
std::vector<CalStandard::Virtual *> getStandards() const;
|
||||
void addStandard(CalStandard::Virtual* s);
|
||||
void removeStandard(CalStandard::Virtual* s);
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
void setIdealDefault();
|
||||
|
||||
private:
|
||||
void clearStandards();
|
||||
void updateSCPINames();
|
||||
QString manufacturer, serialnumber, description;
|
||||
QString filename;
|
||||
SCPINode scpi_std;
|
||||
std::vector<CalStandard::Virtual*> standards;
|
||||
|
||||
const std::vector<Savable::SettingDescription> descr = {{
|
||||
{&manufacturer, "Manufacturer", ""},
|
||||
{&serialnumber, "Serialnumber", ""},
|
||||
{&description, "Description", ""},
|
||||
}};
|
||||
};
|
||||
|
||||
#endif // CALKIT_H
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
#include "calkitdialog.h"
|
||||
|
||||
#include "ui_calkitdialog.h"
|
||||
#include "CustomWidgets/informationbox.h"
|
||||
#include "preferences.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include <fstream>
|
||||
#include <touchstone.h>
|
||||
#include <QtGlobal>
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
|
||||
using namespace std;
|
||||
|
||||
CalkitDialog::CalkitDialog(Calkit &c, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::CalkitDialog),
|
||||
kit(c)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
updateEntries();
|
||||
|
||||
connect(ui->bDelete, &QPushButton::clicked, [=](){
|
||||
auto row = ui->list->currentRow();
|
||||
if(row >= 0) {
|
||||
kit.removeStandard(kit.standards[row]);
|
||||
updateStandardList();
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->bMoveUp, &QPushButton::clicked, [=](){
|
||||
auto row = ui->list->currentRow();
|
||||
if(row >= 1) {
|
||||
swap(kit.standards[row], kit.standards[row-1]);
|
||||
ui->list->setCurrentRow(row-1);
|
||||
updateStandardList();
|
||||
kit.updateSCPINames();
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->bMoveDown, &QPushButton::clicked, [=](){
|
||||
auto row = ui->list->currentRow();
|
||||
if(row < ui->list->count() - 1) {
|
||||
swap(kit.standards[row], kit.standards[row+1]);
|
||||
ui->list->setCurrentRow(row+1);
|
||||
updateStandardList();
|
||||
kit.updateSCPINames();
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->list, &QListWidget::currentRowChanged, this, &CalkitDialog::updateListEditButtons);
|
||||
|
||||
auto addMenu = new QMenu();
|
||||
for(auto t : CalStandard::Virtual::availableTypes()) {
|
||||
auto action = new QAction(CalStandard::Virtual::TypeToString(t));
|
||||
connect(action, &QAction::triggered, [=](){
|
||||
auto newStandard = CalStandard::Virtual::create(t);
|
||||
if(newStandard) {
|
||||
kit.addStandard(newStandard);
|
||||
updateStandardList();
|
||||
// start the edit dialog of the newly created standard
|
||||
kit.standards.back()->edit(bind(&CalkitDialog::updateStandardList, this));
|
||||
}
|
||||
});
|
||||
addMenu->addAction(action);
|
||||
}
|
||||
|
||||
ui->bAdd->setMenu(addMenu);
|
||||
|
||||
updateStandardList();
|
||||
|
||||
connect(ui->list, &QListWidget::doubleClicked, [=](const QModelIndex &index){
|
||||
if(!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
kit.standards[index.row()]->edit(bind(&CalkitDialog::updateStandardList, this));
|
||||
});
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, [this]() {
|
||||
parseEntries();
|
||||
emit settingsChanged();
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [this]() {
|
||||
parseEntries();
|
||||
emit settingsChanged();
|
||||
accept();
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){
|
||||
auto filename = QFileDialog::getOpenFileName(this, "Open calibration kit coefficients", Preferences::getInstance().UISettings.Paths.calkit, "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.calkit = QFileInfo(filename).path();
|
||||
try {
|
||||
kit.fromFile(filename);
|
||||
} catch (runtime_error &e) {
|
||||
InformationBox::ShowError("Error", "The calibration kit file could not be parsed (" + QString(e.what()) + ")");
|
||||
qWarning() << "Parsing of calibration kit failed while opening calibration file: " << e.what();
|
||||
}
|
||||
updateEntries();
|
||||
updateStandardList();
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Save calibration kit coefficients", Preferences::getInstance().UISettings.Paths.calkit, "Calibration kit files (*.calkit)", nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.calkit = QFileInfo(filename).path();
|
||||
parseEntries();
|
||||
kit.toFile(filename);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CalkitDialog::~CalkitDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void CalkitDialog::updateListEditButtons()
|
||||
{
|
||||
ui->bDelete->setEnabled(ui->list->currentRow() >= 0);
|
||||
ui->bMoveUp->setEnabled(ui->list->currentRow() >= 1);
|
||||
ui->bMoveDown->setEnabled(ui->list->currentRow() >= 0 && ui->list->currentRow() < ui->list->count() - 1);
|
||||
}
|
||||
|
||||
void CalkitDialog::parseEntries()
|
||||
{
|
||||
kit.manufacturer = ui->manufacturer->text();
|
||||
kit.serialnumber = ui->serialnumber->text();
|
||||
kit.description = ui->description->toPlainText();
|
||||
}
|
||||
|
||||
void CalkitDialog::updateEntries()
|
||||
{
|
||||
ui->manufacturer->setText(kit.manufacturer);
|
||||
ui->serialnumber->setText(kit.serialnumber);
|
||||
ui->description->setPlainText(kit.description);
|
||||
}
|
||||
|
||||
void CalkitDialog::updateStandardList()
|
||||
{
|
||||
auto row = ui->list->currentRow();
|
||||
ui->list->clear();
|
||||
for(auto s : kit.standards) {
|
||||
ui->list->addItem(s->getDescription());
|
||||
}
|
||||
if(row >= 0) {
|
||||
if(row < ui->list->count()) {
|
||||
ui->list->setCurrentRow(row);
|
||||
} else if(ui->list->count() > 0) {
|
||||
ui->list->setCurrentRow(ui->list->count() - 1);
|
||||
}
|
||||
}
|
||||
updateListEditButtons();
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#ifndef CALKITDIALOG_H
|
||||
#define CALKITDIALOG_H
|
||||
|
||||
#include "calkit.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QAbstractButton>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Ui {
|
||||
class CalkitDialog;
|
||||
}
|
||||
|
||||
class CalkitDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CalkitDialog(Calkit &c, QWidget *parent = nullptr);
|
||||
~CalkitDialog();
|
||||
|
||||
signals:
|
||||
void settingsChanged();
|
||||
private slots:
|
||||
void updateListEditButtons();
|
||||
private:
|
||||
void parseEntries();
|
||||
void updateEntries();
|
||||
void updateStandardList();
|
||||
Ui::CalkitDialog *ui;
|
||||
|
||||
Calkit &kit;
|
||||
};
|
||||
|
||||
#endif // CALKITDIALOG_H
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
<?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>648</width>
|
||||
<height>530</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_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_7">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_27">
|
||||
<property name="text">
|
||||
<string>Manufacturer:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="manufacturer"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_31">
|
||||
<property name="text">
|
||||
<string>Serial number:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="serialnumber"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_28">
|
||||
<property name="text">
|
||||
<string>Description:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<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>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="description"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Standards</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="list"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="bAdd">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" 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" resource="../icons.qrc">
|
||||
<normaloff>:/icons/add.png</normaloff>:/icons/add.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bDelete">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" 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" resource="../icons.qrc">
|
||||
<normaloff>:/icons/remove.png</normaloff>:/icons/remove.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bMoveUp">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Move up</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-up" resource="../icons.qrc">
|
||||
<normaloff>:/icons/up.png</normaloff>:/icons/up.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bMoveDown">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Move down</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-down" resource="../icons.qrc">
|
||||
<normaloff>:/icons/down.png</normaloff>:/icons/down.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>18</width>
|
||||
<height>186</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Ok|QDialogButtonBox::Open|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
+920
@@ -0,0 +1,920 @@
|
||||
#include "calstandard.h"
|
||||
#include "ui_CalStandardOpenEditDialog.h"
|
||||
#include "ui_CalStandardShortEditDialog.h"
|
||||
#include "ui_CalStandardLoadEditDialog.h"
|
||||
#include "ui_CalStandardReflectEditDialog.h"
|
||||
#include "ui_CalStandardThroughEditDialog.h"
|
||||
#include "ui_CalStandardLineEditDialog.h"
|
||||
#include "unit.h"
|
||||
#include "Util/util.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace CalStandard;
|
||||
|
||||
Virtual::Virtual(QString name) :
|
||||
SCPINode(name),
|
||||
name(name),
|
||||
minFreq(std::numeric_limits<double>::lowest()),
|
||||
maxFreq(std::numeric_limits<double>::max())
|
||||
{
|
||||
id = Util::random(numeric_limits<unsigned long long>::max());
|
||||
}
|
||||
|
||||
Virtual *Virtual::create(Virtual::Type type)
|
||||
{
|
||||
switch(type) {
|
||||
case Type::Open: return new Open;
|
||||
case Type::Short: return new Short;
|
||||
case Type::Load: return new Load;
|
||||
case Type::Reflect: return new Reflect;
|
||||
case Type::Through: return new Through;
|
||||
case Type::Line: return new Line;
|
||||
case Type::Last:
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<Virtual::Type> Virtual::availableTypes()
|
||||
{
|
||||
std::vector<Type> ret;
|
||||
for(int i=0;i<(int) Type::Last;i++) {
|
||||
ret.push_back((Type) i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString Virtual::TypeToString(Virtual::Type type)
|
||||
{
|
||||
switch(type) {
|
||||
case Type::Open: return "Open";
|
||||
case Type::Short: return "Short";
|
||||
case Type::Load: return "Load";
|
||||
case Type::Reflect: return "Reflect";
|
||||
case Type::Through: return "Through";
|
||||
case Type::Line: return "Line";
|
||||
case Type::Last: return "Invalid";
|
||||
}
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
Virtual::Type Virtual::TypeFromString(QString s)
|
||||
{
|
||||
for(int i=0;i<(int) Type::Last;i++) {
|
||||
if(TypeToString((Type) i).compare(s, Qt::CaseInsensitive) == 0) {
|
||||
return (Type) i;
|
||||
}
|
||||
}
|
||||
return Type::Last;
|
||||
}
|
||||
|
||||
QString Virtual::getDescription()
|
||||
{
|
||||
return TypeToString(getType())+", "+name;
|
||||
}
|
||||
|
||||
nlohmann::json Virtual::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["name"] = name.toStdString();
|
||||
j["id"] = id;
|
||||
return j;
|
||||
}
|
||||
|
||||
void Virtual::fromJSON(nlohmann::json j)
|
||||
{
|
||||
name = QString::fromStdString(j.value("name", ""));
|
||||
id = j.value("id", id);
|
||||
}
|
||||
|
||||
unsigned long long Virtual::getID()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
QString Virtual::getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
void Virtual::setName(const QString &value)
|
||||
{
|
||||
name = value;
|
||||
}
|
||||
|
||||
void Virtual::setupSCPI()
|
||||
{
|
||||
addStringParameter("NAME", name);
|
||||
}
|
||||
|
||||
void OnePort::setMeasurement(const Touchstone &ts, int port)
|
||||
{
|
||||
if(!touchstone) {
|
||||
touchstone = new Touchstone(ts);
|
||||
} else {
|
||||
*touchstone = ts;
|
||||
}
|
||||
if(touchstone->ports() > 1) {
|
||||
touchstone->reduceTo1Port(port);
|
||||
}
|
||||
minFreq = touchstone->minFreq();
|
||||
maxFreq = touchstone->maxFreq();
|
||||
}
|
||||
|
||||
void OnePort::clearMeasurement()
|
||||
{
|
||||
delete touchstone;
|
||||
touchstone = nullptr;
|
||||
minFreq = std::numeric_limits<double>::lowest();
|
||||
maxFreq = std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
nlohmann::json OnePort::toJSON()
|
||||
{
|
||||
auto j = Virtual::toJSON();
|
||||
if(touchstone) {
|
||||
j["touchstone"] = touchstone->toJSON();
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void OnePort::fromJSON(nlohmann::json j)
|
||||
{
|
||||
Virtual::fromJSON(j);
|
||||
if(j.contains("touchstone")) {
|
||||
Touchstone ts(1);
|
||||
ts.fromJSON(j["touchstone"]);
|
||||
setMeasurement(ts);
|
||||
}
|
||||
}
|
||||
|
||||
void OnePort::setupSCPI()
|
||||
{
|
||||
add(new SCPICommand("FILE", [=](QStringList params) -> QString {
|
||||
if(params.size() < 1) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
try {
|
||||
auto ts = Touchstone::fromFile(params[0].toStdString());
|
||||
unsigned long long index = 0;
|
||||
if(params.size() == 2) {
|
||||
if(!SCPI::paramToULongLong(params, 1, index)) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
}
|
||||
setMeasurement(ts, index-1);
|
||||
return SCPI::getResultName(SCPI::Result::Empty);
|
||||
} catch(const std::exception& e) {
|
||||
// failed to load file
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
}, nullptr, false));
|
||||
Virtual::setupSCPI();
|
||||
}
|
||||
|
||||
Open::Open()
|
||||
{
|
||||
Z0 = 50.0;
|
||||
delay = loss = C0 = C1 = C2 = C3 = 0.0;
|
||||
setupSCPI();
|
||||
}
|
||||
|
||||
std::complex<double> Open::toS11(double freq)
|
||||
{
|
||||
if(touchstone) {
|
||||
return touchstone->interpolate(freq).S[0];
|
||||
} else {
|
||||
// calculate fringing capacitance for open
|
||||
double Cfringing = C0 * 1e-15 + C1 * 1e-27 * freq + C2 * 1e-36 * pow(freq, 2) + C3 * 1e-45 * pow(freq, 3);
|
||||
// convert to impedance
|
||||
complex<double> open;
|
||||
if (Cfringing == 0) {
|
||||
// special case to avoid issues with infinity
|
||||
open = complex<double>(1.0, 0);
|
||||
} else {
|
||||
auto imp_open = complex<double>(0, -1.0 / (freq * 2 * M_PI * Cfringing));
|
||||
open = (imp_open - complex<double>(50.0)) / (imp_open + complex<double>(50.0));
|
||||
}
|
||||
return Util::addTransmissionLine(open, Z0, delay*1e-12, loss*1e9, freq);
|
||||
}
|
||||
}
|
||||
|
||||
void Open::edit(std::function<void(void)> finishedCallback)
|
||||
{
|
||||
auto d = new QDialog;
|
||||
auto ui = new Ui::CalStandardOpenEditDialog;
|
||||
ui->setupUi(d);
|
||||
|
||||
ui->name->setText(name);
|
||||
ui->Z0->setUnit("Ω");
|
||||
ui->Z0->setPrecision(4);
|
||||
ui->Z0->setValue(Z0);
|
||||
ui->delay->setValue(delay);
|
||||
ui->loss->setValue(loss);
|
||||
ui->C0->setValue(C0);
|
||||
ui->C1->setValue(C1);
|
||||
ui->C2->setValue(C2);
|
||||
ui->C3->setValue(C3);
|
||||
|
||||
auto updateMeasurementLabel = [=](){
|
||||
QString label;
|
||||
if(touchstone) {
|
||||
label = QString::number(touchstone->points())+" points from "+Unit::ToString(touchstone->minFreq(), "Hz", " kMG")+" to "+Unit::ToString(touchstone->maxFreq(), "Hz", " kMG");
|
||||
} else {
|
||||
label = "No measurements stored yet";
|
||||
}
|
||||
ui->measurementLabel->setText(label);
|
||||
};
|
||||
|
||||
QObject::connect(ui->coefficients, &QRadioButton::toggled, [=](bool checked) {
|
||||
if(checked) {
|
||||
clearMeasurement();
|
||||
}
|
||||
ui->stackedWidget->setCurrentIndex(checked ? 0 : 1);
|
||||
});
|
||||
QObject::connect(ui->measurement, &QRadioButton::toggled, [=](bool checked) {
|
||||
updateMeasurementLabel();
|
||||
ui->stackedWidget->setCurrentIndex(checked ? 1 : 0);
|
||||
});
|
||||
|
||||
QObject::connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, ui->updateFile, &QPushButton::setEnabled);
|
||||
|
||||
ui->touchstoneImport->setPorts(1);
|
||||
if(touchstone) {
|
||||
ui->measurement->setChecked(true);
|
||||
ui->touchstoneImport->setFile(touchstone->getFilename());
|
||||
} else {
|
||||
ui->coefficients->setChecked(true);
|
||||
}
|
||||
|
||||
QObject::connect(ui->updateFile, &QPushButton::clicked, [=](){
|
||||
setMeasurement(ui->touchstoneImport->getTouchstone(), ui->touchstoneImport->getPorts()[0]);
|
||||
updateMeasurementLabel();
|
||||
});
|
||||
|
||||
QObject::connect(d, &QDialog::accepted, [=](){
|
||||
name = ui->name->text();
|
||||
Z0 = ui->Z0->value();
|
||||
delay = ui->delay->value();
|
||||
loss = ui->loss->value();
|
||||
C0 = ui->C0->value();
|
||||
C1 = ui->C1->value();
|
||||
C2 = ui->C2->value();
|
||||
C3 = ui->C3->value();
|
||||
if(finishedCallback) {
|
||||
finishedCallback();
|
||||
}
|
||||
});
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
nlohmann::json Open::toJSON()
|
||||
{
|
||||
auto j = OnePort::toJSON();
|
||||
j["Z0"] = Z0;
|
||||
j["delay"] = delay;
|
||||
j["loss"] = loss;
|
||||
j["C0"] = C0;
|
||||
j["C1"] = C1;
|
||||
j["C2"] = C2;
|
||||
j["C3"] = C3;
|
||||
return j;
|
||||
}
|
||||
|
||||
void Open::fromJSON(nlohmann::json j)
|
||||
{
|
||||
OnePort::fromJSON(j);
|
||||
Z0 = j.value("Z0", 50.0);
|
||||
delay = j.value("delay", 0.0);
|
||||
loss = j.value("loss", 0.0);
|
||||
C0 = j.value("C0", 0.0);
|
||||
C1 = j.value("C1", 0.0);
|
||||
C2 = j.value("C2", 0.0);
|
||||
C3 = j.value("C3", 0.0);
|
||||
}
|
||||
|
||||
void Open::setupSCPI()
|
||||
{
|
||||
addDoubleParameter("Z0", Z0, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("DELAY", delay, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("LOSS", loss, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("C0", C0, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("C1", C1, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("C2", C2, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("C3", C3, true, true, [=](){clearMeasurement();});
|
||||
OnePort::setupSCPI();
|
||||
}
|
||||
|
||||
Short::Short()
|
||||
{
|
||||
Z0 = 50.0;
|
||||
delay = loss = L0 = L1 = L2 = L3 = 0.0;
|
||||
setupSCPI();
|
||||
}
|
||||
|
||||
std::complex<double> Short::toS11(double freq)
|
||||
{
|
||||
if(touchstone) {
|
||||
return touchstone->interpolate(freq).S[0];
|
||||
} else {
|
||||
// calculate inductance for short
|
||||
double Lseries = L0 * 1e-12 + L1 * 1e-24 * freq + L2 * 1e-33 * pow(freq, 2) + L3 * 1e-42 * pow(freq, 3);
|
||||
// convert to impedance
|
||||
auto imp_short = complex<double>(0, freq * 2 * M_PI * Lseries);
|
||||
complex<double> _short = (imp_short - complex<double>(50.0)) / (imp_short + complex<double>(50.0));
|
||||
return Util::addTransmissionLine(_short, Z0, delay*1e-12, loss*1e9, freq);
|
||||
}
|
||||
}
|
||||
|
||||
void Short::edit(std::function<void(void)> finishedCallback)
|
||||
{
|
||||
auto d = new QDialog;
|
||||
auto ui = new Ui::CalStandardShortEditDialog;
|
||||
ui->setupUi(d);
|
||||
|
||||
ui->name->setText(name);
|
||||
ui->Z0->setUnit("Ω");
|
||||
ui->Z0->setPrecision(4);
|
||||
ui->Z0->setValue(Z0);
|
||||
ui->delay->setValue(delay);
|
||||
ui->loss->setValue(loss);
|
||||
ui->L0->setValue(L0);
|
||||
ui->L1->setValue(L1);
|
||||
ui->L2->setValue(L2);
|
||||
ui->L3->setValue(L3);
|
||||
|
||||
auto updateMeasurementLabel = [=](){
|
||||
QString label;
|
||||
if(touchstone) {
|
||||
label = QString::number(touchstone->points())+" points from "+Unit::ToString(touchstone->minFreq(), "Hz", " kMG")+" to "+Unit::ToString(touchstone->maxFreq(), "Hz", " kMG");
|
||||
} else {
|
||||
label = "No measurements stored yet";
|
||||
}
|
||||
ui->measurementLabel->setText(label);
|
||||
};
|
||||
|
||||
QObject::connect(ui->coefficients, &QRadioButton::toggled, [=](bool checked) {
|
||||
if(checked) {
|
||||
clearMeasurement();
|
||||
}
|
||||
ui->stackedWidget->setCurrentIndex(checked ? 0 : 1);
|
||||
});
|
||||
QObject::connect(ui->measurement, &QRadioButton::toggled, [=](bool checked) {
|
||||
updateMeasurementLabel();
|
||||
ui->stackedWidget->setCurrentIndex(checked ? 1 : 0);
|
||||
});
|
||||
|
||||
QObject::connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, ui->updateFile, &QPushButton::setEnabled);
|
||||
|
||||
ui->touchstoneImport->setPorts(1);
|
||||
if(touchstone) {
|
||||
ui->measurement->setChecked(true);
|
||||
ui->touchstoneImport->setFile(touchstone->getFilename());
|
||||
} else {
|
||||
ui->coefficients->setChecked(true);
|
||||
}
|
||||
|
||||
QObject::connect(ui->updateFile, &QPushButton::clicked, [=](){
|
||||
setMeasurement(ui->touchstoneImport->getTouchstone(), ui->touchstoneImport->getPorts()[0]);
|
||||
updateMeasurementLabel();
|
||||
});
|
||||
|
||||
QObject::connect(d, &QDialog::accepted, [=](){
|
||||
name = ui->name->text();
|
||||
Z0 = ui->Z0->value();
|
||||
delay = ui->delay->value();
|
||||
loss = ui->loss->value();
|
||||
L0 = ui->L0->value();
|
||||
L1 = ui->L1->value();
|
||||
L2 = ui->L2->value();
|
||||
L3 = ui->L3->value();
|
||||
if(finishedCallback) {
|
||||
finishedCallback();
|
||||
}
|
||||
});
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
nlohmann::json Short::toJSON()
|
||||
{
|
||||
auto j = OnePort::toJSON();
|
||||
j["Z0"] = Z0;
|
||||
j["delay"] = delay;
|
||||
j["loss"] = loss;
|
||||
j["L0"] = L0;
|
||||
j["L1"] = L1;
|
||||
j["L2"] = L2;
|
||||
j["L3"] = L3;
|
||||
return j;
|
||||
}
|
||||
|
||||
void Short::fromJSON(nlohmann::json j)
|
||||
{
|
||||
OnePort::fromJSON(j);
|
||||
Z0 = j.value("Z0", 50.0);
|
||||
delay = j.value("delay", 0.0);
|
||||
loss = j.value("loss", 0.0);
|
||||
L0 = j.value("L0", 0.0);
|
||||
L1 = j.value("L1", 0.0);
|
||||
L2 = j.value("L2", 0.0);
|
||||
L3 = j.value("L3", 0.0);
|
||||
}
|
||||
|
||||
void Short::setupSCPI()
|
||||
{
|
||||
addDoubleParameter("Z0", Z0, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("DELAY", delay, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("LOSS", loss, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("L0", L0, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("L1", L1, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("L2", L2, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("L3", L3, true, true, [=](){clearMeasurement();});
|
||||
OnePort::setupSCPI();
|
||||
}
|
||||
|
||||
Load::Load()
|
||||
{
|
||||
Z0 = 50.0;
|
||||
resistance = 50.0;
|
||||
delay = Cparallel = Lseries = 0;
|
||||
Cfirst = true;
|
||||
setupSCPI();
|
||||
}
|
||||
|
||||
std::complex<double> Load::toS11(double freq)
|
||||
{
|
||||
if(touchstone) {
|
||||
return touchstone->interpolate(freq).S[0];
|
||||
} else {
|
||||
auto imp_load = complex<double>(resistance, 0);
|
||||
if (Cfirst) {
|
||||
// C is the first parameter starting from the VNA port. But the load is modeled here starting from
|
||||
// the other end, so we need to start with the inductor
|
||||
imp_load += complex<double>(0, freq * 2 * M_PI * Lseries);
|
||||
}
|
||||
// Add parallel capacitor to impedance
|
||||
if(Cparallel > 0) {
|
||||
auto imp_C = complex<double>(0, -1.0 / (freq * 2 * M_PI * Cparallel));
|
||||
imp_load = (imp_load * imp_C) / (imp_load + imp_C);
|
||||
}
|
||||
if (!Cfirst) {
|
||||
// inductor not added yet, do so now
|
||||
imp_load += complex<double>(0, freq * 2 * M_PI * Lseries);
|
||||
}
|
||||
complex<double> load = (imp_load - complex<double>(50.0)) / (imp_load + complex<double>(50.0));
|
||||
return Util::addTransmissionLine(load, Z0, delay*1e-12, loss*1e9, freq);
|
||||
}
|
||||
}
|
||||
|
||||
void Load::edit(std::function<void(void)> finishedCallback)
|
||||
{
|
||||
auto d = new QDialog;
|
||||
auto ui = new Ui::CalStandardLoadEditDialog;
|
||||
ui->setupUi(d);
|
||||
|
||||
ui->name->setText(name);
|
||||
ui->resistance->setUnit("Ω");
|
||||
ui->resistance->setPrecision(4);
|
||||
ui->resistance->setValue(resistance);
|
||||
ui->Z0->setUnit("Ω");
|
||||
ui->Z0->setPrecision(4);
|
||||
ui->Z0->setValue(Z0);
|
||||
ui->delay->setValue(delay);
|
||||
ui->loss->setValue(loss);
|
||||
ui->parC->setUnit("F");
|
||||
ui->parC->setPrefixes("pnum ");
|
||||
ui->parC->setPrecision(4);
|
||||
ui->parC->setValue(Cparallel);
|
||||
ui->serL->setUnit("H");
|
||||
ui->serL->setPrefixes("num ");
|
||||
ui->serL->setPrecision(4);
|
||||
ui->serL->setValue(Lseries);
|
||||
|
||||
if(Cfirst) {
|
||||
ui->C_first->setChecked(true);
|
||||
} else {
|
||||
ui->L_first->setChecked(true);
|
||||
}
|
||||
|
||||
auto updateMeasurementLabel = [=](){
|
||||
QString label;
|
||||
if(touchstone) {
|
||||
label = QString::number(touchstone->points())+" points from "+Unit::ToString(touchstone->minFreq(), "Hz", " kMG")+" to "+Unit::ToString(touchstone->maxFreq(), "Hz", " kMG");
|
||||
} else {
|
||||
label = "No measurements stored yet";
|
||||
}
|
||||
ui->measurementLabel->setText(label);
|
||||
};
|
||||
|
||||
QObject::connect(ui->coefficients, &QRadioButton::toggled, [=](bool checked) {
|
||||
if(checked) {
|
||||
clearMeasurement();
|
||||
}
|
||||
ui->stackedWidget->setCurrentIndex(checked ? 0 : 1);
|
||||
});
|
||||
QObject::connect(ui->measurement, &QRadioButton::toggled, [=](bool checked) {
|
||||
updateMeasurementLabel();
|
||||
ui->stackedWidget->setCurrentIndex(checked ? 1 : 0);
|
||||
});
|
||||
|
||||
QObject::connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, ui->updateFile, &QPushButton::setEnabled);
|
||||
|
||||
ui->touchstoneImport->setPorts(1);
|
||||
if(touchstone) {
|
||||
ui->measurement->setChecked(true);
|
||||
ui->touchstoneImport->setFile(touchstone->getFilename());
|
||||
} else {
|
||||
ui->coefficients->setChecked(true);
|
||||
}
|
||||
|
||||
QObject::connect(ui->updateFile, &QPushButton::clicked, [=](){
|
||||
setMeasurement(ui->touchstoneImport->getTouchstone(), ui->touchstoneImport->getPorts()[0]);
|
||||
updateMeasurementLabel();
|
||||
});
|
||||
|
||||
QObject::connect(d, &QDialog::accepted, [=](){
|
||||
name = ui->name->text();
|
||||
resistance = ui->resistance->value();
|
||||
Z0 = ui->Z0->value();
|
||||
delay = ui->delay->value();
|
||||
loss = ui->loss->value();
|
||||
Cparallel = ui->parC->value();
|
||||
Lseries = ui->serL->value();
|
||||
Cfirst = ui->C_first->isChecked();
|
||||
if(finishedCallback) {
|
||||
finishedCallback();
|
||||
}
|
||||
});
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
nlohmann::json Load::toJSON()
|
||||
{
|
||||
auto j = OnePort::toJSON();
|
||||
j["Z0"] = Z0;
|
||||
j["delay"] = delay;
|
||||
j["loss"] = loss;
|
||||
j["resistance"] = resistance;
|
||||
j["Cparallel"] = Cparallel;
|
||||
j["Lseries"] = Lseries;
|
||||
j["Cfirst"] = Cfirst;
|
||||
return j;
|
||||
}
|
||||
|
||||
void Load::fromJSON(nlohmann::json j)
|
||||
{
|
||||
OnePort::fromJSON(j);
|
||||
Z0 = j.value("Z0", 50.0);
|
||||
delay = j.value("delay", 0.0);
|
||||
loss = j.value("loss", 0.0);
|
||||
resistance = j.value("resistance", 0.0);
|
||||
Cparallel = j.value("Cparallel", 0.0);
|
||||
Lseries = j.value("Lseries", 0.0);
|
||||
Cfirst = j.value("Cfirst", true);
|
||||
}
|
||||
|
||||
void Load::setupSCPI()
|
||||
{
|
||||
addDoubleParameter("Z0", Z0, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("DELAY", delay, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("LOSS", loss, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("RESistance", resistance, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("CPARallel", Cparallel, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("LSERies", Lseries, true, true, [=](){clearMeasurement();});
|
||||
addBoolParameter("CFIRST", Cfirst, true, true, [=](){clearMeasurement();});
|
||||
OnePort::setupSCPI();
|
||||
}
|
||||
|
||||
void TwoPort::setMeasurement(const Touchstone &ts, int port1, int port2)
|
||||
{
|
||||
if(!touchstone) {
|
||||
touchstone = new Touchstone(ts);
|
||||
} else {
|
||||
*touchstone = ts;
|
||||
}
|
||||
if(touchstone->ports() > 2) {
|
||||
touchstone->reduceTo2Port(port1, port2);
|
||||
}
|
||||
minFreq = touchstone->minFreq();
|
||||
maxFreq = touchstone->maxFreq();
|
||||
}
|
||||
|
||||
void TwoPort::clearMeasurement()
|
||||
{
|
||||
delete touchstone;
|
||||
touchstone = nullptr;
|
||||
minFreq = std::numeric_limits<double>::lowest();
|
||||
maxFreq = std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
nlohmann::json TwoPort::toJSON()
|
||||
{
|
||||
auto j = Virtual::toJSON();
|
||||
if(touchstone) {
|
||||
j["touchstone"] = touchstone->toJSON();
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void TwoPort::fromJSON(nlohmann::json j)
|
||||
{
|
||||
Virtual::fromJSON(j);
|
||||
if(j.contains("touchstone")) {
|
||||
Touchstone ts(1);
|
||||
ts.fromJSON(j["touchstone"]);
|
||||
setMeasurement(ts);
|
||||
}
|
||||
}
|
||||
|
||||
void TwoPort::setupSCPI()
|
||||
{
|
||||
add(new SCPICommand("FILE", [=](QStringList params) -> QString {
|
||||
if(params.size() != 3) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
try {
|
||||
auto ts = Touchstone::fromFile(params[0].toStdString());
|
||||
unsigned long long index1, index2 = 0;
|
||||
if(!SCPI::paramToULongLong(params, 1, index1)) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
if(!SCPI::paramToULongLong(params, 2, index1)) {
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
setMeasurement(ts, index1-1, index2-1);
|
||||
return SCPI::getResultName(SCPI::Result::Empty);
|
||||
} catch(const std::exception& e) {
|
||||
// failed to load file
|
||||
return SCPI::getResultName(SCPI::Result::Error);
|
||||
}
|
||||
}, nullptr, false));
|
||||
Virtual::setupSCPI();
|
||||
}
|
||||
|
||||
Through::Through()
|
||||
{
|
||||
Z0 = 50.0;
|
||||
delay = 0.0;
|
||||
loss = 0.0;
|
||||
setupSCPI();
|
||||
}
|
||||
|
||||
Sparam Through::toSparam(double freq)
|
||||
{
|
||||
if(touchstone) {
|
||||
auto interp = touchstone->interpolate(freq).S;
|
||||
return Sparam(interp[0], interp[1], interp[2], interp[3]);
|
||||
} else {
|
||||
// calculate effect of through
|
||||
// nomenclature and formulas from https://loco.lab.asu.edu/loco-memos/edges_reports/report_20130807.pdf
|
||||
auto w = 2.0 * M_PI * freq;
|
||||
auto f_sqrt = sqrt(freq / 1e9);
|
||||
auto offset_impedance = Z0; // lossless characteristic impedance
|
||||
auto offset_loss = loss * 1e9;
|
||||
auto offset_delay = delay * 1e-12;
|
||||
|
||||
auto Z_c = std::complex<double>(offset_impedance + (offset_loss / (2*w)) * f_sqrt, -(offset_loss / (2*w)) * f_sqrt);
|
||||
auto gamma_l = std::complex<double>(offset_loss*offset_delay/(2*offset_impedance)*f_sqrt, w*offset_delay+offset_loss*offset_delay/(2*offset_impedance)*f_sqrt);
|
||||
auto p = exp(-gamma_l); // propagation factor
|
||||
|
||||
auto Z_r = std::complex<double>(50.0);
|
||||
auto Gamma = (Z_c - Z_r) / (Z_c + Z_r);
|
||||
|
||||
// S-parameter of a transmission line, see e.g. "Specifying Calibration Standards and Kits for Keysight Vector Network Analyzers"
|
||||
// Keysight application note equations (6) and (7)
|
||||
auto Sxx = Gamma * (1.0 - p*p) / (1.0 - p*p*Gamma*Gamma);
|
||||
auto Sxy = p * (1.0 - Gamma*Gamma) / (1.0 - p*p*Gamma*Gamma);
|
||||
return Sparam(Sxx, Sxy, Sxy, Sxx);
|
||||
}
|
||||
}
|
||||
|
||||
void Through::edit(std::function<void(void)> finishedCallback)
|
||||
{
|
||||
auto d = new QDialog;
|
||||
auto ui = new Ui::CalStandardThroughEditDialog;
|
||||
ui->setupUi(d);
|
||||
|
||||
ui->name->setText(name);
|
||||
ui->Z0->setUnit("Ω");
|
||||
ui->Z0->setPrecision(4);
|
||||
ui->Z0->setValue(Z0);
|
||||
ui->delay->setValue(delay);
|
||||
ui->loss->setValue(loss);
|
||||
|
||||
auto updateMeasurementLabel = [=](){
|
||||
QString label;
|
||||
if(touchstone) {
|
||||
label = QString::number(touchstone->points())+" points from "+Unit::ToString(touchstone->minFreq(), "Hz", " kMG")+" to "+Unit::ToString(touchstone->maxFreq(), "Hz", " kMG");
|
||||
} else {
|
||||
label = "No measurements stored yet";
|
||||
}
|
||||
ui->measurementLabel->setText(label);
|
||||
};
|
||||
|
||||
QObject::connect(ui->coefficients, &QRadioButton::toggled, [=](bool checked) {
|
||||
if(checked) {
|
||||
clearMeasurement();
|
||||
}
|
||||
ui->stackedWidget->setCurrentIndex(checked ? 0 : 1);
|
||||
});
|
||||
QObject::connect(ui->measurement, &QRadioButton::toggled, [=](bool checked) {
|
||||
updateMeasurementLabel();
|
||||
ui->stackedWidget->setCurrentIndex(checked ? 1 : 0);
|
||||
});
|
||||
|
||||
QObject::connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, ui->updateFile, &QPushButton::setEnabled);
|
||||
|
||||
ui->touchstoneImport->setPorts(2);
|
||||
if(touchstone) {
|
||||
ui->measurement->setChecked(true);
|
||||
ui->touchstoneImport->setFile(touchstone->getFilename());
|
||||
} else {
|
||||
ui->coefficients->setChecked(true);
|
||||
}
|
||||
|
||||
QObject::connect(ui->updateFile, &QPushButton::clicked, [=](){
|
||||
setMeasurement(ui->touchstoneImport->getTouchstone(), ui->touchstoneImport->getPorts()[0], ui->touchstoneImport->getPorts()[1]);
|
||||
updateMeasurementLabel();
|
||||
});
|
||||
|
||||
QObject::connect(d, &QDialog::accepted, [=](){
|
||||
name = ui->name->text();
|
||||
Z0 = ui->Z0->value();
|
||||
delay = ui->delay->value();
|
||||
loss = ui->loss->value();
|
||||
if(finishedCallback) {
|
||||
finishedCallback();
|
||||
}
|
||||
});
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
nlohmann::json Through::toJSON()
|
||||
{
|
||||
auto j = TwoPort::toJSON();
|
||||
j["Z0"] = Z0;
|
||||
j["delay"] = delay;
|
||||
j["loss"] = loss;
|
||||
return j;
|
||||
}
|
||||
|
||||
void Through::fromJSON(nlohmann::json j)
|
||||
{
|
||||
TwoPort::fromJSON(j);
|
||||
Z0 = j.value("Z0", 50.0);
|
||||
delay = j.value("delay", 0.0);
|
||||
loss = j.value("loss", 0.0);
|
||||
}
|
||||
|
||||
void Through::setupSCPI()
|
||||
{
|
||||
addDoubleParameter("Z0", Z0, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("DELAY", delay, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("LOSS", loss, true, true, [=](){clearMeasurement();});
|
||||
TwoPort::setupSCPI();
|
||||
}
|
||||
|
||||
Reflect::Reflect()
|
||||
{
|
||||
isShort = true;
|
||||
setupSCPI();
|
||||
}
|
||||
|
||||
std::complex<double> Reflect::toS11(double freq)
|
||||
{
|
||||
Q_UNUSED(freq)
|
||||
return std::numeric_limits<complex<double>>::quiet_NaN();
|
||||
}
|
||||
|
||||
void Reflect::edit(std::function<void ()> finishedCallback)
|
||||
{
|
||||
auto d = new QDialog;
|
||||
auto ui = new Ui::CalStandardReflectEditDialog;
|
||||
ui->setupUi(d);
|
||||
|
||||
ui->name->setText(name);
|
||||
ui->type->setCurrentIndex(isShort ? 1 : 0);
|
||||
|
||||
QObject::connect(d, &QDialog::accepted, [=](){
|
||||
name = ui->name->text();
|
||||
isShort = ui->type->currentIndex() == 1;
|
||||
if(finishedCallback) {
|
||||
finishedCallback();
|
||||
}
|
||||
});
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
nlohmann::json Reflect::toJSON()
|
||||
{
|
||||
auto j = OnePort::toJSON();
|
||||
j["isShort"] = isShort;
|
||||
return j;
|
||||
}
|
||||
|
||||
void Reflect::fromJSON(nlohmann::json j)
|
||||
{
|
||||
OnePort::fromJSON(j);
|
||||
isShort = j.value("isShort", true);
|
||||
}
|
||||
|
||||
bool Reflect::getIsShort() const
|
||||
{
|
||||
return isShort;
|
||||
}
|
||||
|
||||
void Reflect::setupSCPI()
|
||||
{
|
||||
addBoolParameter("SHORT", isShort, true, true, [=](){clearMeasurement();});
|
||||
OnePort::setupSCPI();
|
||||
}
|
||||
|
||||
Line::Line()
|
||||
{
|
||||
Z0 = 50.0;
|
||||
setDelay(0.0);
|
||||
setupSCPI();
|
||||
}
|
||||
|
||||
Sparam Line::toSparam(double freq)
|
||||
{
|
||||
Q_UNUSED(freq)
|
||||
Sparam ret;
|
||||
ret.set(1,1,numeric_limits<complex<double>>::quiet_NaN());
|
||||
ret.set(1,2,numeric_limits<complex<double>>::quiet_NaN());
|
||||
ret.set(2,1,numeric_limits<complex<double>>::quiet_NaN());
|
||||
ret.set(2,2,numeric_limits<complex<double>>::quiet_NaN());
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Line::edit(std::function<void ()> finishedCallback)
|
||||
{
|
||||
auto d = new QDialog;
|
||||
auto ui = new Ui::CalStandardLineEditDialog;
|
||||
ui->setupUi(d);
|
||||
|
||||
ui->name->setText(name);
|
||||
ui->Z0->setUnit("Ω");
|
||||
ui->Z0->setPrecision(4);
|
||||
ui->Z0->setValue(Z0);
|
||||
ui->delay->setValue(delay);
|
||||
ui->delay->setUnit("s");
|
||||
ui->delay->setPrefixes("pnum ");
|
||||
ui->delay->setPrecision(4);
|
||||
ui->minFreq->setUnit("Hz");
|
||||
ui->minFreq->setPrecision(4);
|
||||
ui->minFreq->setPrefixes(" kMG");
|
||||
ui->minFreq->setValue(minFreq);
|
||||
ui->maxFreq->setUnit("Hz");
|
||||
ui->maxFreq->setPrecision(4);
|
||||
ui->maxFreq->setPrefixes(" kMG");
|
||||
ui->maxFreq->setValue(maxFreq);
|
||||
|
||||
QObject::connect(ui->delay, &SIUnitEdit::valueChanged, [=](double val){
|
||||
ui->minFreq->setValue(1.0 / val * 20 / 360);
|
||||
ui->maxFreq->setValue(1.0 / val * 160 / 360);
|
||||
});
|
||||
|
||||
QObject::connect(d, &QDialog::accepted, [=](){
|
||||
name = ui->name->text();
|
||||
Z0 = ui->Z0->value();
|
||||
setDelay(ui->delay->value());
|
||||
if(finishedCallback) {
|
||||
finishedCallback();
|
||||
}
|
||||
});
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
nlohmann::json Line::toJSON()
|
||||
{
|
||||
auto j = TwoPort::toJSON();
|
||||
j["delay"] = delay;
|
||||
return j;
|
||||
}
|
||||
|
||||
void Line::fromJSON(nlohmann::json j)
|
||||
{
|
||||
TwoPort::fromJSON(j);
|
||||
setDelay(j.value("delay", 0.0));
|
||||
}
|
||||
|
||||
void Line::setDelay(double delay)
|
||||
{
|
||||
this->delay = delay;
|
||||
minFreq = 1.0 / delay * 20 / 360;
|
||||
maxFreq = 1.0 / delay * 160 / 360;
|
||||
}
|
||||
|
||||
void Line::setupSCPI()
|
||||
{
|
||||
addDoubleParameter("Z0", Z0, true, true, [=](){clearMeasurement();});
|
||||
addDoubleParameter("DELAY", delay, true, true, [=](){clearMeasurement();});
|
||||
TwoPort::setupSCPI();
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
#ifndef CALSTANDARD_H
|
||||
#define CALSTANDARD_H
|
||||
|
||||
#include "savable.h"
|
||||
#include "touchstone.h"
|
||||
#include "Tools/parameters.h"
|
||||
#include "scpi.h"
|
||||
|
||||
#include <complex>
|
||||
#include <functional>
|
||||
|
||||
namespace CalStandard
|
||||
{
|
||||
|
||||
class Virtual : public QObject, public Savable, public SCPINode
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Virtual(QString name = "");
|
||||
virtual ~Virtual(){emit deleted();}
|
||||
|
||||
enum class Type {
|
||||
Open,
|
||||
Short,
|
||||
Load,
|
||||
Reflect,
|
||||
Through,
|
||||
Line,
|
||||
Last
|
||||
};
|
||||
|
||||
static Virtual* create(Type type);
|
||||
|
||||
static std::vector<Type> availableTypes();
|
||||
static QString TypeToString(Type type);
|
||||
static Type TypeFromString(QString s);
|
||||
|
||||
virtual Type getType() = 0;
|
||||
double minFrequency() {return minFreq;}
|
||||
double maxFrequency() {return maxFreq;}
|
||||
|
||||
virtual void edit(std::function<void(void)> finishedCallback) = 0;
|
||||
virtual QString getDescription();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
unsigned long long getID();
|
||||
|
||||
QString getName() const;
|
||||
void setName(const QString &value);
|
||||
|
||||
signals:
|
||||
void deleted();
|
||||
|
||||
protected:
|
||||
void setupSCPI();
|
||||
QString name;
|
||||
double minFreq;
|
||||
double maxFreq;
|
||||
unsigned long long id;
|
||||
};
|
||||
|
||||
class OnePort : public Virtual
|
||||
{
|
||||
public:
|
||||
OnePort(QString name = "") :
|
||||
Virtual(name),
|
||||
touchstone(nullptr){}
|
||||
|
||||
virtual std::complex<double> toS11(double freq) = 0;
|
||||
|
||||
void setMeasurement(const Touchstone &ts, int port = 0);
|
||||
void clearMeasurement();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
protected:
|
||||
void setupSCPI();
|
||||
Touchstone *touchstone;
|
||||
};
|
||||
|
||||
class Calkit;
|
||||
|
||||
class Open : public OnePort
|
||||
{
|
||||
public:
|
||||
Open();
|
||||
Open(QString name, double Z0, double delay, double loss, double C0, double C1, double C2, double C3)
|
||||
: OnePort(name), Z0(Z0), delay(delay), loss(loss), C0(C0), C1(C1), C2(C2), C3(C3){setupSCPI();}
|
||||
|
||||
virtual std::complex<double> toS11(double freq) override;
|
||||
virtual void edit(std::function<void(void)> finishedCallback = nullptr) override;
|
||||
virtual Type getType() override {return Type::Open;}
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
private:
|
||||
void setupSCPI();
|
||||
double Z0, delay, loss, C0, C1, C2, C3;
|
||||
};
|
||||
|
||||
class Short : public OnePort
|
||||
{
|
||||
public:
|
||||
Short();
|
||||
Short(QString name, double Z0, double delay, double loss, double L0, double L1, double L2, double L3)
|
||||
: OnePort(name), Z0(Z0), delay(delay), loss(loss), L0(L0), L1(L1), L2(L2), L3(L3){setupSCPI();}
|
||||
|
||||
virtual std::complex<double> toS11(double freq) override;
|
||||
virtual void edit(std::function<void(void)> finishedCallback = nullptr) override;
|
||||
virtual Type getType() override {return Type::Short;}
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
private:
|
||||
void setupSCPI();
|
||||
double Z0, delay, loss, L0, L1, L2, L3;
|
||||
};
|
||||
|
||||
class Load : public OnePort
|
||||
{
|
||||
public:
|
||||
Load();
|
||||
Load(QString name, double Z0, double delay, double loss, double resistance, double Cparallel, double Lseries, bool Cfirst = true)
|
||||
: OnePort(name), Z0(Z0), delay(delay), loss(loss), resistance(resistance), Cparallel(Cparallel), Lseries(Lseries), Cfirst(Cfirst){setupSCPI();}
|
||||
|
||||
virtual std::complex<double> toS11(double freq) override;
|
||||
virtual void edit(std::function<void(void)> finishedCallback = nullptr) override;
|
||||
virtual Type getType() override {return Type::Load;}
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
private:
|
||||
void setupSCPI();
|
||||
double Z0, delay, loss, resistance, Cparallel, Lseries;
|
||||
bool Cfirst;
|
||||
};
|
||||
|
||||
class Reflect : public OnePort
|
||||
{
|
||||
public:
|
||||
Reflect();
|
||||
Reflect(QString name, bool isShort)
|
||||
: OnePort(name), isShort(isShort){}
|
||||
|
||||
virtual std::complex<double> toS11(double freq) override;
|
||||
virtual void edit(std::function<void(void)> finishedCallback = nullptr) override;
|
||||
virtual Type getType() override {return Type::Reflect;}
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
bool getIsShort() const;
|
||||
|
||||
private:
|
||||
void setupSCPI();
|
||||
bool isShort;
|
||||
};
|
||||
|
||||
|
||||
class TwoPort : public Virtual
|
||||
{
|
||||
public:
|
||||
TwoPort(QString name = "") :
|
||||
Virtual(name),
|
||||
touchstone(nullptr){}
|
||||
|
||||
virtual Sparam toSparam(double freq) = 0;
|
||||
|
||||
void setMeasurement(const Touchstone &ts, int port1 = 0, int port2 = 1);
|
||||
void clearMeasurement();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
protected:
|
||||
void setupSCPI();
|
||||
Touchstone *touchstone;
|
||||
};
|
||||
|
||||
class Through : public TwoPort
|
||||
{
|
||||
public:
|
||||
Through();
|
||||
Through(QString name, double Z0, double delay, double loss)
|
||||
: TwoPort(name), Z0(Z0), delay(delay), loss(loss){setupSCPI();}
|
||||
|
||||
virtual Sparam toSparam(double freq) override;
|
||||
virtual void edit(std::function<void(void)> finishedCallback = nullptr) override;
|
||||
virtual Type getType() override {return Type::Through;}
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
private:
|
||||
void setupSCPI();
|
||||
double Z0, delay, loss;
|
||||
};
|
||||
|
||||
class Line : public TwoPort
|
||||
{
|
||||
public:
|
||||
Line();
|
||||
Line(QString name, double Z0, double delay)
|
||||
: TwoPort(name), Z0(Z0), delay(delay){setupSCPI();}
|
||||
|
||||
virtual Sparam toSparam(double freq) override;
|
||||
virtual void edit(std::function<void(void)> finishedCallback = nullptr) override;
|
||||
virtual Type getType() override {return Type::Line;}
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
private:
|
||||
void setDelay(double delay);
|
||||
void setupSCPI();
|
||||
double Z0, delay;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CALSTANDARD_H
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#include "manualcalibrationdialog.h"
|
||||
|
||||
#include "ui_manualcalibrationdialog.h"
|
||||
#include "Traces/sparamtraceselector.h"
|
||||
|
||||
ManualCalibrationDialog::ManualCalibrationDialog(const TraceModel &model, Calibration *cal) :
|
||||
ui(new Ui::ManualCalibrationDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
auto traceSelector = new SparamTraceSelector(model, cal->getCaltype().usedPorts, true);
|
||||
ui->verticalLayout->insertWidget(1, traceSelector, 1.0);
|
||||
ui->buttonBox->setEnabled(false);
|
||||
connect(traceSelector, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=]() {
|
||||
cal->correctTraces(traceSelector->getTraces());
|
||||
accept();
|
||||
});
|
||||
}
|
||||
|
||||
ManualCalibrationDialog::~ManualCalibrationDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#ifndef MANUALCALIBRATIONDIALOG_H
|
||||
#define MANUALCALIBRATIONDIALOG_H
|
||||
|
||||
#include "calibration.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class ManualCalibrationDialog;
|
||||
}
|
||||
|
||||
class ManualCalibrationDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManualCalibrationDialog(const TraceModel &model, Calibration *cal);
|
||||
~ManualCalibrationDialog();
|
||||
|
||||
private:
|
||||
Ui::ManualCalibrationDialog *ui;
|
||||
};
|
||||
|
||||
#endif // MANUALCALIBRATIONDIALOG_H
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ManualCalibrationDialog</class>
|
||||
<widget class="QDialog" name="ManualCalibrationDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>597</width>
|
||||
<height>331</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Manual Calibration</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Select the traces which contain the S parameters you want to apply the calibration to:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#include "colorpickerbutton.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
|
||||
ColorPickerButton::ColorPickerButton(QWidget *parent)
|
||||
: QPushButton(parent)
|
||||
{
|
||||
color = Qt::white;
|
||||
connect(this, &ColorPickerButton::clicked, this, &ColorPickerButton::changeColor);
|
||||
updateBackground();
|
||||
}
|
||||
|
||||
void ColorPickerButton::setColor(const QColor &color)
|
||||
{
|
||||
this->color = color;
|
||||
updateBackground();
|
||||
}
|
||||
|
||||
const QColor &ColorPickerButton::getColor()
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
void ColorPickerButton::changeColor()
|
||||
{
|
||||
auto newColor = QColorDialog::getColor(color, parentWidget(), "Select color", QColorDialog::DontUseNativeDialog);
|
||||
if(newColor.isValid() && newColor != color) {
|
||||
setColor(newColor);
|
||||
emit colorChanged(newColor);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorPickerButton::updateBackground()
|
||||
{
|
||||
setStyleSheet("background-color:"+color.name());
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#ifndef COLORPICKERBUTTON_H
|
||||
#define COLORPICKERBUTTON_H
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
class ColorPickerButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ColorPickerButton(QWidget *parent = nullptr);
|
||||
|
||||
void setColor(const QColor& color);
|
||||
const QColor& getColor();
|
||||
signals:
|
||||
void colorChanged(const QColor& color);
|
||||
private slots:
|
||||
void changeColor();
|
||||
private:
|
||||
void updateBackground();
|
||||
QColor color;
|
||||
};
|
||||
|
||||
#endif // COLORPICKERBUTTON_H
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
#include "csvimport.h"
|
||||
#include "ui_csvimport.h"
|
||||
#include "Traces/trace.h"
|
||||
#include "preferences.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QDebug>
|
||||
|
||||
CSVImport::CSVImport(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::csvimport),
|
||||
status(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->from->setPrecision(5);
|
||||
ui->to->setPrecision(5);
|
||||
ui->traces->setPrecision(0);
|
||||
ui->points->setPrecision(0);
|
||||
connect(ui->file, &QLineEdit::textChanged, this, &CSVImport::setFile);
|
||||
}
|
||||
|
||||
CSVImport::~CSVImport()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool CSVImport::getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
CSV CSVImport::getCSV()
|
||||
{
|
||||
return csv;
|
||||
}
|
||||
|
||||
QString CSVImport::getFilename()
|
||||
{
|
||||
return ui->file->text();
|
||||
}
|
||||
|
||||
bool CSVImport::fillTrace(Trace &t)
|
||||
{
|
||||
try {
|
||||
t.fillFromCSV(csv, ui->traceSelector->currentIndex());
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
qWarning() << QString("CSV import failed to fill trace: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CSVImport::setFile(QString filename)
|
||||
{
|
||||
ui->file->setText(filename);
|
||||
evaluateFile();
|
||||
emit filenameChanged(filename);
|
||||
}
|
||||
|
||||
void CSVImport::selectTrace(unsigned int index)
|
||||
{
|
||||
ui->traceSelector->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
void CSVImport::on_browse_clicked()
|
||||
{
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions());
|
||||
if (filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
|
||||
ui->file->setText(filename);
|
||||
evaluateFile();
|
||||
}
|
||||
}
|
||||
|
||||
void CSVImport::evaluateFile()
|
||||
{
|
||||
// store currently selected trace
|
||||
auto selectedTrace = ui->traceSelector->currentText();
|
||||
|
||||
bool new_status = false;
|
||||
ui->traces->setText("");
|
||||
ui->points->setText("");
|
||||
ui->from->setText("");
|
||||
ui->to->setText("");
|
||||
ui->status->clear();
|
||||
ui->traceSelector->clear();
|
||||
try {
|
||||
csv = CSV::fromFile(ui->file->text());
|
||||
auto domain = Trace::dataTypeFromString(csv.getHeader(0));
|
||||
if (domain == Trace::DataType::Invalid) {
|
||||
// just assume frequency domain
|
||||
domain = Trace::DataType::Frequency;
|
||||
}
|
||||
ui->domain->setCurrentText(Trace::dataTypeToString(domain));
|
||||
switch(domain) {
|
||||
case Trace::DataType::Frequency:
|
||||
ui->from->setUnit("Hz");
|
||||
ui->to->setUnit("Hz");
|
||||
break;
|
||||
case Trace::DataType::Time:
|
||||
case Trace::DataType::TimeZeroSpan:
|
||||
ui->from->setUnit("s");
|
||||
ui->to->setUnit("s");
|
||||
break;
|
||||
case Trace::DataType::Power:
|
||||
ui->from->setUnit("dBm");
|
||||
ui->to->setUnit("dBm");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto xvalues = csv.getColumn(0);
|
||||
ui->from->setValue(xvalues.front());
|
||||
ui->to->setValue(xvalues.back());
|
||||
ui->points->setValue(xvalues.size());
|
||||
|
||||
bool okay = true;
|
||||
int numTraces = 0;
|
||||
while(okay) {
|
||||
try {
|
||||
Trace t;
|
||||
auto name = t.fillFromCSV(csv, numTraces);
|
||||
ui->traceSelector->addItem(name);
|
||||
numTraces++;
|
||||
} catch (const std::exception &e) {
|
||||
// no more traces
|
||||
okay = false;
|
||||
}
|
||||
}
|
||||
ui->traces->setValue(numTraces);
|
||||
|
||||
// check if previously selected trace is still available
|
||||
if(ui->traceSelector->findText(selectedTrace) >= 0) {
|
||||
ui->traceSelector->setCurrentText(selectedTrace);
|
||||
} else {
|
||||
ui->traceSelector->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
new_status = true;
|
||||
} catch (const std::exception &e) {
|
||||
ui->status->setText(e.what());
|
||||
}
|
||||
if (new_status != status) {
|
||||
status = new_status;
|
||||
emit statusChanged(status);
|
||||
}
|
||||
emit filenameChanged(ui->file->text());
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#ifndef CSVIMPORT_H
|
||||
#define CSVIMPORT_H
|
||||
|
||||
#include "csv.h"
|
||||
#include "Traces/trace.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class csvimport;
|
||||
}
|
||||
|
||||
class CSVImport : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CSVImport(QWidget *parent = nullptr);
|
||||
~CSVImport();
|
||||
|
||||
bool getStatus();
|
||||
CSV getCSV();
|
||||
QString getFilename();
|
||||
|
||||
bool fillTrace(Trace &t);
|
||||
|
||||
signals:
|
||||
void statusChanged(bool status);
|
||||
void filenameChanged(QString name);
|
||||
|
||||
public slots:
|
||||
void setFile(QString filename);
|
||||
void selectTrace(unsigned int index);
|
||||
|
||||
private slots:
|
||||
void on_browse_clicked();
|
||||
|
||||
private:
|
||||
void evaluateFile();
|
||||
Ui::csvimport *ui;
|
||||
CSV csv;
|
||||
bool status;
|
||||
};
|
||||
|
||||
#endif // CSVIMPORT_H
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>csvimport</class>
|
||||
<widget class="QWidget" name="csvimport">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>232</width>
|
||||
<height>256</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>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Domain:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="domain">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Traces:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="traces">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Points:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="points">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>From:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="from">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>To:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="SIUnitEdit" name="to">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Trace:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="traceSelector"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
#include "informationbox.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QSettings>
|
||||
#include <QDebug>
|
||||
|
||||
bool InformationBox::has_gui = true;
|
||||
|
||||
void InformationBox::ShowMessage(QString title, QString message, QString messageID, bool block, QWidget *parent)
|
||||
{
|
||||
if(!has_gui) {
|
||||
// no gui option active, do not show any messages
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the user still wants to see this message
|
||||
unsigned int hash;
|
||||
if(messageID.isEmpty()) {
|
||||
hash = qHash(message);
|
||||
} else {
|
||||
hash = qHash(messageID);
|
||||
}
|
||||
|
||||
QSettings s;
|
||||
if(!s.contains(hashToSettingsKey(hash))) {
|
||||
auto box = new InformationBox(title, message, QMessageBox::Information, hash, parent);
|
||||
if(block) {
|
||||
box->exec();
|
||||
} else {
|
||||
box->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InformationBox::ShowMessageBlocking(QString title, QString message, QString messageID, QWidget *parent)
|
||||
{
|
||||
ShowMessage(title, message, messageID, true, parent);
|
||||
}
|
||||
|
||||
void InformationBox::ShowError(QString title, QString message, QWidget *parent)
|
||||
{
|
||||
if(!has_gui) {
|
||||
// no gui option active, do not show any messages
|
||||
return;
|
||||
}
|
||||
auto box = new InformationBox(title, message, QMessageBox::Information, 0, parent);
|
||||
box->show();
|
||||
}
|
||||
|
||||
bool InformationBox::AskQuestion(QString title, QString question, bool defaultAnswer, QString messageID, QWidget *parent)
|
||||
{
|
||||
if(!has_gui) {
|
||||
// no gui option active, do not show any messages
|
||||
return defaultAnswer;
|
||||
}
|
||||
|
||||
// check if the user still wants to see this message
|
||||
unsigned int hash;
|
||||
if(messageID.isEmpty()) {
|
||||
hash = qHash(question);
|
||||
} else {
|
||||
hash = qHash(messageID);
|
||||
}
|
||||
|
||||
QSettings s;
|
||||
if(!s.contains(hashToSettingsKey(hash))) {
|
||||
auto box = new InformationBox(title, question, QMessageBox::Question, hash, parent);
|
||||
box->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
int ret = box->exec();
|
||||
if(ret == QMessageBox::Yes) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// don't show this question anymore
|
||||
return s.value(hashToSettingsKey(hash)).toBool();
|
||||
}
|
||||
}
|
||||
|
||||
void InformationBox::setGUI(bool enable)
|
||||
{
|
||||
has_gui = enable;
|
||||
}
|
||||
|
||||
InformationBox::InformationBox(QString title, QString message, Icon icon, unsigned int hash, QWidget *parent)
|
||||
: QMessageBox(parent),
|
||||
hash(hash)
|
||||
{
|
||||
setWindowTitle(title);
|
||||
setText(message);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setModal(true);
|
||||
setIcon(icon);
|
||||
|
||||
auto cb = new QCheckBox("Do not show this message again");
|
||||
setCheckBox(cb);
|
||||
|
||||
if(hash == 0) {
|
||||
cb->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
InformationBox::~InformationBox()
|
||||
{
|
||||
auto cb = checkBox();
|
||||
if(cb->isChecked()) {
|
||||
auto value = true;
|
||||
auto clicked = clickedButton();
|
||||
if(clicked && QMessageBox::standardButton(clicked) == StandardButton::No) {
|
||||
value = false;
|
||||
}
|
||||
QSettings s;
|
||||
s.setValue(hashToSettingsKey(hash), value);
|
||||
}
|
||||
}
|
||||
|
||||
QString InformationBox::hashToSettingsKey(unsigned int hash)
|
||||
{
|
||||
return QString("DoNotShowDialog/") + QString::number(hash);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
#ifndef INFORMATIONBOX_H
|
||||
#define INFORMATIONBOX_H
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
class InformationBox : public QMessageBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static void ShowMessage(QString title, QString message, QString messageID = QString(), bool block = false, QWidget *parent = nullptr);
|
||||
static void ShowMessageBlocking(QString title, QString message, QString messageID = QString(), QWidget *parent = nullptr);
|
||||
static void ShowError(QString title, QString message, QWidget *parent = nullptr);
|
||||
// Display a dialog with yes/no buttons. Returns true if yes is clicked, false otherwise. If the user has selected to never see this message again, defaultAnswer is returned instead
|
||||
static bool AskQuestion(QString title, QString question, bool defaultAnswer, QString messageID = QString(), QWidget *parent = nullptr);
|
||||
|
||||
static void setGUI(bool enable);
|
||||
private:
|
||||
InformationBox(QString title, QString message, QMessageBox::Icon icon, unsigned int hash, QWidget *parent);
|
||||
~InformationBox();
|
||||
static QString hashToSettingsKey(unsigned int hash);
|
||||
unsigned int hash;
|
||||
static bool has_gui;
|
||||
};
|
||||
|
||||
#endif // INFORMATIONBOX_H
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
#include "jsonpickerdialog.h"
|
||||
|
||||
#include "ui_jsonpickerdialog.h"
|
||||
|
||||
JSONPickerDialog::JSONPickerDialog(const nlohmann::json &json, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::JSONPickerDialog),
|
||||
model(new JSONModel(json))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->treeView->setModel(model);
|
||||
}
|
||||
|
||||
JSONPickerDialog::~JSONPickerDialog()
|
||||
{
|
||||
delete ui;
|
||||
delete model;
|
||||
}
|
||||
|
||||
JSONModel::JSONModel(const nlohmann::json &json, QObject *parent) :
|
||||
json(json)
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
setupJsonInfo(json);
|
||||
}
|
||||
|
||||
JSONModel::~JSONModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QModelIndex JSONModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
nlohmann::json const *parentItem;
|
||||
|
||||
if (!parent.isValid())
|
||||
parentItem = &json;
|
||||
else
|
||||
parentItem = static_cast<nlohmann::json*>(parent.internalPointer());
|
||||
|
||||
auto it = parentItem->begin();
|
||||
int rb = row;
|
||||
while(rb) {
|
||||
it++;
|
||||
rb--;
|
||||
}
|
||||
nlohmann::json *childItem = const_cast<nlohmann::json*>(&*it);
|
||||
if (childItem)
|
||||
return createIndex(row, column, childItem);
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex JSONModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
nlohmann::json *childItem = static_cast<nlohmann::json*>(index.internalPointer());
|
||||
if (childItem == &json) {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
// find the parent of this entry and its position in the list
|
||||
nlohmann::json *parentItem = const_cast<nlohmann::json*>(jsonInfo.at(childItem).parent);
|
||||
auto it = parentItem->begin();
|
||||
int row = 0;
|
||||
while(&*it != childItem) {
|
||||
it++;
|
||||
row++;
|
||||
}
|
||||
return createIndex(row, 0, parentItem);
|
||||
}
|
||||
|
||||
int JSONModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
const nlohmann::json *parentItem;
|
||||
if (parent.column() > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!parent.isValid()) {
|
||||
parentItem = &json;
|
||||
} else {
|
||||
parentItem = static_cast<nlohmann::json*>(parent.internalPointer());
|
||||
}
|
||||
|
||||
if (parentItem->is_object() || parentItem->is_array()) {
|
||||
return parentItem->size();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int JSONModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return 2;
|
||||
}
|
||||
|
||||
QVariant JSONModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
nlohmann::json *item = static_cast<nlohmann::json*>(index.internalPointer());
|
||||
auto info = jsonInfo.at(item);
|
||||
switch(role) {
|
||||
case Qt::DisplayRole:
|
||||
switch(index.column()) {
|
||||
case 0:
|
||||
return info.name;
|
||||
case 1:
|
||||
if(!item->is_object() && !item->is_array()) {
|
||||
return info.data;
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
case Qt::CheckStateRole: {
|
||||
if(index.column() == 0) {
|
||||
return info.enabled ? Qt::Checked : Qt::Unchecked;
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant JSONModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
Q_UNUSED(section)
|
||||
Q_UNUSED(orientation)
|
||||
Q_UNUSED(role)
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool JSONModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
Q_UNUSED(value)
|
||||
nlohmann::json *item = static_cast<nlohmann::json*>(index.internalPointer());
|
||||
auto info = jsonInfo.at(item);
|
||||
if(role == Qt::CheckStateRole)
|
||||
{
|
||||
info.enabled = !info.enabled;
|
||||
jsonInfo[item] = info;
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (role != Qt::EditRole)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags JSONModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
return Qt::ItemIsEditable | QAbstractItemModel::flags(index)|Qt::ItemIsUserCheckable;
|
||||
}
|
||||
|
||||
void JSONModel::setupJsonInfo(const nlohmann::json &j)
|
||||
{
|
||||
for(auto it = j.begin();it != j.end(); it++) {
|
||||
JSONInfo i;
|
||||
i.parent = &j;
|
||||
i.enabled = true;
|
||||
if(j.is_object()) {
|
||||
i.name = it.key().c_str();
|
||||
} else if(j.is_array()) {
|
||||
i.name = QString::number(it - j.begin() + 1);
|
||||
}
|
||||
if(it->is_object() || it->is_array()) {
|
||||
setupJsonInfo(*it);
|
||||
} else {
|
||||
i.data = QString::fromStdString(it->dump());
|
||||
}
|
||||
jsonInfo[&*it] = i;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
#ifndef JSONPICKERDIALOG_H
|
||||
#define JSONPICKERDIALOG_H
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
namespace Ui {
|
||||
class JSONPickerDialog;
|
||||
}
|
||||
|
||||
class JSONModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
JSONModel(const nlohmann::json &json, QObject *parent = 0);
|
||||
~JSONModel();
|
||||
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
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;
|
||||
|
||||
private:
|
||||
void setupJsonInfo(const nlohmann::json &j);
|
||||
const nlohmann::json &json;
|
||||
class JSONInfo {
|
||||
public:
|
||||
const nlohmann::json *parent;
|
||||
QString name;
|
||||
QString data;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
std::map<const nlohmann::json*, JSONInfo> jsonInfo;
|
||||
};
|
||||
|
||||
class JSONPickerDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit JSONPickerDialog(const nlohmann::json &json, QWidget *parent = nullptr);
|
||||
~JSONPickerDialog();
|
||||
|
||||
private:
|
||||
Ui::JSONPickerDialog *ui;
|
||||
JSONModel *model;
|
||||
};
|
||||
|
||||
#endif // JSONPICKERDIALOG_H
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>JSONPickerDialog</class>
|
||||
<widget class="QDialog" name="JSONPickerDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="treeView"/>
|
||||
</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>JSONPickerDialog</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>JSONPickerDialog</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>
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
#include "siunitedit.h"
|
||||
|
||||
#include <QDoubleValidator>
|
||||
#include <unit.h>
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <cmath>
|
||||
|
||||
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);
|
||||
connect(this, &QLineEdit::editingFinished, [this]() {
|
||||
parseNewValue(1.0);
|
||||
});
|
||||
setValueQuiet(0);
|
||||
}
|
||||
|
||||
SIUnitEdit::SIUnitEdit(QWidget *parent)
|
||||
: SIUnitEdit("", " ", 4, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SIUnitEdit::setValue(double value)
|
||||
{
|
||||
if(value != _value) {
|
||||
setValueQuiet(value);
|
||||
emit valueChanged(value);
|
||||
emit valueUpdated(this);
|
||||
}
|
||||
}
|
||||
|
||||
static char swapUpperLower(char c) {
|
||||
if(isupper(c)) {
|
||||
return tolower(c);
|
||||
} else if(islower(c)) {
|
||||
return toupper(c);
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
clear();
|
||||
setValueQuiet(_value);
|
||||
emit editingAborted();
|
||||
clearFocus();
|
||||
return true;
|
||||
}
|
||||
if(key == Qt::Key_Return || key == Qt::Key_Enter) {
|
||||
// use new value without prefix
|
||||
parseNewValue(1.0);
|
||||
continueEditing();
|
||||
return true;
|
||||
}
|
||||
auto mod = static_cast<QKeyEvent *>(event)->modifiers();
|
||||
if (!(mod & Qt::ShiftModifier)) {
|
||||
key = tolower(key);
|
||||
}
|
||||
if(key <= 255) {
|
||||
if (prefixes.indexOf(static_cast<QChar>(key)) >= 0) {
|
||||
// a valid prefix key was pressed
|
||||
parseNewValue(Unit::SIPrefixToFactor(key));
|
||||
continueEditing();
|
||||
return true;
|
||||
} else if (prefixes.indexOf(swapUpperLower(key)) >= 0) {
|
||||
// no match on the pressed case but on the upper/lower case instead -> also accept this
|
||||
parseNewValue(Unit::SIPrefixToFactor(swapUpperLower(key)));
|
||||
continueEditing();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if(event->type() == QEvent::FocusOut) {
|
||||
parseNewValue(1.0);
|
||||
emit focusLost();
|
||||
} else if(event->type() == QEvent::FocusIn) {
|
||||
// online found clumsy way to select all text when clicked!?!
|
||||
// just selectAll() alone does _not_ work!
|
||||
QTimer::singleShot(0, this, &SIUnitEdit::continueEditing);
|
||||
} else if(event->type() == QEvent::Wheel) {
|
||||
if(_value == 0.0) {
|
||||
// can't figure out step size with zero value
|
||||
return true;
|
||||
}
|
||||
auto wheel = static_cast<QWheelEvent*>(event);
|
||||
// most mousewheel have 15 degree increments, the reported delta is in 1/8th degree -> 120
|
||||
auto increment = wheel->angleDelta().y() / 120.0;
|
||||
// round toward bigger step in case of special higher resolution mousewheel
|
||||
unsigned int steps = std::abs(increment > 0 ? ceil(increment) : floor(increment));
|
||||
int sign = increment > 0 ? 1 : -1;
|
||||
// figure out step increment
|
||||
auto newVal = _value;
|
||||
if(hasFocus()) {
|
||||
auto cursor = cursorPosition();
|
||||
if(cursor == 0) {
|
||||
// cursor in front of first digit, do nothing (too big of a change, probably over/underflows)
|
||||
return true;
|
||||
}
|
||||
// change the digit at the current cursor
|
||||
int nthDigit = cursor;
|
||||
// account for decimal point/leading zero/sign
|
||||
if(_value < 0) {
|
||||
nthDigit--;
|
||||
}
|
||||
auto dotPos = text().indexOf('.');
|
||||
if(dotPos >= 0 && dotPos < nthDigit) {
|
||||
nthDigit--;
|
||||
}
|
||||
if(text().startsWith("-0.") || text().startsWith("0.")) {
|
||||
nthDigit--;
|
||||
}
|
||||
auto step_size = pow(10, floor(log10(std::abs(newVal))) - nthDigit + 1);
|
||||
newVal += step_size * steps * sign;
|
||||
setValue(newVal);
|
||||
setText(placeholderText());
|
||||
setCursorPosition(cursor);
|
||||
} else {
|
||||
// default to the third digit
|
||||
constexpr int nthDigit = 3;
|
||||
while(steps > 0) {
|
||||
// do update in multiple steps because the step size could change inbetween
|
||||
auto step_size = pow(10, floor(log10(std::abs(newVal))) - nthDigit + 1);
|
||||
newVal += step_size * sign;
|
||||
steps--;
|
||||
}
|
||||
setValue(newVal);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SIUnitEdit::setValueQuiet(double value)
|
||||
{
|
||||
_value = value;
|
||||
setPlaceholderText(Unit::ToString(value, unit, prefixes, precision));
|
||||
if(!text().isEmpty()) {
|
||||
// currently editing, update the text as well
|
||||
continueEditing();
|
||||
}
|
||||
}
|
||||
|
||||
void SIUnitEdit::parseNewValue(double factor)
|
||||
{
|
||||
QString input = text();
|
||||
if(input.isEmpty()) {
|
||||
setValueQuiet(_value);
|
||||
emit editingAborted();
|
||||
} else {
|
||||
// remove optional unit
|
||||
if(input.endsWith(unit)) {
|
||||
input.chop(unit.size());
|
||||
}
|
||||
auto lastChar = input.at(input.size()-1).toLatin1();
|
||||
if(prefixes.indexOf(lastChar) >= 0) {
|
||||
factor = Unit::SIPrefixToFactor(lastChar);
|
||||
input.chop(1);
|
||||
}
|
||||
// remaining input should only contain numbers
|
||||
bool conversion_ok;
|
||||
auto v = input.toDouble(&conversion_ok);
|
||||
clear();
|
||||
if(conversion_ok) {
|
||||
setValue(v * factor);
|
||||
} else {
|
||||
qWarning() << "SIUnit conversion failure:" << input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SIUnitEdit::continueEditing()
|
||||
{
|
||||
setText(placeholderText());
|
||||
selectAll();
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#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);
|
||||
void valueUpdated(QWidget *w);
|
||||
void editingAborted();
|
||||
void focusLost();
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
private slots:
|
||||
void continueEditing();
|
||||
private:
|
||||
void parseNewValue(double factor);
|
||||
QString unit, prefixes;
|
||||
int precision;
|
||||
double _value;
|
||||
};
|
||||
|
||||
#endif // SIUNITEDIT_H
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
#include "tilewidget.h"
|
||||
|
||||
#include "ui_tilewidget.h"
|
||||
#include "Traces/tracexyplot.h"
|
||||
#include "Traces/tracesmithchart.h"
|
||||
#include "Traces/tracewaterfall.h"
|
||||
#include "Traces/tracepolarchart.h"
|
||||
#include "Traces/eyediagramplot.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
TileWidget::TileWidget(TraceModel &model, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
fullScreenPlot(nullptr),
|
||||
ui(new Ui::TileWidget),
|
||||
splitter(0),
|
||||
isSplit(false),
|
||||
parent(0),
|
||||
child1(0),
|
||||
child2(0),
|
||||
hasContent(false),
|
||||
isFullScreen(false),
|
||||
content(0),
|
||||
model(model)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
auto layout = new QGridLayout;
|
||||
layout->setContentsMargins(0,0,0,0);
|
||||
auto fsLayout = new QGridLayout;
|
||||
fsLayout->setContentsMargins(0,0,0,0);
|
||||
ui->ContentPage->setLayout(layout);
|
||||
ui->ContentFullScreenMode->setLayout(fsLayout);
|
||||
ui->bClose->setVisible(false);
|
||||
}
|
||||
|
||||
TileWidget::~TileWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void TileWidget::clear()
|
||||
{
|
||||
if(hasContent) {
|
||||
delete content;
|
||||
content = nullptr;
|
||||
hasContent = false;
|
||||
}
|
||||
if(isSplit) {
|
||||
delete child1;
|
||||
delete child2;
|
||||
isSplit = false;
|
||||
delete splitter;
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json TileWidget::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["split"] = isSplit;
|
||||
if(isSplit) {
|
||||
j["orientation"] = splitter->orientation() == Qt::Horizontal ? "horizontal" : "vertical";
|
||||
j["sizes"] = splitter->sizes();
|
||||
j["tile1"] = child1->toJSON();
|
||||
j["tile2"] = child2->toJSON();
|
||||
}
|
||||
if(hasContent) {
|
||||
std::string plotname;
|
||||
switch(content->getType()) {
|
||||
case TracePlot::Type::SmithChart:
|
||||
plotname = "smithchart";
|
||||
break;
|
||||
case TracePlot::Type::XYPlot:
|
||||
plotname = "XY-plot";
|
||||
break;
|
||||
case TracePlot::Type::Waterfall:
|
||||
plotname = "Waterfall";
|
||||
break;
|
||||
case TracePlot::Type::PolarChart:
|
||||
plotname = "PolarChart";
|
||||
break;
|
||||
case TracePlot::Type::EyeDiagram:
|
||||
plotname = "EyeDiagram";
|
||||
break;
|
||||
}
|
||||
j["plot"] = plotname;
|
||||
j["plotsettings"] = content->toJSON();
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void TileWidget::fromJSON(nlohmann::json j)
|
||||
{
|
||||
// delete all childs before parsing json
|
||||
clear();
|
||||
bool split = j.value("split", false);
|
||||
if(split) {
|
||||
if(j["orientation"] == "horizontal") {
|
||||
splitHorizontally();
|
||||
} else {
|
||||
splitVertically();
|
||||
}
|
||||
splitter->setSizes(j["sizes"]);
|
||||
child1->fromJSON(j["tile1"]);
|
||||
child2->fromJSON(j["tile2"]);
|
||||
} else if(j.contains("plot")) {
|
||||
// has a plot enabled
|
||||
auto plotname = j["plot"];
|
||||
if(plotname == "smithchart") {
|
||||
content = new TraceSmithChart(model);
|
||||
} else if (plotname == "XY-plot"){
|
||||
content = new TraceXYPlot(model);
|
||||
} else if (plotname == "Waterfall"){
|
||||
content = new TraceWaterfall(model);
|
||||
} else if (plotname == "PolarChart"){
|
||||
content = new TracePolarChart(model);
|
||||
} else if (plotname == "EyeDiagram"){
|
||||
content = new EyeDiagramPlot(model);
|
||||
}
|
||||
if(content) {
|
||||
setContent(content);
|
||||
content->fromJSON(j["plotsettings"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TileWidget::allLimitsPassing()
|
||||
{
|
||||
if(isSplit) {
|
||||
return child1->allLimitsPassing() && child2->allLimitsPassing();
|
||||
} else if(hasContent) {
|
||||
return content->getLimitPassing();
|
||||
} else {
|
||||
// empty tile always passes
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void TileWidget::setSplitPercentage(int percentage)
|
||||
{
|
||||
if(!isSplit) {
|
||||
return;
|
||||
}
|
||||
splitter->setStretchFactor(0, percentage);
|
||||
splitter->setStretchFactor(1, 100 - percentage);
|
||||
}
|
||||
|
||||
void TileWidget::splitVertically(bool moveContentToSecondChild)
|
||||
{
|
||||
if(isSplit) {
|
||||
return;
|
||||
}
|
||||
isSplit = true;
|
||||
splitter = new QSplitter(Qt::Vertical);
|
||||
split(moveContentToSecondChild);
|
||||
tryMinimize();
|
||||
}
|
||||
|
||||
void TileWidget::splitHorizontally(bool moveContentToSecondChild)
|
||||
{
|
||||
if(isSplit) {
|
||||
return;
|
||||
}
|
||||
isSplit = true;
|
||||
splitter = new QSplitter(Qt::Horizontal);
|
||||
split(moveContentToSecondChild);
|
||||
tryMinimize();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void TileWidget::removePlot()
|
||||
{
|
||||
if(hasContent) {
|
||||
content->setParentTile(nullptr);
|
||||
removeContent();
|
||||
}
|
||||
}
|
||||
|
||||
TileWidget::TileWidget(TraceModel &model, TileWidget &parent)
|
||||
: TileWidget(model)
|
||||
{
|
||||
this->parent = &parent;
|
||||
ui->bClose->setVisible(true);
|
||||
}
|
||||
|
||||
void TileWidget::split(bool moveContentToSecondChild)
|
||||
{
|
||||
splitter->setHandleWidth(0);
|
||||
child1 = new TileWidget(model, *this);
|
||||
child2 = new TileWidget(model, *this);
|
||||
splitter->addWidget(child1);
|
||||
splitter->addWidget(child2);
|
||||
ui->ContentPage->layout()->addWidget(splitter);
|
||||
if(hasContent) {
|
||||
if(moveContentToSecondChild) {
|
||||
child2->setContent(content);
|
||||
} else {
|
||||
child1->setContent(content);
|
||||
}
|
||||
removeContent();
|
||||
}
|
||||
|
||||
ui->stack->setCurrentWidget(ui->ContentPage);
|
||||
}
|
||||
|
||||
void TileWidget::setContent(TracePlot *plot)
|
||||
{
|
||||
content = plot;
|
||||
content->setParentTile(this);
|
||||
hasContent = true;
|
||||
ui->ContentPage->layout()->addWidget(plot);
|
||||
ui->stack->setCurrentWidget(ui->ContentPage);
|
||||
connect(content, &TracePlot::deleted, this, &TileWidget::plotDeleted);
|
||||
connect(content, &TracePlot::doubleClicked, this, &TileWidget::on_plotDoubleClicked);
|
||||
}
|
||||
|
||||
void TileWidget::removeContent()
|
||||
{
|
||||
if(hasContent) {
|
||||
disconnect(content, &TracePlot::deleted, this, &TileWidget::plotDeleted);
|
||||
disconnect(content, &TracePlot::doubleClicked, this, &TileWidget::on_plotDoubleClicked);
|
||||
hasContent = false;
|
||||
content = nullptr;
|
||||
ui->stack->setCurrentWidget(ui->TilePage);
|
||||
}
|
||||
}
|
||||
|
||||
void TileWidget::on_plotDoubleClicked()
|
||||
{
|
||||
setFullScreen();
|
||||
}
|
||||
|
||||
void TileWidget::setFullScreen(void)
|
||||
{
|
||||
auto clickedTile = this;
|
||||
|
||||
if(!clickedTile->parent) {
|
||||
// Toplevel tile is already in full screen
|
||||
return;
|
||||
}
|
||||
|
||||
auto rootTile = findRootTile();
|
||||
|
||||
rootTile->fullScreenPlot = clickedTile->content;
|
||||
|
||||
if (isFullScreen == false)
|
||||
{
|
||||
ui->ContentPage->layout()->removeWidget(clickedTile->content);
|
||||
rootTile->ui->ContentFullScreenMode->layout()->addWidget(rootTile->fullScreenPlot);
|
||||
rootTile->ui->stack->setCurrentWidget(rootTile->ui->ContentFullScreenMode);
|
||||
isFullScreen = true;
|
||||
}
|
||||
else {
|
||||
rootTile->ui->stack->setCurrentWidget(rootTile->ui->ContentPage);
|
||||
rootTile->ui->ContentFullScreenMode->layout()->removeWidget(rootTile->fullScreenPlot);
|
||||
ui->ContentPage->layout()->addWidget(clickedTile->content);
|
||||
isFullScreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
TileWidget* TileWidget::findRootTile(void)
|
||||
{
|
||||
auto node = this;
|
||||
while (node->parent != nullptr) { // root tile should nullptr
|
||||
node = node->parent;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
void TileWidget::on_bSmithchart_clicked()
|
||||
{
|
||||
setContent(new TraceSmithChart(model));
|
||||
}
|
||||
|
||||
void TileWidget::on_bXYplot_clicked()
|
||||
{
|
||||
auto plot = new TraceXYPlot(model);
|
||||
setContent(plot);
|
||||
plot->axisSetupDialog();
|
||||
}
|
||||
|
||||
void TileWidget::plotDeleted()
|
||||
{
|
||||
tryMinimize();
|
||||
|
||||
ui->stack->setCurrentWidget(ui->TilePage);
|
||||
hasContent = false;
|
||||
content = nullptr;
|
||||
}
|
||||
|
||||
void TileWidget::tryMinimize()
|
||||
{
|
||||
if (isFullScreen)
|
||||
{
|
||||
auto rootTile = findRootTile();
|
||||
rootTile->ui->stack->setCurrentWidget(rootTile->ui->ContentPage);
|
||||
}
|
||||
}
|
||||
|
||||
void TileWidget::on_bWaterfall_clicked()
|
||||
{
|
||||
setContent(new TraceWaterfall(model));
|
||||
}
|
||||
|
||||
void TileWidget::on_bPolarchart_clicked()
|
||||
{
|
||||
setContent(new TracePolarChart(model));
|
||||
}
|
||||
|
||||
void TileWidget::on_eyeDiagram_clicked()
|
||||
{
|
||||
auto plot = new EyeDiagramPlot(model);
|
||||
setContent(plot);
|
||||
plot->axisSetupDialog();
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
#ifndef TILEWIDGET_H
|
||||
#define TILEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "Traces/traceplot.h"
|
||||
#include "Traces/tracemodel.h"
|
||||
#include "savable.h"
|
||||
|
||||
#include <QSplitter>
|
||||
|
||||
namespace Ui {
|
||||
class TileWidget;
|
||||
}
|
||||
|
||||
class TileWidget : public QWidget, public Savable
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TileWidget(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TileWidget();
|
||||
|
||||
TileWidget *Child1() { return child1; }
|
||||
TileWidget *Child2() { return child2; }
|
||||
|
||||
// closes all plots/childs, leaving only the tilewidget at the top
|
||||
void clear();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
// check potential trace limits on graphs, only returns true if all traces in all graphs are within limits
|
||||
bool allLimitsPassing();
|
||||
|
||||
void setSplitPercentage(int percentage);
|
||||
|
||||
public slots:
|
||||
void splitVertically(bool moveContentToSecondChild = false);
|
||||
void splitHorizontally(bool moveContentToSecondChild = false);
|
||||
void closeTile();
|
||||
void setPlot(TracePlot *plot);
|
||||
void removePlot();
|
||||
|
||||
private slots:
|
||||
void on_bSmithchart_clicked();
|
||||
void on_bXYplot_clicked();
|
||||
void on_plotDoubleClicked();
|
||||
void plotDeleted();
|
||||
void on_bWaterfall_clicked();
|
||||
void on_bPolarchart_clicked();
|
||||
|
||||
void on_eyeDiagram_clicked();
|
||||
|
||||
private:
|
||||
TileWidget(TraceModel &model, TileWidget &parent);
|
||||
void split(bool moveContentToSecondChild = false);
|
||||
void setContent(TracePlot *plot);
|
||||
void removeContent();
|
||||
void setChild();
|
||||
TileWidget* findRootTile();
|
||||
void setFullScreen();
|
||||
void tryMinimize();
|
||||
TracePlot *fullScreenPlot;
|
||||
Ui::TileWidget *ui;
|
||||
QSplitter *splitter;
|
||||
bool isSplit;
|
||||
TileWidget *parent;
|
||||
TileWidget *child1, *child2;
|
||||
bool hasContent;
|
||||
bool isFullScreen;
|
||||
TracePlot *content;
|
||||
TraceModel &model;
|
||||
};
|
||||
|
||||
#endif // TILEWIDGET_H
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
<?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="bXYplot">
|
||||
<property name="text">
|
||||
<string>XY-plot</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bWaterfall">
|
||||
<property name="text">
|
||||
<string>Waterfall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bPolarchart">
|
||||
<property name="text">
|
||||
<string>Polar Chart</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="eyeDiagram">
|
||||
<property name="text">
|
||||
<string>Eye Diagram</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="ContentFullScreenMode"/>
|
||||
<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>
|
||||
+66
@@ -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);
|
||||
}
|
||||
+28
@@ -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
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
#include "touchstoneimport.h"
|
||||
|
||||
#include "ui_touchstoneimport.h"
|
||||
#include "preferences.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);
|
||||
ui->lowerFreq->setUnit("Hz");
|
||||
ui->lowerFreq->setPrefixes(" kMG");
|
||||
ui->upperFreq->setUnit("Hz");
|
||||
ui->upperFreq->setPrefixes(" kMG");
|
||||
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::idClicked), [=](int id) {
|
||||
preventCollisionWithGroup(ui->port2Group, id);
|
||||
});
|
||||
connect(ui->port2Group, qOverload<int>(&QButtonGroup::idClicked), [=](int id) {
|
||||
preventCollisionWithGroup(ui->port1Group, id);
|
||||
});
|
||||
connect(ui->file, &QLineEdit::textChanged, this, &TouchstoneImport::setFile);
|
||||
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();
|
||||
emit filenameChanged(filename);
|
||||
}
|
||||
|
||||
void TouchstoneImport::on_browse_clicked()
|
||||
{
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", Preferences::getInstance().UISettings.Paths.data, "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, Preferences::QFileDialogOptions());
|
||||
if (filename.length() > 0) {
|
||||
Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path();
|
||||
ui->file->setText(filename);
|
||||
}
|
||||
}
|
||||
|
||||
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->setValue(touchstone.minFreq());
|
||||
ui->upperFreq->setValue(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
#ifndef TOUCHSTONEIMPORT_H
|
||||
#define TOUCHSTONEIMPORT_H
|
||||
|
||||
#include "touchstone.h"
|
||||
|
||||
#include <QWidget>
|
||||
#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
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
<?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="SIUnitEdit" 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="SIUnitEdit" name="upperFreq">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="port1Group"/>
|
||||
<buttongroup name="port2Group"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
#include "tracesetselector.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
||||
TraceSetSelector::TraceSetSelector(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
model(nullptr)
|
||||
{
|
||||
points = 0;
|
||||
ports = 0;
|
||||
lowerFreq = upperFreq = 0;
|
||||
referenceImpedance = 50.0;
|
||||
freqsSet = false;
|
||||
partialSelectionAllowed = false;
|
||||
|
||||
setLayout(new QGridLayout);
|
||||
setPorts(2);
|
||||
}
|
||||
|
||||
TraceSetSelector::~TraceSetSelector()
|
||||
{
|
||||
blockSignals(true);
|
||||
// this will free all created widgets
|
||||
setPorts(0);
|
||||
blockSignals(false);
|
||||
}
|
||||
|
||||
unsigned int TraceSetSelector::getPorts() const
|
||||
{
|
||||
return ports;
|
||||
}
|
||||
|
||||
void TraceSetSelector::setPorts(unsigned int newPorts)
|
||||
{
|
||||
if(!model) {
|
||||
return;
|
||||
}
|
||||
QGridLayout *layout = static_cast<QGridLayout*>(this->layout());
|
||||
if(newPorts != ports) {
|
||||
ports = newPorts;
|
||||
// remove the previous widgets
|
||||
QLayoutItem *child;
|
||||
while ((child = layout->takeAt(0)) != 0) {
|
||||
delete child->widget();
|
||||
delete child;
|
||||
}
|
||||
cTraces.clear();
|
||||
for(unsigned int i=0;i<ports;i++) {
|
||||
cTraces.push_back(std::vector<QComboBox*>());
|
||||
for(unsigned int j=0;j<ports;j++) {
|
||||
auto l = new QLabel("S"+QString::number(i+1)+QString::number(j+1)+":");
|
||||
auto c = new QComboBox();
|
||||
cTraces[i].push_back(c);
|
||||
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [=](int) {
|
||||
selectionChanged(c);
|
||||
});
|
||||
layout->addWidget(l, i, j*2);
|
||||
layout->addWidget(c, i, j*2 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto availableTraces = model->getTraces();
|
||||
for(unsigned int i=0;i<ports;i++) {
|
||||
for(unsigned int j=0;j<ports;j++) {
|
||||
auto c = cTraces[i][j];
|
||||
c->blockSignals(true);
|
||||
c->clear();
|
||||
// create possible trace selections
|
||||
c->addItem("None");
|
||||
for(auto t : availableTraces) {
|
||||
if(t->outputType() != Trace::DataType::Frequency) {
|
||||
// can only add frequency traces
|
||||
continue;
|
||||
}
|
||||
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));
|
||||
}
|
||||
c->blockSignals(false);
|
||||
}
|
||||
}
|
||||
emit selectionChanged();
|
||||
}
|
||||
|
||||
void TraceSetSelector::setDefaultTraces()
|
||||
{
|
||||
auto traces = model->getTraces();
|
||||
for(unsigned int i=1;i<=ports;i++) {
|
||||
for(unsigned int j=1;j<=ports;j++) {
|
||||
QString defaultTraceName = "S"+QString::number(i)+QString::number(j);
|
||||
for(auto t : traces) {
|
||||
if(t->name() == defaultTraceName) {
|
||||
setTrace(i, j, t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TraceSetSelector::setTrace(unsigned int destPort, unsigned int srcPort, Trace *t)
|
||||
{
|
||||
if(destPort < 1 || destPort > ports || srcPort < 1 || srcPort > ports) {
|
||||
// invalid port selection
|
||||
return false;
|
||||
}
|
||||
auto c = cTraces[destPort-1][srcPort-1];
|
||||
// find required trace
|
||||
for(unsigned int i=1;i<(unsigned int) c->count();i++) {
|
||||
if(qvariant_cast<Trace*>(c->itemData(i)) == t) {
|
||||
c->setCurrentIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// requested trace not found, unable to set
|
||||
return false;
|
||||
}
|
||||
|
||||
Trace *TraceSetSelector::getTrace(unsigned int destPort, unsigned int srcPort)
|
||||
{
|
||||
if(destPort < 1 || destPort > ports || srcPort < 1 || srcPort > ports) {
|
||||
// invalid port selection
|
||||
return nullptr;
|
||||
}
|
||||
auto c = cTraces[destPort-1][srcPort-1];
|
||||
if(c->currentIndex() == 0) {
|
||||
// no trace selected
|
||||
return nullptr;
|
||||
} else {
|
||||
return qvariant_cast<Trace*>(c->itemData(c->currentIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
bool TraceSetSelector::selectionValid()
|
||||
{
|
||||
for(unsigned int i=1;i<=ports;i++) {
|
||||
for(unsigned int j=1;j<=ports;j++) {
|
||||
auto t = getTrace(i, j);
|
||||
if(!t && !partialSelectionAllowed) {
|
||||
// at least one trace is missing
|
||||
return false;
|
||||
}
|
||||
if(t && partialSelectionAllowed) {
|
||||
// at least one trace is present
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we get here, either:
|
||||
// - all traces are valid and partial selection is not allowed -> whole selection valid
|
||||
// or:
|
||||
// - all traces are invalid and partial selection is allowed -> whole selection invalid
|
||||
return !partialSelectionAllowed;
|
||||
}
|
||||
|
||||
void TraceSetSelector::selectionChanged(QComboBox *c)
|
||||
{
|
||||
if(c->currentIndex() != 0 && !freqsSet) {
|
||||
// the first trace has been selected, extract frequency info
|
||||
Trace *t = qvariant_cast<Trace*>(c->itemData(c->currentIndex()));
|
||||
points = t->size();
|
||||
referenceImpedance = t->getReferenceImpedance();
|
||||
if(points > 0) {
|
||||
lowerFreq = t->minX();
|
||||
upperFreq = t->maxX();
|
||||
}
|
||||
freqsSet = 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->getReferenceImpedance() != referenceImpedance || t->size() != points || (points > 0 && (t->minX() != lowerFreq || t->maxX() != upperFreq))) {
|
||||
// this trace is not available anymore
|
||||
c->removeItem(i);
|
||||
// decrement to check the next index in the next loop iteration
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(c->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
|
||||
emit selectionChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// all traces set for none
|
||||
freqsSet = false;
|
||||
setPorts(ports);
|
||||
}
|
||||
emit selectionChanged();
|
||||
}
|
||||
|
||||
double TraceSetSelector::getReferenceImpedance() const
|
||||
{
|
||||
return referenceImpedance;
|
||||
}
|
||||
|
||||
double TraceSetSelector::getUpperFreq() const
|
||||
{
|
||||
return upperFreq;
|
||||
}
|
||||
|
||||
double TraceSetSelector::getLowerFreq() const
|
||||
{
|
||||
return lowerFreq;
|
||||
}
|
||||
|
||||
unsigned int TraceSetSelector::getPoints() const
|
||||
{
|
||||
return points;
|
||||
}
|
||||
|
||||
void TraceSetSelector::setModel(TraceModel *newModel)
|
||||
{
|
||||
model = newModel;
|
||||
}
|
||||
|
||||
void TraceSetSelector::setPartialSelectionAllowed(bool newPartialSelectionAllowed)
|
||||
{
|
||||
partialSelectionAllowed = newPartialSelectionAllowed;
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
#ifndef TRACESETSELECTOR_H
|
||||
#define TRACESETSELECTOR_H
|
||||
|
||||
#include "Traces/tracemodel.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace Ui {
|
||||
class TraceSetSelector;
|
||||
}
|
||||
|
||||
class TraceSetSelector : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TraceSetSelector(QWidget *parent = nullptr);
|
||||
~TraceSetSelector();
|
||||
|
||||
unsigned int getPorts() const;
|
||||
void setPorts(unsigned int newPorts);
|
||||
void setDefaultTraces();
|
||||
|
||||
bool setTrace(unsigned int destPort, unsigned int srcPort, Trace *t);
|
||||
Trace *getTrace(unsigned int destPort, unsigned int srcPort);
|
||||
|
||||
bool selectionValid();
|
||||
|
||||
void setPartialSelectionAllowed(bool newPartialSelectionAllowed);
|
||||
|
||||
void setModel(TraceModel *newModel);
|
||||
|
||||
unsigned int getPoints() const;
|
||||
double getLowerFreq() const;
|
||||
double getUpperFreq() const;
|
||||
double getReferenceImpedance() const;
|
||||
TraceModel* getModel() const {return model;}
|
||||
|
||||
signals:
|
||||
void selectionChanged();
|
||||
|
||||
private:
|
||||
void selectionChanged(QComboBox *c);
|
||||
Ui::TraceSetSelector *ui;
|
||||
TraceModel *model;
|
||||
unsigned int ports;
|
||||
|
||||
std::vector<std::vector<QComboBox*>> cTraces;
|
||||
|
||||
unsigned int points;
|
||||
double lowerFreq, upperFreq;
|
||||
double referenceImpedance;
|
||||
bool freqsSet;
|
||||
|
||||
bool partialSelectionAllowed;
|
||||
};
|
||||
|
||||
#endif // TRACESETSELECTOR_H
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#include "harogicb60.h"
|
||||
|
||||
HarogicB60::HarogicB60()
|
||||
{
|
||||
validUSBIDs.clear();
|
||||
validUSBIDs.append({0x367F, 0x0200, "B60"});
|
||||
|
||||
for(auto &s : specificSettings) {
|
||||
s.name.replace("LibreVNAUSBDriver", "HarogicB60Driver");
|
||||
}
|
||||
}
|
||||
|
||||
QString HarogicB60::getDriverName()
|
||||
{
|
||||
return "Harogic B60";
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#ifndef HAROGICB60_H
|
||||
#define HAROGICB60_H
|
||||
|
||||
#include "../LibreVNA/librevnausbdriver.h"
|
||||
|
||||
class HarogicB60 : public LibreVNAUSBDriver
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
HarogicB60();
|
||||
|
||||
/**
|
||||
* @brief Returns the driver name. It must be unique across all implemented drivers and is used to identify the driver
|
||||
* @return driver name
|
||||
*/
|
||||
virtual QString getDriverName() override;
|
||||
};
|
||||
|
||||
#endif // HAROGICB60_H
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
#include "compounddevice.h"
|
||||
|
||||
CompoundDevice::CompoundDevice()
|
||||
{
|
||||
name = "";
|
||||
sync = LibreVNADriver::Synchronization::GUI;
|
||||
}
|
||||
|
||||
nlohmann::json CompoundDevice::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["name"] = name.toStdString();
|
||||
j["synchronization"] = SyncToString(sync).toStdString();
|
||||
nlohmann::json jserials;
|
||||
for(auto d : deviceSerials) {
|
||||
jserials.push_back(d.toStdString());
|
||||
}
|
||||
j["devices"] = jserials;
|
||||
nlohmann::json jmappings;
|
||||
for(auto m : portMapping) {
|
||||
nlohmann::json jmapping;
|
||||
jmapping["device"] = m.device;
|
||||
jmapping["port"] = m.port;
|
||||
jmappings.push_back(jmapping);
|
||||
}
|
||||
j["mapping"] = jmappings;
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
void CompoundDevice::fromJSON(nlohmann::json j)
|
||||
{
|
||||
name = QString::fromStdString(j.value("name", "CompoundDevice"));
|
||||
sync = SyncFromString(QString::fromStdString(j.value("synchronization", "USB")));
|
||||
deviceSerials.clear();
|
||||
if(j.contains("devices")) {
|
||||
for(auto js : j["devices"]) {
|
||||
deviceSerials.push_back(QString::fromStdString(js));
|
||||
}
|
||||
}
|
||||
portMapping.clear();
|
||||
if(j.contains("mapping")) {
|
||||
for(auto jm : j["mapping"]) {
|
||||
PortMapping mapping;
|
||||
mapping.device = jm.value("device", 0);
|
||||
mapping.port = jm.value("port", 0);
|
||||
portMapping.push_back(mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString CompoundDevice::SyncToString(LibreVNADriver::Synchronization sync)
|
||||
{
|
||||
switch(sync) {
|
||||
case LibreVNADriver::Synchronization::Disabled: return "Disabled";
|
||||
case LibreVNADriver::Synchronization::GUI: return "GUI";
|
||||
case LibreVNADriver::Synchronization::ExternalTrigger: return "Trigger";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
LibreVNADriver::Synchronization CompoundDevice::SyncFromString(QString s)
|
||||
{
|
||||
for(int i=0;i<(int) LibreVNADriver::Synchronization::Last;i++) {
|
||||
if(SyncToString((LibreVNADriver::Synchronization)i) == s) {
|
||||
return (LibreVNADriver::Synchronization) i;
|
||||
}
|
||||
}
|
||||
// default to GUI
|
||||
return LibreVNADriver::Synchronization::GUI;
|
||||
}
|
||||
|
||||
QString CompoundDevice::getDesription()
|
||||
{
|
||||
return name + ", "+QString::number(deviceSerials.size())+" devices, "+QString::number(portMapping.size())+" ports in total";
|
||||
}
|
||||
|
||||
unsigned int CompoundDevice::PortMapping::findActiveStage(std::vector<CompoundDevice::PortMapping> map, unsigned int device, unsigned int port)
|
||||
{
|
||||
for(unsigned int i=0;i<map.size();i++) {
|
||||
if(map[i].device == device && map[i].port == port) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return map.size();
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#ifndef COMPOUNDDEVICE_H
|
||||
#define COMPOUNDDEVICE_H
|
||||
|
||||
#include "savable.h"
|
||||
#include "../librevnadriver.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QString>
|
||||
|
||||
class CompoundDevice : public Savable
|
||||
{
|
||||
friend class CompoundDeviceEditDialog;
|
||||
public:
|
||||
CompoundDevice();
|
||||
|
||||
nlohmann::json toJSON() override;
|
||||
void fromJSON(nlohmann::json j) override;
|
||||
|
||||
class PortMapping {
|
||||
public:
|
||||
unsigned int device; // starts at zero
|
||||
unsigned int port; // starts at zero
|
||||
static unsigned int findActiveStage(std::vector<PortMapping> map, unsigned int device, unsigned int port);
|
||||
};
|
||||
|
||||
static QString SyncToString(LibreVNADriver::Synchronization sync);
|
||||
static LibreVNADriver::Synchronization SyncFromString(QString s);
|
||||
|
||||
QString getDesription();
|
||||
|
||||
QString name;
|
||||
LibreVNADriver::Synchronization sync;
|
||||
std::vector<QString> deviceSerials;
|
||||
std::vector<PortMapping> portMapping;
|
||||
};
|
||||
|
||||
#endif // COMPOUNDDEVICE_H
|
||||
+554
@@ -0,0 +1,554 @@
|
||||
#include "compounddeviceeditdialog.h"
|
||||
#include "ui_compounddeviceeditdialog.h"
|
||||
|
||||
#include "compounddriver.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QDrag>
|
||||
#include <QMimeData>
|
||||
#include <QMouseEvent>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
using namespace std;
|
||||
|
||||
CompoundDeviceEditDialog::CompoundDeviceEditDialog(CompoundDevice *cdev, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::CompoundDeviceEditDialog),
|
||||
dropPending(false),
|
||||
dragFrame(nullptr),
|
||||
dropFrame(nullptr)
|
||||
{
|
||||
ldev = *cdev;
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->name, &QLineEdit::editingFinished, [=](){
|
||||
ldev.name = ui->name->text();
|
||||
checkIfOkay();
|
||||
});
|
||||
for(int i=0;i<(int)LibreVNADriver::Synchronization::Last;i++) {
|
||||
ui->sync->addItem(CompoundDevice::SyncToString((LibreVNADriver::Synchronization) i));
|
||||
switch((LibreVNADriver::Synchronization) i) {
|
||||
case LibreVNADriver::Synchronization::Disabled:
|
||||
case LibreVNADriver::Synchronization::Reserved: {
|
||||
// Disable for now
|
||||
auto *model = qobject_cast<QStandardItemModel *>(ui->sync->model());
|
||||
Q_ASSERT(model != nullptr);
|
||||
bool disabled = true;
|
||||
auto *item = model->item(i);
|
||||
item->setFlags(disabled ? item->flags() & ~Qt::ItemIsEnabled
|
||||
: item->flags() | Qt::ItemIsEnabled);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
connect(ui->sync, &QComboBox::currentTextChanged, [=](){
|
||||
ldev.sync = CompoundDevice::SyncFromString(ui->sync->currentText());
|
||||
updateDeviceFrames();
|
||||
});
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [=](){
|
||||
*cdev = ldev;
|
||||
accept();
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &QDialog::reject);
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){
|
||||
ldev.saveToFileDialog("Save compound device", "Compound device file (*.cdev)", ".cdev");
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){
|
||||
if(ldev.openFromFileDialog("Load compound device", "Compound device file (*.cdev)")) {
|
||||
setInitialGUI();
|
||||
}
|
||||
});
|
||||
|
||||
ui->status->setStyleSheet("QLabel { color : red; }");
|
||||
|
||||
graph = new QWidget();
|
||||
ui->scrollArea->setWidget(graph);
|
||||
auto layout = new QHBoxLayout();
|
||||
graph->setLayout(layout);
|
||||
graph->setAcceptDrops(true);
|
||||
graph->setObjectName("Graph");
|
||||
graph->installEventFilter(this);
|
||||
ui->lLibreVNA1->installEventFilter(this);
|
||||
layout->setContentsMargins(0,0,0,0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
setInitialGUI();
|
||||
}
|
||||
|
||||
CompoundDeviceEditDialog::~CompoundDeviceEditDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void CompoundDeviceEditDialog::checkIfOkay()
|
||||
{
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
|
||||
|
||||
if(deviceFrames.size() != ldev.deviceSerials.size()) {
|
||||
// probably still populating the GUI, skip check
|
||||
return;
|
||||
}
|
||||
|
||||
if(ldev.name.isEmpty()) {
|
||||
ui->status->setText("Name must not be empty");
|
||||
return;
|
||||
}
|
||||
if(ldev.deviceSerials.size() < 2) {
|
||||
ui->status->setText("At least two devices are required");
|
||||
return;
|
||||
}
|
||||
if(ldev.deviceSerials.size() > 4) {
|
||||
ui->status->setText("Only up to four devices supported");
|
||||
return;
|
||||
}
|
||||
// Check serials
|
||||
for(unsigned int i=0;i<ldev.deviceSerials.size();i++) {
|
||||
ldev.deviceSerials[i] = deviceFrames[i]->getSerial();
|
||||
if(ldev.deviceSerials[i].isEmpty()) {
|
||||
ui->status->setText("Device "+QString::number(i+1)+" has no serial number");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Check serials for duplicates
|
||||
for(unsigned int i=0;i<ldev.deviceSerials.size();i++) {
|
||||
for(unsigned int j=i+1;j<ldev.deviceSerials.size();j++) {
|
||||
if(ldev.deviceSerials[i] == ldev.deviceSerials[j]) {
|
||||
ui->status->setText("Duplicate serial number ("+ldev.deviceSerials[i]+") in devices "+QString::number(i+1)+" and "+QString::number(j+1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check port mapping
|
||||
// Looking for duplicate and missing ports
|
||||
bool highestPortFound = false;
|
||||
unsigned int highestPort = 0;
|
||||
for(unsigned int port=0;port<2*ldev.deviceSerials.size();port++) {
|
||||
int num = 0;
|
||||
for(unsigned int i=0;i<deviceFrames.size();i++) {
|
||||
if(deviceFrames[i]->getPort1() == (int) port) {
|
||||
num++;
|
||||
}
|
||||
if(deviceFrames[i]->getPort2() == (int) port) {
|
||||
num++;
|
||||
}
|
||||
}
|
||||
if(num > 1) {
|
||||
ui->status->setText("Duplicate port "+QString::number(port+1));
|
||||
return;
|
||||
} else if(num == 0) {
|
||||
if(port == 0) {
|
||||
ui->status->setText("Port 1 must be present");
|
||||
return;
|
||||
}
|
||||
if(!highestPortFound) {
|
||||
highestPort = port;
|
||||
}
|
||||
highestPortFound = true;
|
||||
} else if(highestPortFound) {
|
||||
// port is present, but earlier port was missing
|
||||
ui->status->setText("Missing port: "+QString::number(port+1)+" is selected, but port "+QString::number(port)+" is not present.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(!highestPortFound) {
|
||||
highestPort = 2*ldev.deviceSerials.size();
|
||||
}
|
||||
|
||||
// All good, actually create the port mapping
|
||||
ldev.portMapping.clear();
|
||||
for(unsigned int port=0;port<highestPort;port++) {
|
||||
CompoundDevice::PortMapping map;
|
||||
bool found = false;
|
||||
for(unsigned int i=0;i<deviceFrames.size();i++) {
|
||||
if(deviceFrames[i]->getPort1() == (int) port) {
|
||||
found = true;
|
||||
map.device = i;
|
||||
map.port = 0;
|
||||
break;
|
||||
}
|
||||
if(deviceFrames[i]->getPort2() == (int) port) {
|
||||
found = true;
|
||||
map.device = i;
|
||||
map.port = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
ui->status->setText("Failed to find port "+QString::number(port+1)+" (likely a bug)");
|
||||
return;
|
||||
}
|
||||
ldev.portMapping.push_back(map);
|
||||
}
|
||||
|
||||
ui->status->clear();
|
||||
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(true);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||
}
|
||||
|
||||
void CompoundDeviceEditDialog::setInitialGUI()
|
||||
{
|
||||
ui->name->setText(ldev.name);
|
||||
ui->sync->setCurrentText(CompoundDevice::SyncToString(ldev.sync));
|
||||
|
||||
// Removing the old frames actually modifies the state of ldev as well. Block signals to prevent this from happening
|
||||
for(auto f : deviceFrames) {
|
||||
f->blockSignals(true);
|
||||
}
|
||||
|
||||
QHBoxLayout* layout = (QHBoxLayout*) graph->layout();
|
||||
// remove all widgets from the layout
|
||||
if (layout != NULL ) {
|
||||
QLayoutItem* item;
|
||||
while ((item = layout->takeAt(0)) != NULL ) {
|
||||
delete item->widget();
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
deviceFrames.clear();
|
||||
layout->addStretch(1);
|
||||
for(unsigned int i=0;i<ldev.deviceSerials.size();i++) {
|
||||
auto frame = new DeviceFrame(&ldev, i);
|
||||
connect(frame, &DeviceFrame::settingChanged, this, &CompoundDeviceEditDialog::checkIfOkay);
|
||||
addFrame(i, frame);
|
||||
}
|
||||
layout->addStretch(1);
|
||||
|
||||
checkIfOkay();
|
||||
}
|
||||
|
||||
DeviceFrame *CompoundDeviceEditDialog::frameAtPosition(int pos)
|
||||
{
|
||||
pos -= graph->layout()->itemAt(0)->geometry().width();
|
||||
if(pos > 0 && pos <= (int) deviceFrames.size() * frameSize) {
|
||||
return deviceFrames[pos / frameSize];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned int CompoundDeviceEditDialog::findInsertPosition(int xcoord)
|
||||
{
|
||||
xcoord -= graph->layout()->itemAt(0)->geometry().width();
|
||||
// added in port 1 network
|
||||
int index = (xcoord + frameSize / 2) / frameSize;
|
||||
if(index < 0) {
|
||||
index = 0;
|
||||
} else if(index > (int) deviceFrames.size()) {
|
||||
index = deviceFrames.size();
|
||||
}
|
||||
// add 1 (first widget is always the stretch)
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
void CompoundDeviceEditDialog::addFrameAtPosition(int pos, DeviceFrame *c)
|
||||
{
|
||||
auto index = findInsertPosition(pos);
|
||||
|
||||
// add component to the deviceFrame vector
|
||||
index -= 1; // first widget is fixed
|
||||
if(index <= deviceFrames.size()) {
|
||||
addFrame(index, c);
|
||||
}
|
||||
}
|
||||
|
||||
void CompoundDeviceEditDialog::addFrame(int index, DeviceFrame *c)
|
||||
{
|
||||
if(deviceFrames.size() == ldev.deviceSerials.size()) {
|
||||
ldev.deviceSerials.insert(ldev.deviceSerials.begin() + index, "");
|
||||
}
|
||||
deviceFrames.insert(deviceFrames.begin() + index, c);
|
||||
deviceFrames[index]->setPosition(index);
|
||||
// remove from list when the component deletes itself
|
||||
connect(c, &DeviceFrame::deleted, [=](){
|
||||
removeDeviceFrame(c);
|
||||
});
|
||||
QHBoxLayout *l = static_cast<QHBoxLayout*>(graph->layout());
|
||||
l->insertWidget(index + 1, c);
|
||||
c->show();
|
||||
updateDeviceFrames();
|
||||
}
|
||||
|
||||
void CompoundDeviceEditDialog::createDragFrame(DeviceFrame *c)
|
||||
{
|
||||
QDrag *drag = new QDrag(this);
|
||||
QMimeData *mimeData = new QMimeData;
|
||||
|
||||
QByteArray encodedPointer;
|
||||
QDataStream stream(&encodedPointer, QIODevice::WriteOnly);
|
||||
stream << quintptr(c);
|
||||
|
||||
mimeData->setData("compoundDeviceFrame/pointer", encodedPointer);
|
||||
drag->setMimeData(mimeData);
|
||||
|
||||
drag->exec(Qt::MoveAction);
|
||||
dragFrame = nullptr;
|
||||
}
|
||||
|
||||
void CompoundDeviceEditDialog::updateInsertIndicator(int xcoord)
|
||||
{
|
||||
auto index = findInsertPosition(xcoord);
|
||||
QHBoxLayout *l = static_cast<QHBoxLayout*>(graph->layout());
|
||||
l->removeWidget(insertIndicator);
|
||||
l->insertWidget(index, insertIndicator);
|
||||
}
|
||||
|
||||
bool CompoundDeviceEditDialog::eventFilter(QObject *object, QEvent *event)
|
||||
{
|
||||
if(object->objectName() == "Graph") {
|
||||
if(event->type() == QEvent::MouseButtonPress) {
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
dragFrame = frameAtPosition(mouseEvent->pos().x());
|
||||
if(dragFrame) {
|
||||
dragStartPosition = mouseEvent->pos();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if(event->type() == QEvent::MouseMove) {
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (!(mouseEvent->buttons() & Qt::LeftButton)) {
|
||||
return false;
|
||||
}
|
||||
if (!dragFrame) {
|
||||
return false;
|
||||
}
|
||||
if ((mouseEvent->pos() - dragStartPosition).manhattanLength()
|
||||
< QApplication::startDragDistance()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove and hide component while it is being dragged
|
||||
graph->layout()->removeWidget(dragFrame);
|
||||
dragFrame->hide();
|
||||
removeDeviceFrame(dragFrame);
|
||||
graph->update();
|
||||
|
||||
createDragFrame(dragFrame);
|
||||
return true;
|
||||
} else if(event->type() == QEvent::DragEnter) {
|
||||
auto dragEvent = static_cast<QDragEnterEvent*>(event);
|
||||
if(dragEvent->mimeData()->hasFormat("compoundDeviceFrame/pointer")) {
|
||||
dropPending = true;
|
||||
auto data = dragEvent->mimeData()->data("compoundDeviceFrame/pointer");
|
||||
QDataStream stream(&data, QIODevice::ReadOnly);
|
||||
quintptr dropPtr;
|
||||
stream >> dropPtr;
|
||||
dropFrame = (DeviceFrame*) dropPtr;
|
||||
dragEvent->acceptProposedAction();
|
||||
insertIndicator = new QWidget();
|
||||
insertIndicator->setMinimumSize(2, frameSize);
|
||||
insertIndicator->setMaximumSize(2, frameSize);
|
||||
insertIndicator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
insertIndicator->setStyleSheet("background-color:red;");
|
||||
updateInsertIndicator(dragEvent->position().toPoint().x());
|
||||
return true;
|
||||
}
|
||||
} else if(event->type() == QEvent::DragMove) {
|
||||
auto dragEvent = static_cast<QDragMoveEvent*>(event);
|
||||
updateInsertIndicator(dragEvent->position().toPoint().x());
|
||||
return true;
|
||||
} else if(event->type() == QEvent::Drop) {
|
||||
auto dragEvent = static_cast<QDropEvent*>(event);
|
||||
delete insertIndicator;
|
||||
addFrameAtPosition(dragEvent->position().toPoint().x(), dropFrame);
|
||||
return true;
|
||||
} else if(event->type() == QEvent::DragLeave) {
|
||||
dropPending = false;
|
||||
dropFrame = nullptr;
|
||||
delete insertIndicator;
|
||||
}
|
||||
} else {
|
||||
// clicked/dragged one of the components outside of the graph
|
||||
if(event->type() == QEvent::MouseButtonPress) {
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
dragStartPosition = mouseEvent->pos();
|
||||
if(object->objectName() == "lLibreVNA1") {
|
||||
dragFrame = new DeviceFrame(&ldev, 0);
|
||||
connect(dragFrame, &DeviceFrame::settingChanged, this, &CompoundDeviceEditDialog::checkIfOkay);
|
||||
} else {
|
||||
dragFrame = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if(event->type() == QEvent::MouseMove) {
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (!(mouseEvent->buttons() & Qt::LeftButton)) {
|
||||
return false;
|
||||
}
|
||||
if (!dragFrame) {
|
||||
return false;
|
||||
}
|
||||
if ((mouseEvent->pos() - dragStartPosition).manhattanLength()
|
||||
< QApplication::startDragDistance()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
createDragFrame(dragFrame);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CompoundDeviceEditDialog::removeDeviceFrame(DeviceFrame *dev)
|
||||
{
|
||||
auto it = std::find(deviceFrames.begin(), deviceFrames.end(), dev);
|
||||
if(it == deviceFrames.end()) {
|
||||
// not found, shouldn't happen
|
||||
return;
|
||||
}
|
||||
auto pos = it - deviceFrames.begin();
|
||||
deviceFrames.erase(it);
|
||||
ldev.deviceSerials.erase(ldev.deviceSerials.begin() + pos);
|
||||
// remove all port mappings from the removed device
|
||||
bool mappingFound;
|
||||
do {
|
||||
mappingFound = false;
|
||||
for(unsigned int i=0;i<ldev.portMapping.size();i++) {
|
||||
if(ldev.portMapping[i].device == pos) {
|
||||
mappingFound = true;
|
||||
ldev.portMapping.erase(ldev.portMapping.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(mappingFound);
|
||||
updateDeviceFrames();
|
||||
}
|
||||
|
||||
void CompoundDeviceEditDialog::updateDeviceFrames()
|
||||
{
|
||||
int i=0;
|
||||
for(auto df : deviceFrames) {
|
||||
df->setPosition(i++);
|
||||
}
|
||||
}
|
||||
|
||||
DeviceFrame::DeviceFrame(CompoundDevice *dev, unsigned int position) :
|
||||
position(position),
|
||||
dev(dev)
|
||||
{
|
||||
setMinimumSize(frameSize, frameSize);
|
||||
setMaximumSize(frameSize, frameSize);
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
|
||||
|
||||
serial = new QComboBox();
|
||||
serial->setEditable(true);
|
||||
port1 = new QComboBox();
|
||||
port2 = new QComboBox();
|
||||
|
||||
connect(serial, &QComboBox::currentTextChanged, this, &DeviceFrame::settingChanged);
|
||||
connect(port1, &QComboBox::currentTextChanged, this, &DeviceFrame::settingChanged);
|
||||
connect(port2, &QComboBox::currentTextChanged, this, &DeviceFrame::settingChanged);
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->addStretch(1);
|
||||
auto l1 = new QHBoxLayout;
|
||||
l1->addStretch(1);
|
||||
l1->addWidget(serial);
|
||||
l1->addStretch(1);
|
||||
layout->addLayout(l1);
|
||||
layout->addStretch(1);
|
||||
auto l2 = new QHBoxLayout;
|
||||
l2->addWidget(port1);
|
||||
l2->addStretch(1);
|
||||
l2->addWidget(port2);
|
||||
layout->addLayout(l2);
|
||||
setLayout(layout);
|
||||
|
||||
update();
|
||||
|
||||
// Set initial state
|
||||
if(position < dev->deviceSerials.size()) {
|
||||
serial->setCurrentText(dev->deviceSerials[position]);
|
||||
for(unsigned int i=0;i<dev->portMapping.size();i++) {
|
||||
if(dev->portMapping[i].device == position) {
|
||||
if(dev->portMapping[i].port == 0) {
|
||||
port1->setCurrentIndex(i + 1);
|
||||
} else if(dev->portMapping[i].port == 1) {
|
||||
port2->setCurrentIndex(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeviceFrame::~DeviceFrame()
|
||||
{
|
||||
emit deleted();
|
||||
}
|
||||
|
||||
void DeviceFrame::setPosition(int pos)
|
||||
{
|
||||
position = pos;
|
||||
update();
|
||||
}
|
||||
|
||||
void DeviceFrame::update()
|
||||
{
|
||||
auto p1 = port1->currentText();
|
||||
auto p2 = port2->currentText();
|
||||
auto s = serial->currentText();
|
||||
port1->clear();
|
||||
port2->clear();
|
||||
serial->clear();
|
||||
|
||||
port1->addItem("Unused");
|
||||
port2->addItem("Unused");
|
||||
for(unsigned int i=0;i<dev->deviceSerials.size();i++) {
|
||||
port1->addItem("Port "+QString::number(i*2+1));
|
||||
port2->addItem("Port "+QString::number(i*2+1));
|
||||
port1->addItem("Port "+QString::number(i*2+2));
|
||||
port2->addItem("Port "+QString::number(i*2+2));
|
||||
}
|
||||
if(port1->findText(p1) >= 0) {
|
||||
port1->setCurrentText(p1);
|
||||
} else {
|
||||
port1->setCurrentIndex(0);
|
||||
}
|
||||
if(port2->findText(p2) >= 0) {
|
||||
port2->setCurrentText(p2);
|
||||
} else {
|
||||
port2->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
for(auto d : CompoundDriver::getIndividualDeviceSerials()) {
|
||||
serial->addItem(d);
|
||||
}
|
||||
serial->setCurrentText(s);
|
||||
|
||||
if(dev->sync == LibreVNADriver::Synchronization::GUI) {
|
||||
setStyleSheet("image: url(:/icons/compound_V1_USB.png);");
|
||||
} else if(dev->sync == LibreVNADriver::Synchronization::Reserved) {
|
||||
if(position == 0) {
|
||||
setStyleSheet("image: url(:/icons/compound_V1_Ref_Left.png);");
|
||||
} else if(position == dev->deviceSerials.size() - 1) {
|
||||
setStyleSheet("image: url(:/icons/compound_V1_Ref_Right.png);");
|
||||
} else {
|
||||
setStyleSheet("image: url(:/icons/compound_V1_Ref_Middle.png);");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString DeviceFrame::getSerial()
|
||||
{
|
||||
return serial->currentText();
|
||||
}
|
||||
|
||||
int DeviceFrame::getPort1()
|
||||
{
|
||||
return port1->currentIndex() - 1;
|
||||
}
|
||||
|
||||
int DeviceFrame::getPort2()
|
||||
{
|
||||
return port2->currentIndex() - 1;
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
#ifndef COMPOUNDDEVICEEDITDIALOG_H
|
||||
#define COMPOUNDDEVICEEDITDIALOG_H
|
||||
|
||||
#include "compounddevice.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace Ui {
|
||||
class CompoundDeviceEditDialog;
|
||||
}
|
||||
|
||||
class DeviceFrame : public QFrame {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DeviceFrame(CompoundDevice *dev, unsigned int position);
|
||||
~DeviceFrame();
|
||||
void setPosition(int pos);
|
||||
void update();
|
||||
QString getSerial();
|
||||
int getPort1();
|
||||
int getPort2();
|
||||
signals:
|
||||
void deleted();
|
||||
void settingChanged();
|
||||
private:
|
||||
static constexpr int frameSize = 350;
|
||||
QComboBox *serial;
|
||||
QComboBox *port1, *port2;
|
||||
|
||||
unsigned int position;
|
||||
CompoundDevice *dev;
|
||||
};
|
||||
|
||||
class CompoundDeviceEditDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CompoundDeviceEditDialog(CompoundDevice *cdev, QWidget *parent = nullptr);
|
||||
~CompoundDeviceEditDialog();
|
||||
|
||||
private slots:
|
||||
void checkIfOkay();
|
||||
private:
|
||||
static constexpr int frameSize = 350;
|
||||
void setInitialGUI();
|
||||
DeviceFrame *frameAtPosition(int pos);
|
||||
unsigned int findInsertPosition(int xcoord);
|
||||
void addFrameAtPosition(int pos, DeviceFrame *c);
|
||||
void addFrame(int index, DeviceFrame *c);
|
||||
void createDragFrame(DeviceFrame *c);
|
||||
void updateInsertIndicator(int xcoord);
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
void removeDeviceFrame(DeviceFrame *dev);
|
||||
|
||||
void updateDeviceFrames();
|
||||
|
||||
Ui::CompoundDeviceEditDialog *ui;
|
||||
CompoundDevice ldev;
|
||||
|
||||
QWidget *graph, *insertIndicator;
|
||||
QPoint dragStartPosition;
|
||||
bool dropPending;
|
||||
DeviceFrame *dragFrame;
|
||||
DeviceFrame *dropFrame;
|
||||
|
||||
std::vector<DeviceFrame*> deviceFrames;
|
||||
};
|
||||
|
||||
#endif // COMPOUNDDEVICEEDITDIALOG_H
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CompoundDeviceEditDialog</class>
|
||||
<widget class="QDialog" name="CompoundDeviceEditDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1097</width>
|
||||
<height>598</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Compound Device Edit</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<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>Synchronization:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="sync"/>
|
||||
</item>
|
||||
</layout>
|
||||
</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="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Standalone Devices</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Drag/drop into compound device</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</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="lLibreVNA1">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">border-image: url(:/icons/LibreVNAV1.svg);</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>LibreVNA V1</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1077</width>
|
||||
<height>372</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="status">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Open|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
+819
@@ -0,0 +1,819 @@
|
||||
#include "compounddriver.h"
|
||||
|
||||
#include "../librevnatcpdriver.h"
|
||||
#include "../librevnausbdriver.h"
|
||||
|
||||
#include "ui_compounddriversettingswidget.h"
|
||||
#include "compounddeviceeditdialog.h"
|
||||
#include "preferences.h"
|
||||
#include "Device/LibreVNA/devicepacketlogview.h"
|
||||
|
||||
#include <exception>
|
||||
|
||||
CompoundDriver::CompoundDriver()
|
||||
{
|
||||
connected = false;
|
||||
isIdle = true;
|
||||
triggerForwarding = false;
|
||||
SApoints = 0;
|
||||
|
||||
drivers.push_back(new LibreVNAUSBDriver);
|
||||
drivers.push_back(new LibreVNATCPDriver);
|
||||
|
||||
auto log = new QAction("View Packet Log");
|
||||
connect(log, &QAction::triggered, this, [=](){
|
||||
auto d = new DevicePacketLogView();
|
||||
d->show();
|
||||
});
|
||||
specificActions.push_back(log);
|
||||
|
||||
auto &p = Preferences::getInstance();
|
||||
for(auto d : drivers) {
|
||||
p.load(d->driverSpecificSettings());
|
||||
}
|
||||
|
||||
specificSettings.push_back(Savable::SettingDescription(&compoundJSONString, "compoundDriver.compoundDeviceJSON", "{}"));
|
||||
specificSettings.push_back(Savable::SettingDescription(&captureRawReceiverValues, "compoundDriver.captureRawReceiverValues", false));
|
||||
specificSettings.push_back(Savable::SettingDescription(&preservePhase, "compoundDriver.preservePhase", false));
|
||||
}
|
||||
|
||||
CompoundDriver::~CompoundDriver()
|
||||
{
|
||||
disconnect();
|
||||
for(auto d : drivers) {
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
std::set<QString> CompoundDriver::GetAvailableDevices()
|
||||
{
|
||||
parseCompoundJSON();
|
||||
|
||||
std::set<QString> availableSerials;
|
||||
for(auto d : drivers) {
|
||||
availableSerials.merge(d->GetAvailableDevices());
|
||||
}
|
||||
|
||||
std::set<QString> ret;
|
||||
|
||||
for(auto cd : configuredDevices) {
|
||||
bool allAvailable = true;
|
||||
for(auto s : cd->deviceSerials) {
|
||||
if(availableSerials.count(s) == 0) {
|
||||
allAvailable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(allAvailable) {
|
||||
ret.insert(cd->name);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CompoundDriver::connectTo(QString getSerial)
|
||||
{
|
||||
if(connected) {
|
||||
disconnect();
|
||||
}
|
||||
bool found = false;
|
||||
for(auto cd : configuredDevices) {
|
||||
if(cd->name == getSerial) {
|
||||
activeDevice = *cd;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
qWarning() << "Attempted to connect to an unknown compound device";
|
||||
return false;
|
||||
}
|
||||
std::vector<std::set<QString>> availableSerials;
|
||||
for(auto d : drivers) {
|
||||
availableSerials.push_back(d->GetAvailableDevices());
|
||||
}
|
||||
// attempt to connect to all individual devices
|
||||
for(auto s : activeDevice.deviceSerials) {
|
||||
LibreVNADriver *device = nullptr;
|
||||
for(unsigned int i=0;i<availableSerials.size();i++) {
|
||||
if(availableSerials[i].count(s) > 0) {
|
||||
// this driver can connect to the requested device
|
||||
if (i == 0) {
|
||||
device = new LibreVNAUSBDriver();
|
||||
break;
|
||||
} else if(i == 1) {
|
||||
auto tcp = new LibreVNATCPDriver();
|
||||
tcp->copyDetectedDevices(*static_cast<LibreVNATCPDriver*>(drivers[i]));
|
||||
device = tcp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!device) {
|
||||
qWarning() << "Unable to find required serial for compound device:" <<s;
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
auto &p = Preferences::getInstance();
|
||||
p.load(device->driverSpecificSettings());
|
||||
if(!device->connectDevice(s, true)) {
|
||||
qWarning() << "Unable to connect to required serial for compound device:" <<s;
|
||||
delete device;
|
||||
disconnect();
|
||||
return false;
|
||||
} else {
|
||||
devices.push_back(device);
|
||||
}
|
||||
}
|
||||
|
||||
// make device connections
|
||||
for(auto dev : devices) {
|
||||
// Create device connections
|
||||
connect(dev, &LibreVNADriver::ConnectionLost, this, &CompoundDriver::ConnectionLost, Qt::QueuedConnection);
|
||||
connect(dev, &LibreVNADriver::LogLineReceived, this, [=](QString line){
|
||||
emit LogLineReceived(line.prepend(dev->getSerial()+": "));
|
||||
});
|
||||
connect(dev, &LibreVNADriver::InfoUpdated, this, [=]() {
|
||||
updatedInfo(dev);
|
||||
});
|
||||
connect(dev, &LibreVNADriver::passOnReceivedPacket, this, [=](const Protocol::PacketInfo& packet) {
|
||||
incomingPacket(dev, packet);
|
||||
});
|
||||
}
|
||||
|
||||
connected = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CompoundDriver::disconnect()
|
||||
{
|
||||
for(auto d : devices) {
|
||||
QObject::disconnect(d, nullptr, this, nullptr);
|
||||
d->disconnect();
|
||||
delete d;
|
||||
}
|
||||
devices.clear();
|
||||
deviceInfos.clear();
|
||||
deviceStatus.clear();
|
||||
compoundSABuffer.clear();
|
||||
compoundVNABuffer.clear();
|
||||
connected = false;
|
||||
}
|
||||
|
||||
QString CompoundDriver::getSerial()
|
||||
{
|
||||
if(connected) {
|
||||
return activeDevice.name;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::set<DeviceDriver::Flag> CompoundDriver::getFlags()
|
||||
{
|
||||
std::set<DeviceDriver::Flag> ret;
|
||||
if(lastStatus.V1.extRefInUse) {
|
||||
ret.insert(Flag::ExtRef);
|
||||
}
|
||||
if(!lastStatus.V1.source_locked || !lastStatus.V1.LO1_locked) {
|
||||
ret.insert(Flag::Unlocked);
|
||||
}
|
||||
if(lastStatus.V1.unlevel) {
|
||||
ret.insert(Flag::Unlevel);
|
||||
}
|
||||
if(lastStatus.V1.ADC_overload) {
|
||||
ret.insert(Flag::Overload);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString CompoundDriver::getStatus()
|
||||
{
|
||||
QString ret;
|
||||
ret.append("HW Rev.");
|
||||
ret.append(info.hardware_version);
|
||||
ret.append(" FW "+info.firmware_version);
|
||||
ret.append(" Temps: Source PLL: "+QString::number(lastStatus.V1.temp_source)+"°C LO PLL: "+QString::number(lastStatus.V1.temp_LO1)+"°C MCU: "+QString::number(lastStatus.V1.temp_MCU)+"°C");
|
||||
ret.append(" Reference:");
|
||||
if(lastStatus.V1.extRefInUse) {
|
||||
ret.append("External");
|
||||
} else {
|
||||
ret.append("Internal");
|
||||
if(lastStatus.V1.extRefAvailable) {
|
||||
ret.append(" (External available)");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QWidget *CompoundDriver::createSettingsWidget()
|
||||
{
|
||||
auto w = new QWidget();
|
||||
auto ui = new Ui::CompoundDriverSettingsWidget;
|
||||
ui->setupUi(w);
|
||||
|
||||
// Set initial values
|
||||
ui->CaptureRawReceiverValues->setChecked(captureRawReceiverValues);
|
||||
ui->PreservePhase->setChecked(preservePhase);
|
||||
|
||||
// make connections
|
||||
connect(ui->CaptureRawReceiverValues, &QCheckBox::toggled, this, [=](){
|
||||
captureRawReceiverValues = ui->CaptureRawReceiverValues->isChecked();
|
||||
});
|
||||
connect(ui->PreservePhase, &QCheckBox::toggled, this, [=](){
|
||||
preservePhase = ui->PreservePhase->isChecked();
|
||||
});
|
||||
|
||||
connect(ui->compoundList, &QListWidget::doubleClicked, [=](){
|
||||
auto index = ui->compoundList->currentRow();
|
||||
if(index >= 0 && index < (int) configuredDevices.size()) {
|
||||
auto d = new CompoundDeviceEditDialog(configuredDevices[index]);
|
||||
connect(d, &QDialog::accepted, [=](){
|
||||
ui->compoundList->item(index)->setText(configuredDevices[index]->getDesription());
|
||||
createCompoundJSON();
|
||||
});
|
||||
d->show();
|
||||
}
|
||||
});
|
||||
connect(ui->compoundAdd, &QPushButton::clicked, [=](){
|
||||
auto cd = new CompoundDevice;
|
||||
auto d = new CompoundDeviceEditDialog(cd);
|
||||
connect(d, &QDialog::accepted, [=](){
|
||||
configuredDevices.push_back(cd);
|
||||
ui->compoundList->addItem(cd->getDesription());
|
||||
createCompoundJSON();
|
||||
});
|
||||
connect(d, &QDialog::rejected, [=](){
|
||||
delete cd;
|
||||
});
|
||||
d->show();
|
||||
});
|
||||
connect(ui->compoundDelete, &QPushButton::clicked, [=](){
|
||||
auto index = ui->compoundList->currentRow();
|
||||
if(index >= 0 && index < (int) configuredDevices.size()) {
|
||||
// delete the actual compound device
|
||||
delete configuredDevices[index];
|
||||
// delete the line in the GUI list
|
||||
delete ui->compoundList->takeItem(index);
|
||||
// remove compound device from list
|
||||
configuredDevices.erase(configuredDevices.begin() + index);
|
||||
createCompoundJSON();
|
||||
}
|
||||
});
|
||||
|
||||
for(auto cd : configuredDevices) {
|
||||
ui->compoundList->addItem(cd->getDesription());
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
QStringList CompoundDriver::availableVNAMeasurements()
|
||||
{
|
||||
QStringList ret;
|
||||
for(unsigned int i=1;i<=info.Limits.VNA.ports;i++) {
|
||||
for(unsigned int j=1;j<=info.Limits.VNA.ports;j++) {
|
||||
ret.push_back("S"+QString::number(i)+QString::number(j));
|
||||
}
|
||||
}
|
||||
if(captureRawReceiverValues) {
|
||||
for(unsigned int i=1;i<=info.Limits.VNA.ports;i++) {
|
||||
for(unsigned int j=0;j<info.Limits.VNA.ports;j++) {
|
||||
ret.push_back("RawPort"+QString::number(i)+"Stage"+QString::number(j));
|
||||
ret.push_back("RawPort"+QString::number(i)+"Stage"+QString::number(j)+"Ref");
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CompoundDriver::setVNA(const DeviceDriver::VNASettings &s, std::function<void (bool)> cb)
|
||||
{
|
||||
if(!supports(Feature::VNA)) {
|
||||
return false;
|
||||
}
|
||||
if(s.excitedPorts.size() == 0) {
|
||||
return setIdle(cb);
|
||||
}
|
||||
|
||||
setIdle([this](bool){
|
||||
enableTriggerForwarding();
|
||||
qDebug() << "Start trigger forwarding";
|
||||
});
|
||||
|
||||
// create port->stage mapping
|
||||
portStageMapping.clear();
|
||||
for(unsigned int i=0;i<s.excitedPorts.size();i++) {
|
||||
portStageMapping[s.excitedPorts[i]] = i;
|
||||
}
|
||||
|
||||
zerospan = (s.freqStart == s.freqStop) && (s.dBmStart == s.dBmStop);
|
||||
VNApoints = s.points;
|
||||
// create vector of currently used stimulus ports
|
||||
std::vector<CompoundDevice::PortMapping> activeMapping;
|
||||
for(auto p : s.excitedPorts) {
|
||||
activeMapping.push_back(activeDevice.portMapping[p-1]);
|
||||
}
|
||||
// Configure the devices
|
||||
results.clear();
|
||||
bool success = true;
|
||||
for(unsigned int i=0;i<devices.size();i++) {
|
||||
auto dev = devices[i];
|
||||
dev->setSynchronization(activeDevice.sync, i == 0);
|
||||
auto devSetting = s;
|
||||
// indicate the number of stages
|
||||
devSetting.excitedPorts = std::vector<int>(s.excitedPorts.size(), 0);
|
||||
// activate the ports of this specific device at the correct stage
|
||||
auto p1Stage = CompoundDevice::PortMapping::findActiveStage(activeMapping, i, 0);
|
||||
if(p1Stage < s.excitedPorts.size()) {
|
||||
devSetting.excitedPorts[p1Stage] = 1;
|
||||
}
|
||||
auto p2Stage = CompoundDevice::PortMapping::findActiveStage(activeMapping, i, 1);
|
||||
if(p2Stage < s.excitedPorts.size()) {
|
||||
devSetting.excitedPorts[p2Stage] = 2;
|
||||
}
|
||||
success &= devices[i]->setVNA(devSetting, [=](bool success){
|
||||
if(cb) {
|
||||
results[devices[i]] = success;
|
||||
checkIfAllTransmissionsComplete(cb);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lastNonIdleSettings.type = Types::VNA;
|
||||
lastNonIdleSettings.vna = s;
|
||||
isIdle = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
QStringList CompoundDriver::availableSAMeasurements()
|
||||
{
|
||||
QStringList ret;
|
||||
for(unsigned int i=1;i<=info.Limits.SA.ports;i++) {
|
||||
ret.push_back("PORT"+QString::number(i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CompoundDriver::setSA(const DeviceDriver::SASettings &s, std::function<void (bool)> cb)
|
||||
{
|
||||
if(!supports(Feature::SA)) {
|
||||
return false;
|
||||
}
|
||||
zerospan = s.freqStart == s.freqStop;
|
||||
|
||||
setIdle([this](bool){
|
||||
enableTriggerForwarding();
|
||||
qDebug() << "Start trigger forwarding";
|
||||
});
|
||||
|
||||
// Configure the devices
|
||||
results.clear();
|
||||
bool success = true;
|
||||
for(unsigned int i=0;i<devices.size();i++) {
|
||||
auto dev = devices[i];
|
||||
dev->setSynchronization(activeDevice.sync, i == 0);
|
||||
auto devSettings = s;
|
||||
devSettings.trackingGenerator = false;
|
||||
devSettings.trackingPort = 0;
|
||||
if(s.trackingGenerator) {
|
||||
if(activeDevice.portMapping[s.trackingPort-1].device == i) {
|
||||
// tracking generator active on this device
|
||||
devSettings.trackingGenerator = true;
|
||||
devSettings.trackingPort = activeDevice.portMapping[s.trackingPort-1].port + 1;
|
||||
}
|
||||
}
|
||||
success &= devices[i]->setSA(devSettings, [=](bool success){
|
||||
if(cb) {
|
||||
results[devices[i]] = success;
|
||||
checkIfAllTransmissionsComplete(cb);
|
||||
}
|
||||
});
|
||||
}
|
||||
SApoints = devices[0]->getSApoints();
|
||||
for(unsigned int i=1;i<devices.size();i++) {
|
||||
if(devices[i]->getSApoints() != SApoints) {
|
||||
qWarning() << "Individual devices report different number of SA points, unable to start compound SA sweep";
|
||||
setIdle();
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lastNonIdleSettings.type = Types::SA;
|
||||
lastNonIdleSettings.sa = s;
|
||||
isIdle = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
QStringList CompoundDriver::availableSGPorts()
|
||||
{
|
||||
QStringList ret;
|
||||
for(unsigned int i=1;i<info.Limits.Generator.ports;i++) {
|
||||
ret.push_back("PORT"+QString::number(i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CompoundDriver::setSG(const DeviceDriver::SGSettings &s)
|
||||
{
|
||||
if(!supports(Feature::Generator)) {
|
||||
return false;
|
||||
}
|
||||
// configure all devices
|
||||
bool success = true;
|
||||
for(unsigned int i=0;i<devices.size();i++) {
|
||||
auto devSettings = s;
|
||||
devSettings.port = 0;
|
||||
if(s.port > 0) {
|
||||
if(activeDevice.portMapping[s.port-1].device == i) {
|
||||
// this device has the active port
|
||||
devSettings.port = activeDevice.portMapping[s.port-1].port+1;
|
||||
}
|
||||
}
|
||||
success &= devices[i]->setSG(devSettings);
|
||||
}
|
||||
|
||||
lastNonIdleSettings.type = Types::SG;
|
||||
lastNonIdleSettings.sg = s;
|
||||
isIdle = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool CompoundDriver::setIdle(std::function<void (bool)> cb)
|
||||
{
|
||||
disableTriggerForwarding();
|
||||
qDebug() << "Stop trigger forwarding";
|
||||
auto success = true;
|
||||
results.clear();
|
||||
for(auto dev : devices) {
|
||||
dev->sendWithoutPayload(Protocol::PacketType::ClearTrigger);
|
||||
success &= dev->setIdle([=](bool success){
|
||||
if(cb) {
|
||||
results[dev] = success;
|
||||
checkIfAllTransmissionsComplete(cb);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isIdle = true;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
QStringList CompoundDriver::availableExtRefInSettings()
|
||||
{
|
||||
if(!connected) {
|
||||
return QStringList();
|
||||
}
|
||||
auto list = devices[0]->availableExtRefInSettings();
|
||||
QSet<QString> set(list.begin(), list.end());
|
||||
for(unsigned int i=1;i<devices.size();i++) {
|
||||
list = devices[i]->availableExtRefInSettings();
|
||||
set = set.intersect({list.begin(), list.end()});
|
||||
}
|
||||
return QStringList(set.begin(), set.end());
|
||||
}
|
||||
|
||||
QStringList CompoundDriver::availableExtRefOutSettings()
|
||||
{
|
||||
if(!connected) {
|
||||
return QStringList();
|
||||
}
|
||||
auto list = devices[0]->availableExtRefOutSettings();
|
||||
QSet<QString> set(list.begin(), list.end());
|
||||
for(unsigned int i=1;i<devices.size();i++) {
|
||||
list = devices[i]->availableExtRefOutSettings();
|
||||
set = set.intersect({list.begin(), list.end()});
|
||||
}
|
||||
return QStringList(set.begin(), set.end());
|
||||
}
|
||||
|
||||
bool CompoundDriver::setExtRef(QString option_in, QString option_out)
|
||||
{
|
||||
auto success = true;
|
||||
|
||||
qDebug() << "Ref change start";
|
||||
if(isIdle) {
|
||||
// can immediately switch reference settings
|
||||
for(auto dev : devices) {
|
||||
success &= dev->setExtRef(option_in, option_out);
|
||||
}
|
||||
} else {
|
||||
// can not switch during a sweep
|
||||
// set to idle first
|
||||
setIdle();
|
||||
// change reference
|
||||
for(auto dev : devices) {
|
||||
success &= dev->setExtRef(option_in, option_out);
|
||||
}
|
||||
// restore last non idle state
|
||||
switch(lastNonIdleSettings.type) {
|
||||
case Types::VNA:
|
||||
setVNA(lastNonIdleSettings.vna);
|
||||
break;
|
||||
case Types::SA:
|
||||
setSA(lastNonIdleSettings.sa);
|
||||
break;
|
||||
case Types::SG:
|
||||
setSG(lastNonIdleSettings.sg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
qDebug() << "Ref change stop";
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
std::set<QString> CompoundDriver::getIndividualDeviceSerials()
|
||||
{
|
||||
std::vector<LibreVNADriver*> drivers;
|
||||
drivers.push_back(new LibreVNAUSBDriver);
|
||||
drivers.push_back(new LibreVNATCPDriver);
|
||||
|
||||
auto &p = Preferences::getInstance();
|
||||
std::set<QString> ret;
|
||||
for(auto d : drivers) {
|
||||
p.load(d->driverSpecificSettings());
|
||||
ret.merge(d->GetAvailableDevices());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CompoundDriver::triggerReceived(LibreVNADriver *device, bool set)
|
||||
{
|
||||
triggerMutex.lock();
|
||||
if(activeDevice.sync == LibreVNADriver::Synchronization::GUI && triggerForwarding) {
|
||||
for(unsigned int i=0;i<devices.size();i++) {
|
||||
if(devices[i] == device) {
|
||||
// pass on to the next device
|
||||
if(i < devices.size() - 1) {
|
||||
qDebug() << "Passing on trigger" << set << "from" << device->getSerial() << "to" << devices[i+1]->getSerial();
|
||||
devices[i+1]->sendWithoutPayload(set ? Protocol::PacketType::SetTrigger : Protocol::PacketType::ClearTrigger);
|
||||
} else {
|
||||
qDebug() << "Passing on trigger" << set << "from" << device->getSerial() << "to" << devices[0]->getSerial();
|
||||
devices[0]->sendWithoutPayload(set ? Protocol::PacketType::SetTrigger : Protocol::PacketType::ClearTrigger);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
triggerMutex.unlock();
|
||||
}
|
||||
|
||||
void CompoundDriver::parseCompoundJSON()
|
||||
{
|
||||
try {
|
||||
configuredDevices.clear();
|
||||
if(compoundJSONString.isEmpty()) {
|
||||
// empty string will fail JSON parsing. Abort now instead of running into the exception
|
||||
return;
|
||||
}
|
||||
nlohmann::json jc = nlohmann::json::parse(compoundJSONString.toStdString());
|
||||
for(auto j : jc) {
|
||||
auto cd = new CompoundDevice();
|
||||
cd->fromJSON(j);
|
||||
configuredDevices.push_back(cd);
|
||||
}
|
||||
} catch(const std::exception& e){
|
||||
qDebug() << "Failed to parse compound device string: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void CompoundDriver::createCompoundJSON()
|
||||
{
|
||||
if(configuredDevices.size() > 0) {
|
||||
nlohmann::json j;
|
||||
for(auto cd : configuredDevices) {
|
||||
j.push_back(cd->toJSON());
|
||||
}
|
||||
compoundJSONString = QString::fromStdString(j.dump());
|
||||
} else {
|
||||
compoundJSONString = "[]";
|
||||
}
|
||||
}
|
||||
|
||||
void CompoundDriver::incomingPacket(LibreVNADriver *device, const Protocol::PacketInfo &p)
|
||||
{
|
||||
switch(p.type) {
|
||||
case Protocol::PacketType::DeviceStatus:
|
||||
updatedStatus(device, p.status);
|
||||
break;
|
||||
case Protocol::PacketType::VNADatapoint:
|
||||
datapointReceivecd(device, p.VNAdatapoint);
|
||||
break;
|
||||
case Protocol::PacketType::SpectrumAnalyzerResult:
|
||||
spectrumResultReceived(device, p.spectrumResult);
|
||||
break;
|
||||
default:
|
||||
// nothing to do for other packet types
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CompoundDriver::updatedInfo(LibreVNADriver *device)
|
||||
{
|
||||
deviceInfos[device] = device->getInfo();
|
||||
if(deviceInfos.size() == devices.size()) {
|
||||
// got infos from all devices
|
||||
info = devices[0]->getInfo();
|
||||
for(unsigned int i=1;i<devices.size();i++) {
|
||||
info.subset(devices[i]->getInfo());
|
||||
}
|
||||
// overwrite number of ports (not all physical ports may be configured for this compound device)
|
||||
info.Limits.VNA.ports = activeDevice.portMapping.size();
|
||||
info.Limits.Generator.ports = activeDevice.portMapping.size();
|
||||
info.Limits.SA.ports = activeDevice.portMapping.size();
|
||||
emit InfoUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
void CompoundDriver::updatedStatus(LibreVNADriver *device, const Protocol::DeviceStatus &status)
|
||||
{
|
||||
deviceStatus[device] = status;
|
||||
if(deviceStatus.size() == devices.size()) {
|
||||
// got status from all devices
|
||||
for(unsigned int i=0;i<devices.size();i++) {
|
||||
auto devStat = deviceStatus[devices[i]];
|
||||
if(i==0) {
|
||||
lastStatus = devStat;
|
||||
} else {
|
||||
lastStatus.V1.extRefAvailable &= devStat.V1.extRefAvailable;
|
||||
lastStatus.V1.extRefInUse |= devStat.V1.extRefInUse;
|
||||
lastStatus.V1.FPGA_configured &= devStat.V1.FPGA_configured;
|
||||
lastStatus.V1.source_locked &= devStat.V1.source_locked;
|
||||
lastStatus.V1.LO1_locked &= devStat.V1.LO1_locked;
|
||||
lastStatus.V1.ADC_overload |= devStat.V1.ADC_overload;
|
||||
lastStatus.V1.unlevel |= devStat.V1.unlevel;
|
||||
lastStatus.V1.temp_source = std::max(lastStatus.V1.temp_source, devStat.V1.temp_source);
|
||||
lastStatus.V1.temp_LO1 = std::max(lastStatus.V1.temp_LO1, devStat.V1.temp_LO1);
|
||||
lastStatus.V1.temp_MCU = std::max(lastStatus.V1.temp_MCU, devStat.V1.temp_MCU);
|
||||
}
|
||||
}
|
||||
emit StatusUpdated();
|
||||
emit FlagsUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
void CompoundDriver::spectrumResultReceived(LibreVNADriver *dev, Protocol::SpectrumAnalyzerResult res)
|
||||
{
|
||||
if(!compoundSABuffer.count(res.pointNum)) {
|
||||
compoundSABuffer[res.pointNum] = std::map<LibreVNADriver*, Protocol::SpectrumAnalyzerResult>();
|
||||
}
|
||||
auto &buf = compoundSABuffer[res.pointNum];
|
||||
buf[dev] = res;
|
||||
if(buf.size() == devices.size()) {
|
||||
// Got datapoints from all devices, can create merged VNA result
|
||||
SAMeasurement m;
|
||||
m.pointNum = res.pointNum;
|
||||
if(zerospan) {
|
||||
m.us = res.us;
|
||||
} else {
|
||||
m.frequency = res.frequency;
|
||||
}
|
||||
// assemble data
|
||||
for(unsigned int port=0;port<activeDevice.portMapping.size();port++) {
|
||||
auto device = devices[activeDevice.portMapping[port].device];
|
||||
auto devicePort = activeDevice.portMapping[port].port;
|
||||
|
||||
QString name = "PORT"+QString::number(port+1);
|
||||
if(devicePort == 0) {
|
||||
m.measurements[name] = buf[device].port1;
|
||||
} else {
|
||||
m.measurements[name] = buf[device].port2;
|
||||
}
|
||||
}
|
||||
|
||||
emit SAmeasurementReceived(m);
|
||||
|
||||
// Clear this and all (incomplete) older datapoint buffers
|
||||
auto it = compoundSABuffer.begin();
|
||||
while(it != compoundSABuffer.end()) {
|
||||
if(it->first <= res.pointNum) {
|
||||
it = compoundSABuffer.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompoundDriver::enableTriggerForwarding()
|
||||
{
|
||||
triggerMutex.lock();
|
||||
for(auto d : devices) {
|
||||
connect(d, &LibreVNADriver::receivedTrigger, this, &CompoundDriver::triggerReceived, Qt::UniqueConnection);
|
||||
}
|
||||
triggerForwarding = true;
|
||||
triggerMutex.unlock();
|
||||
}
|
||||
|
||||
void CompoundDriver::disableTriggerForwarding()
|
||||
{
|
||||
triggerMutex.lock();
|
||||
triggerForwarding = false;
|
||||
for(auto d : devices) {
|
||||
QObject::disconnect(d, &LibreVNADriver::receivedTrigger, this, &CompoundDriver::triggerReceived);
|
||||
}
|
||||
triggerMutex.unlock();
|
||||
}
|
||||
|
||||
void CompoundDriver::datapointReceivecd(LibreVNADriver *dev, Protocol::VNADatapoint<32> *data)
|
||||
{
|
||||
if(!compoundVNABuffer.count(data->pointNum)) {
|
||||
compoundVNABuffer[data->pointNum] = std::map<LibreVNADriver*, Protocol::VNADatapoint<32>*>();
|
||||
}
|
||||
auto &buf = compoundVNABuffer[data->pointNum];
|
||||
// create copy of datapoint as it will be deleted by the device driver
|
||||
buf[dev] = new Protocol::VNADatapoint<32>(*data);
|
||||
if(buf.size() == devices.size()) {
|
||||
// Got datapoints from all devices, can create merged VNA result
|
||||
VNAMeasurement m;
|
||||
m.pointNum = data->pointNum;
|
||||
m.Z0 = 50.0;
|
||||
if(zerospan) {
|
||||
m.us = data->us;
|
||||
} else {
|
||||
m.frequency = data->frequency;
|
||||
m.dBm = (double) data->cdBm / 100;
|
||||
}
|
||||
// assemble data
|
||||
for(auto map : portStageMapping) {
|
||||
// map.first is the port (starts at one)
|
||||
// map.second is the stage at which this port had the stimulus (starts at zero)
|
||||
|
||||
// figure out which device had the stimulus for the port...
|
||||
auto stimulusDev = devices[activeDevice.portMapping[map.first-1].device];
|
||||
// ...and which device port was used for the stimulus...
|
||||
auto stimulusDevPort = activeDevice.portMapping[map.first-1].port;
|
||||
// ...grab the reference receiver data
|
||||
std::complex<double> ref = buf[stimulusDev]->getValue(map.second, stimulusDevPort, true);
|
||||
|
||||
// for all ports of the compound device...
|
||||
for(unsigned int i=0;i<activeDevice.portMapping.size();i++) {
|
||||
// ...figure out which physical device and port was used for this input...
|
||||
auto inputDevice = devices[activeDevice.portMapping[i].device];
|
||||
// ...and grab the data
|
||||
auto inputPort = activeDevice.portMapping[i].port;
|
||||
std::complex<double> input = buf[inputDevice]->getValue(map.second, inputPort, false);
|
||||
if(!std::isnan(ref.real()) && !std::isnan(input.real())) {
|
||||
// got both required measurements
|
||||
QString name = "S"+QString::number(i+1)+QString::number(map.first);
|
||||
auto S = input / ref;
|
||||
if(!preservePhase && (inputDevice != stimulusDev)) {
|
||||
// can't use phase information when measuring across devices
|
||||
S = abs(S);
|
||||
}
|
||||
m.measurements[name] = S;
|
||||
}
|
||||
|
||||
if(captureRawReceiverValues) {
|
||||
QString name = "RawPort"+QString::number(inputPort+1)+"Stage"+QString::number(map.second);
|
||||
m.measurements[name] = input;
|
||||
name = "RawPort"+QString::number(inputPort+1)+"Stage"+QString::number(map.second)+"Ref";
|
||||
m.measurements[name] = buf[inputDevice]->getValue(map.second, inputPort, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit VNAmeasurementReceived(m);
|
||||
|
||||
// Clear this and all (incomplete) older datapoint buffers
|
||||
int pointNum = data->pointNum;
|
||||
while(compoundVNABuffer.count(pointNum)) {
|
||||
auto &buf = compoundVNABuffer[pointNum];
|
||||
for(auto d : buf) {
|
||||
delete d.second;
|
||||
}
|
||||
compoundVNABuffer.erase(pointNum);
|
||||
// move on to previous point
|
||||
if(pointNum > 0) {
|
||||
pointNum--;
|
||||
} else {
|
||||
pointNum = VNApoints - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompoundDriver::checkIfAllTransmissionsComplete(std::function<void (bool)> cb)
|
||||
{
|
||||
if(results.size() == devices.size()) {
|
||||
// got all responses
|
||||
bool success = true;
|
||||
for(auto res : results) {
|
||||
if(res.second != true) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(cb) {
|
||||
cb(success);
|
||||
}
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
#ifndef COMPOUNDDRIVER_H
|
||||
#define COMPOUNDDRIVER_H
|
||||
|
||||
#include "../../devicedriver.h"
|
||||
#include "compounddevice.h"
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
class CompoundDriver : public DeviceDriver
|
||||
{
|
||||
public:
|
||||
CompoundDriver();
|
||||
~CompoundDriver();
|
||||
|
||||
/**
|
||||
* @brief Returns the driver name. It must be unique across all implemented drivers and is used to identify the driver
|
||||
* @return driver name
|
||||
*/
|
||||
virtual QString getDriverName() override {return "LibreVNA/Compound";}
|
||||
/**
|
||||
* @brief Lists all available devices by their serial numbers
|
||||
* @return Serial numbers of detected devices
|
||||
*/
|
||||
virtual std::set<QString> GetAvailableDevices() override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Connects to a device, given by its serial number
|
||||
*
|
||||
* @param serial Serial number of device that should be connected to
|
||||
* @return true if connection successful, otherwise false
|
||||
*/
|
||||
virtual bool connectTo(QString getSerial) override;
|
||||
/**
|
||||
* @brief Disconnects from device. Has no effect if no device was connected
|
||||
*/
|
||||
virtual void disconnect() override;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Returns the serial number of the connected device
|
||||
* @return Serial number of connected device (empty string if no device is connected)
|
||||
*/
|
||||
virtual QString getSerial() override;
|
||||
|
||||
/**
|
||||
* @brief Returns the device information. This function will be called when a device has been connected. Its return value must be valid
|
||||
* directly after returning from DeviceDriver::connectTo()
|
||||
*
|
||||
* Emit the InfoUpdate() signal whenever the return value of this function changes.
|
||||
*
|
||||
* @return Device information
|
||||
*/
|
||||
virtual Info getInfo() override {return info;}
|
||||
|
||||
/**
|
||||
* @brief Returns a set of all active flags
|
||||
*
|
||||
* There is also a convenience function to check a specific flag, see DeviceDriver::asserted()
|
||||
*
|
||||
* @return Set of active flags
|
||||
*/
|
||||
virtual std::set<Flag> getFlags() override;
|
||||
|
||||
/**
|
||||
* @brief Returns the device status string. It will be displayed in the status bar of the application
|
||||
*
|
||||
* Emit the StatusUpdated() signal whenever the return value of this function changes
|
||||
*
|
||||
* @return Status string
|
||||
*/
|
||||
virtual QString getStatus() override;
|
||||
|
||||
/**
|
||||
* @brief Returns a widget to edit the driver specific settings.
|
||||
*
|
||||
* The widget is displayed in the global settings dialog and allows the user to edit the settings
|
||||
* specific to this driver. The application takes ownership of the widget after returning,
|
||||
* create a new widget for every call to this function. If the driver has no specific settings
|
||||
* or the settings do not need to be editable by the user, return a nullptr. In this case, no
|
||||
* page for this driver is created in the settings dialog
|
||||
* @return newly constructed settings widget or nullptr
|
||||
*/
|
||||
virtual QWidget* createSettingsWidget() override;
|
||||
|
||||
/**
|
||||
* @brief Names of available measurements.
|
||||
*
|
||||
* The names must be identical to the names used in the returned VNAMeasurement.
|
||||
* Typically the S parameters, e.g. this function may return {"S11","S12","S21","S22"} but any other names are also allowed.
|
||||
*
|
||||
* @return List of available VNA measurement parameters
|
||||
*/
|
||||
virtual QStringList availableVNAMeasurements() override;
|
||||
|
||||
/**
|
||||
* @brief Configures the VNA and starts a sweep
|
||||
* @param s VNA settings
|
||||
* @param cb Callback, must be called after the VNA has been configured
|
||||
* @return true if configuration successful, false otherwise
|
||||
*/
|
||||
virtual bool setVNA(const VNASettings &s, std::function<void(bool)> cb = nullptr) override;
|
||||
|
||||
/**
|
||||
* @brief Names of available measurements.
|
||||
*
|
||||
* The names must be identical to the names used in the returned SAMeasurement.
|
||||
* Typically the port names, e.g. this function may return {"PORT1","PORT2"} but any other names are also allowed.
|
||||
*
|
||||
* @return List of available SA measurement parameters
|
||||
*/
|
||||
virtual QStringList availableSAMeasurements() override;
|
||||
/**
|
||||
* @brief Configures the SA and starts a sweep
|
||||
* @param s SA settings
|
||||
* @param cb Callback, must be called after the SA has been configured
|
||||
* @return true if configuration successful, false otherwise
|
||||
*/
|
||||
virtual bool setSA(const SASettings &s, std::function<void(bool)> cb = nullptr) override;
|
||||
|
||||
/**
|
||||
* @brief Returns the number of points in one spectrum analyzer sweep (as configured by the last setSA() call)
|
||||
* @return Number of points in the sweep
|
||||
*/
|
||||
virtual unsigned int getSApoints() override {return SApoints;}
|
||||
|
||||
/**
|
||||
* @brief Names of available generator ports.
|
||||
*
|
||||
* Typically the port names, e.g. this function may return {"PORT1","PORT2"} but any other names are also allowed.
|
||||
*
|
||||
* @return List of available SA measurement parameters
|
||||
*/
|
||||
virtual QStringList availableSGPorts() override;
|
||||
/**
|
||||
* @brief Configures the generator
|
||||
* @param s Generator settings
|
||||
* @return true if configuration successful, false otherwise
|
||||
*/
|
||||
virtual bool setSG(const SGSettings &s) override;
|
||||
|
||||
/**
|
||||
* @brief Sets the device to idle
|
||||
*
|
||||
* Stops all sweeps and signal generation
|
||||
*
|
||||
* @param cb Callback, must be called after the device has stopped all operations
|
||||
* @return true if configuration successful, false otherwise
|
||||
*/
|
||||
virtual bool setIdle(std::function<void(bool)> cb = nullptr) override;
|
||||
|
||||
/**
|
||||
* @brief Returns the available options for the external reference input
|
||||
* @return External reference input options
|
||||
*/
|
||||
virtual QStringList availableExtRefInSettings() override;
|
||||
|
||||
/**
|
||||
* @brief Returns the available options for the external reference output
|
||||
* @return External reference output options
|
||||
*/
|
||||
virtual QStringList availableExtRefOutSettings() override;
|
||||
|
||||
/**
|
||||
* @brief Configures the external reference input/output
|
||||
* @param option_in Reference input option (one of the options returned by availableExtRefInSettings())
|
||||
* @param option_out Reference output option (one of the options returned by availableExtRefOutSettings())
|
||||
* @return true if configuration successful, false otherwise
|
||||
*/
|
||||
virtual bool setExtRef(QString option_in, QString option_out) override;
|
||||
|
||||
static std::set<QString> getIndividualDeviceSerials();
|
||||
|
||||
private slots:
|
||||
void triggerReceived(LibreVNADriver *device, bool set);
|
||||
private:
|
||||
void parseCompoundJSON();
|
||||
void createCompoundJSON();
|
||||
void incomingPacket(LibreVNADriver *device, const Protocol::PacketInfo &p);
|
||||
void updatedInfo(LibreVNADriver *device);
|
||||
void updatedStatus(LibreVNADriver *device, const Protocol::DeviceStatus &status);
|
||||
void datapointReceivecd(LibreVNADriver *dev, Protocol::VNADatapoint<32> *data);
|
||||
void spectrumResultReceived(LibreVNADriver *dev, Protocol::SpectrumAnalyzerResult res);
|
||||
void enableTriggerForwarding();
|
||||
void disableTriggerForwarding();
|
||||
|
||||
Info info;
|
||||
std::map<LibreVNADriver*, Info> deviceInfos;
|
||||
std::map<LibreVNADriver*, Protocol::DeviceStatus> deviceStatus;
|
||||
std::map<int, std::map<LibreVNADriver*, Protocol::VNADatapoint<32>*>> compoundVNABuffer;
|
||||
std::map<int, std::map<LibreVNADriver*, Protocol::SpectrumAnalyzerResult>> compoundSABuffer;
|
||||
Protocol::DeviceStatus lastStatus;
|
||||
|
||||
// Parsed configuration of compound devices (as extracted from compoundJSONString
|
||||
std::vector<CompoundDevice*> configuredDevices;
|
||||
|
||||
std::map<int, int> portStageMapping; // maps from excitedPort (count starts at one) to stage (count starts at zero)
|
||||
|
||||
// All possible drivers to interact with a LibreVNA
|
||||
std::vector<LibreVNADriver*> drivers;
|
||||
|
||||
// Configuration of the device we are connected to
|
||||
CompoundDevice activeDevice;
|
||||
bool connected;
|
||||
bool triggerForwarding;
|
||||
QMutex triggerMutex;
|
||||
std::vector<LibreVNADriver*> devices;
|
||||
bool zerospan;
|
||||
bool isIdle;
|
||||
enum class Types{VNA, SA, SG};
|
||||
struct {
|
||||
VNASettings vna;
|
||||
SASettings sa;
|
||||
SGSettings sg;
|
||||
Types type;
|
||||
} lastNonIdleSettings;
|
||||
unsigned int VNApoints;
|
||||
unsigned int SApoints;
|
||||
|
||||
// Driver specific settings
|
||||
bool captureRawReceiverValues;
|
||||
bool preservePhase;
|
||||
QString compoundJSONString;
|
||||
|
||||
// Buffers for storing individual device answers
|
||||
std::map<LibreVNADriver*, bool> results;
|
||||
void checkIfAllTransmissionsComplete(std::function<void(bool)> cb = nullptr);
|
||||
};
|
||||
|
||||
#endif // COMPOUNDDRIVER_H
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CompoundDriverSettingsWidget</class>
|
||||
<widget class="QWidget" name="CompoundDriverSettingsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>893</width>
|
||||
<height>559</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>CompoundDriverSettingsWidget</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_13">
|
||||
<item>
|
||||
<widget class="QListWidget" name="compoundList"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_19">
|
||||
<item>
|
||||
<widget class="QPushButton" name="compoundAdd">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add" resource="../../../icons.qrc">
|
||||
<normaloff>:/icons/add.png</normaloff>
|
||||
<normalon>:/icons/add.png</normalon>:/icons/add.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="compoundDelete">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-remove" resource="../../../icons.qrc">
|
||||
<normaloff>:/icons/remove.png</normaloff>
|
||||
<normalon>:/icons/remove.png</normalon>:/icons/remove.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_8">
|
||||
<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>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_25">
|
||||
<property name="title">
|
||||
<string>Debug - Acquisition</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_32">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="CaptureRawReceiverValues">
|
||||
<property name="text">
|
||||
<string>Capture raw receiver values</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="PreservePhase">
|
||||
<property name="text">
|
||||
<string>Preserve phase for measurements between devices</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../../icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AddAmplitudePointsDialog</class>
|
||||
<widget class="QDialog" name="AddAmplitudePointsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>564</width>
|
||||
<height>139</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Add points</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0,1">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="singlePoint">
|
||||
<property name="text">
|
||||
<string>Single Point at</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">groupType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="SIUnitEdit" name="frequency"/>
|
||||
</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>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,1">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="array">
|
||||
<property name="text">
|
||||
<string>Array of </string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">groupType</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="numPoints">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>64</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>points from</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="SIUnitEdit" name="startFreq"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>to</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="SIUnitEdit" name="stopFreq"/>
|
||||
</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>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="deleteExisting">
|
||||
<property name="text">
|
||||
<string>Delete already existing points</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<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/>
|
||||
<buttongroups>
|
||||
<buttongroup name="groupType"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
+696
@@ -0,0 +1,696 @@
|
||||
#include "amplitudecaldialog.h"
|
||||
|
||||
#include "ui_amplitudecaldialog.h"
|
||||
#include "mode.h"
|
||||
#include "unit.h"
|
||||
#include "ui_addamplitudepointsdialog.h"
|
||||
#include "ui_automaticamplitudedialog.h"
|
||||
#include "json.hpp"
|
||||
#include "Util/util.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <CustomWidgets/informationbox.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace nlohmann;
|
||||
|
||||
AmplitudeCalDialog::AmplitudeCalDialog(LibreVNADriver *dev, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::AmplitudeCalDialog),
|
||||
dev(dev),
|
||||
model(this),
|
||||
edited(false),
|
||||
mode(CalibrationMode::BothPorts)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
emit dev->acquireControl();
|
||||
ui->setupUi(this);
|
||||
ui->view->setModel(&model);
|
||||
ui->view->setColumnWidth(AmplitudeModel::ColIndexFreq, 100);
|
||||
ui->view->setColumnWidth(AmplitudeModel::ColIndexCorrectionFactors, 150);
|
||||
ui->view->setColumnWidth(AmplitudeModel::ColIndexPort1, 150);
|
||||
ui->view->setColumnWidth(AmplitudeModel::ColIndexPort2, 150);
|
||||
connect(ui->load, &QPushButton::clicked, [=](){
|
||||
if(ConfirmActionIfEdited()) {
|
||||
LoadFromDevice();
|
||||
}
|
||||
});
|
||||
connect(ui->save, &QPushButton::clicked, this, &AmplitudeCalDialog::SaveToDevice);
|
||||
|
||||
connect(ui->saveFile, &QPushButton::clicked, [=](){
|
||||
auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal";
|
||||
auto fileFilter = QString("Amplitude calibration files (*")+fileEnding+")";
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.vnacaldata, fileFilter, nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.vnacaldata = QFileInfo(filename).path();
|
||||
if(!filename.endsWith(fileEnding)) {
|
||||
filename.append(fileEnding);
|
||||
}
|
||||
json j;
|
||||
for(auto p : points) {
|
||||
json point;
|
||||
point["Frequency"] = p.frequency;
|
||||
point["Port1"] = p.correctionPort1;
|
||||
point["Port2"] = p.correctionPort2;
|
||||
j.push_back(point);
|
||||
}
|
||||
ofstream file;
|
||||
file.open(filename.toStdString());
|
||||
file << setw(4) << j << endl;
|
||||
file.close();
|
||||
edited = false;
|
||||
});
|
||||
|
||||
connect(ui->loadFile, &QPushButton::clicked, [=](){
|
||||
auto fileEnding = pointType() == Protocol::PacketType::SourceCalPoint ? ".srccal" : ".recvcal";
|
||||
auto fileFilter = QString("Amplitude calibration files (*")+fileEnding+")";
|
||||
auto filename = QFileDialog::getOpenFileName(nullptr, "Save calibration data", Preferences::getInstance().UISettings.Paths.vnacaldata, fileFilter, nullptr, Preferences::QFileDialogOptions());
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
Preferences::getInstance().UISettings.Paths.vnacaldata = QFileInfo(filename).path();
|
||||
ifstream file;
|
||||
file.open(filename.toStdString());
|
||||
if(!file.is_open()) {
|
||||
throw runtime_error("Unable to open file");
|
||||
}
|
||||
RemoveAllPoints();
|
||||
json j;
|
||||
file >> j;
|
||||
for(auto point : j) {
|
||||
if(!point.contains("Frequency") || !point.contains("Port1") || !point.contains("Port2")) {
|
||||
InformationBox::ShowError("Error loading file", "Failed to parse calibration point");
|
||||
return;
|
||||
}
|
||||
CorrectionPoint p;
|
||||
p.frequency = point["Frequency"];
|
||||
p.correctionPort1 = point["Port1"];
|
||||
p.correctionPort2 = point["Port2"];
|
||||
p.port1set = false;
|
||||
p.port2set = false;
|
||||
UpdateAmplitude(p);
|
||||
if(!AddPoint(p)) {
|
||||
InformationBox::ShowMessage("Limit reached", "Unable to add all points from file, would not fit into the device");
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->add, &QPushButton::clicked, this, &AmplitudeCalDialog::AddPointDialog);
|
||||
connect(ui->remove, &QPushButton::clicked, [=](){
|
||||
unsigned int row = ui->view->currentIndex().row();
|
||||
if(row < points.size()) {
|
||||
RemovePoint(row);
|
||||
}
|
||||
});
|
||||
auto selectionModel = ui->view->selectionModel();
|
||||
connect(selectionModel, &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex&) {
|
||||
if(current.isValid() && (current.column() == model.ColIndexPort1 || current.column() == model.ColIndexPort2)) {
|
||||
SelectedPoint(points[current.row()].frequency, current.column() == model.ColIndexPort2);
|
||||
} else {
|
||||
// Invalid selection
|
||||
SelectedPoint(0, false);
|
||||
}
|
||||
});
|
||||
connect(ui->modeBoth, &QPushButton::pressed, [=](){
|
||||
mode = CalibrationMode::BothPorts;
|
||||
model.dataChanged(model.index(0, model.ColIndexPort1), model.index(points.size(), model.ColIndexPort2));
|
||||
});
|
||||
connect(ui->modePort1, &QPushButton::pressed, [=](){
|
||||
mode = CalibrationMode::OnlyPort1;
|
||||
model.dataChanged(model.index(0, model.ColIndexPort1), model.index(points.size(), model.ColIndexPort2));
|
||||
});
|
||||
connect(ui->modePort2, &QPushButton::pressed, [=](){
|
||||
mode = CalibrationMode::OnlyPort2;
|
||||
model.dataChanged(model.index(0, model.ColIndexPort1), model.index(points.size(), model.ColIndexPort2));
|
||||
});
|
||||
connect(ui->automatic, &QPushButton::clicked, this, &AmplitudeCalDialog::AutomaticMeasurementDialog);
|
||||
|
||||
connect(this, &AmplitudeCalDialog::SpectrumResultReceived, this, &AmplitudeCalDialog::ReceivedMeasurement);
|
||||
|
||||
connect(dev, &LibreVNADriver::receivedPacket, this, [=](const Protocol::PacketInfo &p){
|
||||
if(p.type == Protocol::PacketType::SpectrumAnalyzerResult) {
|
||||
emit SpectrumResultReceived(p.spectrumResult);
|
||||
} else if(p.type == Protocol::PacketType::SourceCalPoint || p.type == Protocol::PacketType::ReceiverCalPoint) {
|
||||
emit AmplitudeCorrectionPointReceived(p.amplitudePoint);
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
AmplitudeCalDialog::~AmplitudeCalDialog()
|
||||
{
|
||||
emit dev->releaseControl();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::reject()
|
||||
{
|
||||
if(ConfirmActionIfEdited()) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AmplitudeCalDialog::CorrectionPoint> AmplitudeCalDialog::getPoints() const
|
||||
{
|
||||
return points;
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::setAmplitude(double amplitude, unsigned int point, bool port2)
|
||||
{
|
||||
if(point >= points.size()) {
|
||||
return;
|
||||
}
|
||||
if(port2) {
|
||||
points[point].amplitudePort2 = amplitude;
|
||||
points[point].port2set = true;
|
||||
} else {
|
||||
points[point].amplitudePort1 = amplitude;
|
||||
points[point].port1set = true;
|
||||
}
|
||||
edited = true;
|
||||
AmplitudeChanged(points[point], port2);
|
||||
if(mode == CalibrationMode::OnlyPort1 && !port2) {
|
||||
// apply result from port 1 to port 2 as well
|
||||
points[point].correctionPort2 = points[point].correctionPort1;
|
||||
points[point].port2set = points[point].port1set;
|
||||
points[point].amplitudePort2 = points[point].amplitudePort1;
|
||||
} else if(mode == CalibrationMode::OnlyPort2 && port2) {
|
||||
// apply result from port 2 to port 1 as well
|
||||
points[point].correctionPort1 = points[point].correctionPort2;
|
||||
points[point].port1set = points[point].port2set;
|
||||
points[point].amplitudePort1 = points[point].amplitudePort2;
|
||||
}
|
||||
model.dataChanged(model.index(point, model.ColIndexCorrectionFactors), model.index(point, model.ColIndexPort2));
|
||||
UpdateSaveButton();
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::ReceivedPoint(Protocol::AmplitudeCorrectionPoint p)
|
||||
{
|
||||
qDebug() << "Received amplitude calibration point" << p.pointNum << "/" << p.totalPoints;
|
||||
CorrectionPoint c;
|
||||
c.frequency = p.freq * 10.0;
|
||||
c.correctionPort1 = p.port1;
|
||||
c.correctionPort2 = p.port2;
|
||||
c.port1set = false;
|
||||
c.port2set = false;
|
||||
model.beginInsertRows(QModelIndex(), points.size(), points.size());
|
||||
UpdateAmplitude(c);
|
||||
points.push_back(c);
|
||||
model.endInsertRows();
|
||||
emit pointsUpdated();
|
||||
if(p.pointNum == p.totalPoints - 1) {
|
||||
// this was the last point
|
||||
disconnect(this, &AmplitudeCalDialog::AmplitudeCorrectionPointReceived, this, nullptr);
|
||||
ui->load->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::LoadFromDevice()
|
||||
{
|
||||
ui->load->setEnabled(false);
|
||||
dev->setIdle();
|
||||
RemoveAllPoints();
|
||||
qDebug() << "Asking for amplitude calibration";
|
||||
connect(this, &AmplitudeCalDialog::AmplitudeCorrectionPointReceived, this, &AmplitudeCalDialog::ReceivedPoint, Qt::QueuedConnection);
|
||||
dev->sendWithoutPayload(requestCommand());
|
||||
edited = false;
|
||||
UpdateSaveButton();
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::SaveToDevice()
|
||||
{
|
||||
dev->setIdle();
|
||||
for(unsigned int i=0;i<points.size();i++) {
|
||||
auto p = points[i];
|
||||
Protocol::PacketInfo info = {};
|
||||
info.type = pointType();
|
||||
info.amplitudePoint.freq = p.frequency / 10.0;
|
||||
info.amplitudePoint.port1 = p.correctionPort1;
|
||||
info.amplitudePoint.port2 = p.correctionPort2;
|
||||
info.amplitudePoint.totalPoints = points.size();
|
||||
info.amplitudePoint.pointNum = i;
|
||||
dev->SendPacket(info);
|
||||
// qDebug() << "Sent amplitude calibration point" << i << "/" << points.size();
|
||||
}
|
||||
edited = false;
|
||||
UpdateSaveButton();
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::RemovePoint(unsigned int i)
|
||||
{
|
||||
model.beginRemoveRows(QModelIndex(), i, i);
|
||||
points.erase(points.begin() + i);
|
||||
model.endRemoveRows();
|
||||
edited = true;
|
||||
UpdateSaveButton();
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::RemoveAllPoints()
|
||||
{
|
||||
model.beginResetModel();
|
||||
points.clear();
|
||||
model.endResetModel();
|
||||
edited = true;
|
||||
UpdateSaveButton();
|
||||
}
|
||||
|
||||
bool AmplitudeCalDialog::AddPoint(AmplitudeCalDialog::CorrectionPoint &p)
|
||||
{
|
||||
if (points.size() >= dev->getMaxAmplitudePoints()) {
|
||||
// already at limit
|
||||
return false;
|
||||
}
|
||||
// find position at which this frequency gets inserted
|
||||
auto index = upper_bound(points.begin(), points.end(), p.frequency, [](double value, const CorrectionPoint& p){
|
||||
return value < p.frequency;
|
||||
});
|
||||
model.beginInsertRows(QModelIndex(), index - points.begin(), index - points.begin());
|
||||
emit newPointCreated(p);
|
||||
points.insert(index, p);
|
||||
model.endInsertRows();
|
||||
UpdateSaveButton();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AmplitudeCalDialog::AddPoint(double frequency)
|
||||
{
|
||||
CorrectionPoint newPoint;
|
||||
newPoint.frequency = frequency;
|
||||
newPoint.correctionPort1 = 0;
|
||||
newPoint.correctionPort2 = 0;
|
||||
newPoint.port1set = false;
|
||||
newPoint.port2set = false;
|
||||
auto success = AddPoint(newPoint);
|
||||
if (success) {
|
||||
edited = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::AddPointDialog()
|
||||
{
|
||||
auto d = new QDialog();
|
||||
auto ui = new Ui::AddAmplitudePointsDialog();
|
||||
ui->setupUi(d);
|
||||
connect(d, &QDialog::finished, [=](){
|
||||
delete ui;
|
||||
});
|
||||
ui->frequency->setUnit("Hz");
|
||||
ui->frequency->setPrefixes(" kMG");
|
||||
ui->startFreq->setUnit("Hz");
|
||||
ui->startFreq->setPrefixes(" kMG");
|
||||
ui->stopFreq->setUnit("Hz");
|
||||
ui->stopFreq->setPrefixes(" kMG");
|
||||
ui->frequency->setValue(1000000000.0);
|
||||
ui->startFreq->setValue(dev->getInfo().Limits.SA.minFreq);
|
||||
ui->stopFreq->setValue(dev->getInfo().Limits.SA.maxFreq);
|
||||
connect(ui->singlePoint, &QRadioButton::toggled, [=](bool single) {
|
||||
ui->stopFreq->setEnabled(!single);
|
||||
ui->startFreq->setEnabled(!single);
|
||||
ui->numPoints->setEnabled(!single);
|
||||
ui->frequency->setEnabled(single);
|
||||
});
|
||||
ui->singlePoint->setChecked(true);
|
||||
ui->frequency->setFocus();
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
|
||||
// okay clicked, apply selected action
|
||||
if(ui->deleteExisting->isChecked()) {
|
||||
RemoveAllPoints();
|
||||
}
|
||||
if(ui->singlePoint->isChecked()) {
|
||||
if(!AddPoint(ui->frequency->value())) {
|
||||
InformationBox::ShowMessage("Limit reached", "Can't add any more points, would not fit into the device");
|
||||
}
|
||||
} else {
|
||||
double freq_start = ui->startFreq->value();
|
||||
double freq_stop = ui->stopFreq->value();
|
||||
unsigned int points = ui->numPoints->value();
|
||||
double freq_step = (freq_stop - freq_start) / (points - 1);
|
||||
for(unsigned int i=0;i<points;i++) {
|
||||
if(!AddPoint(freq_start + i * freq_step)) {
|
||||
InformationBox::ShowMessage("Limit reached", "Can't add any more points, would not fit into the device");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(d, &QDialog::rejected, ui->buttonBox, &QDialogButtonBox::rejected);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, [=](){
|
||||
// aborted, nothing to do
|
||||
delete d;
|
||||
});
|
||||
|
||||
dev->sendWithoutPayload(requestCommand());
|
||||
|
||||
if(AppWindow::showGUI()) {
|
||||
d->show();
|
||||
}
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::AutomaticMeasurementDialog()
|
||||
{
|
||||
automatic = {};
|
||||
automatic.isSourceCal = pointType() == Protocol::PacketType::SourceCalPoint;
|
||||
const QString ownCal = automatic.isSourceCal ? "Source" : "Receiver";
|
||||
const QString otherCal = automatic.isSourceCal ? "Receiver" : "Source";
|
||||
|
||||
// compile info text
|
||||
QString info = "It is possible to determine the " + ownCal + " Calibration if a valid " + otherCal
|
||||
+ " Calibration is already present. To do so, connect a short cable with as little loss as possible"
|
||||
+ " between the ports. For each point in the " + otherCal + " Calibration, the device will take"
|
||||
+ " a measurement and determine the correction factors for the " + ownCal + " Calibration.";
|
||||
|
||||
automatic.dialog = new QDialog(this);
|
||||
auto ui = new Ui::AutomaticAmplitudeDialog();
|
||||
ui->setupUi(automatic.dialog);
|
||||
connect(automatic.dialog, &QDialog::finished, [=](){
|
||||
delete ui;
|
||||
});
|
||||
automatic.progress = ui->progress;
|
||||
ui->explanation->setText(info);
|
||||
ui->status->setText("Gathering information about "+otherCal+" Calibration...");
|
||||
|
||||
automatic.points.clear();
|
||||
|
||||
connect(automatic.dialog, &QDialog::rejected, ui->abort, &QPushButton::click);
|
||||
connect(ui->abort, &QPushButton::clicked, [=](){
|
||||
// aborted, clean up
|
||||
disconnect(this, &AmplitudeCalDialog::SpectrumResultReceived, this, &AmplitudeCalDialog::ReceivedAutomaticMeasurementResult);
|
||||
dev->setIdle();
|
||||
delete ui;
|
||||
delete automatic.dialog;
|
||||
});
|
||||
|
||||
dev->setIdle();
|
||||
connect(this, &AmplitudeCalDialog::AmplitudeCorrectionPointReceived, this, [this, ui, otherCal](Protocol::AmplitudeCorrectionPoint p) {
|
||||
CorrectionPoint c;
|
||||
c.frequency = p.freq * 10.0;
|
||||
c.correctionPort1 = p.port1;
|
||||
c.correctionPort2 = p.port2;
|
||||
c.port1set = true;
|
||||
c.port2set = true;
|
||||
automatic.points.push_back(c);
|
||||
ui->progress->setValue(100 * (p.pointNum+1) / p.totalPoints);
|
||||
if(p.pointNum == p.totalPoints - 1) {
|
||||
// this was the last point, indicate ready for measurement
|
||||
ui->progress->setValue(0);
|
||||
ui->status->setText(otherCal + " Calibration contains " +QString::number(p.totalPoints)+" points, ready to start measurement");
|
||||
ui->start->setEnabled(true);
|
||||
disconnect(this, &AmplitudeCalDialog::AmplitudeCorrectionPointReceived, this, nullptr);
|
||||
qDebug() << "Received" << p.totalPoints << "points for automatic calibration";
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
// request points of otherCal
|
||||
// switch between source/receiver calibration
|
||||
auto request = automatic.isSourceCal ? Protocol::PacketType::RequestReceiverCal : Protocol::PacketType::RequestSourceCal;
|
||||
dev->sendWithoutPayload(request);
|
||||
|
||||
connect(ui->start, &QPushButton::clicked, [=](){
|
||||
// remove any exising points in own calibration and copy points from other calibration
|
||||
RemoveAllPoints();
|
||||
for(auto p : automatic.points) {
|
||||
AddPoint(p.frequency);
|
||||
}
|
||||
// intialize measurement state machine
|
||||
connect(this, &AmplitudeCalDialog::SpectrumResultReceived, this, &AmplitudeCalDialog::ReceivedAutomaticMeasurementResult, Qt::QueuedConnection);
|
||||
automatic.measuringPort2 = false;
|
||||
automatic.measuringCount = 0;
|
||||
ui->status->setText("Taking measurements...");
|
||||
SetupNextAutomaticPoint(automatic.isSourceCal);
|
||||
});
|
||||
|
||||
if(AppWindow::showGUI()) {
|
||||
automatic.dialog->show();
|
||||
}
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::ReceivedMeasurement(Protocol::SpectrumAnalyzerResult res)
|
||||
{
|
||||
MeasurementResult m = {.port1 = Util::SparamTodB(res.port1), .port2 = Util::SparamTodB(res.port2)};
|
||||
sweepMeasurements.push_back(m);
|
||||
if(res.pointNum == automaticSweepPoints - 1) {
|
||||
// sweep finished, find maximum for each port
|
||||
double maxPort1 = std::numeric_limits<double>::lowest();
|
||||
double maxPort2 = std::numeric_limits<double>::lowest();
|
||||
for(auto s : sweepMeasurements) {
|
||||
if(s.port1 > maxPort1) {
|
||||
maxPort1 = s.port1;
|
||||
}
|
||||
if(s.port2 > maxPort2) {
|
||||
maxPort2 = s.port2;
|
||||
}
|
||||
}
|
||||
MeasurementResult max = {.port1 = maxPort1, .port2 = maxPort2};
|
||||
if(measured.size() >= averages) {
|
||||
measured.pop_front();
|
||||
}
|
||||
measured.push_back(max);
|
||||
sweepMeasurements.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool AmplitudeCalDialog::ConfirmActionIfEdited()
|
||||
{
|
||||
if(edited) {
|
||||
return InformationBox::AskQuestion("Confirm action", "Some points have been edited but not saved in the device yet. If you continue, all changes will be lost (unless they are already saved to a file). Do you want to continue?", true);
|
||||
} else {
|
||||
// not edited yet, nothing to confirm
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::UpdateSaveButton()
|
||||
{
|
||||
bool enable = true;
|
||||
if(points.size() == 0) {
|
||||
// needs at least one point
|
||||
enable = false;
|
||||
}
|
||||
for(auto p : points) {
|
||||
if(!p.port1set || !p.port2set) {
|
||||
// some points are not set yet
|
||||
enable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!edited) {
|
||||
// already saved in device, disable button
|
||||
enable = false;
|
||||
}
|
||||
ui->save->setEnabled(enable);
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::SetupNextAutomaticPoint(bool isSourceCal)
|
||||
{
|
||||
qDebug() << "Starting automatic calibration measurement for point" << automatic.measuringCount <<", measuring port2:" << automatic.measuringPort2;
|
||||
auto point = automatic.points[automatic.measuringCount];
|
||||
Protocol::PacketInfo p = {};
|
||||
p.type = Protocol::PacketType::SpectrumAnalyzerSettings;
|
||||
p.spectrumSettings.RBW = 10000;
|
||||
p.spectrumSettings.UseDFT = 0;
|
||||
// setup points centered around the measurement frequency:
|
||||
// use a span proportional to the center frequency (this makes sure to catch the peak of the excitation signal which
|
||||
// might be slightly off due to PLL divider limtis). Also make sure to use at least a span of 2 Hz (zero span not supported yet)
|
||||
p.spectrumSettings.f_stop = point.frequency * 1.00001 + 1.0;
|
||||
p.spectrumSettings.f_start = point.frequency / 1.00001 - 1.0;
|
||||
p.spectrumSettings.pointNum = automaticSweepPoints;
|
||||
p.spectrumSettings.Detector = 0;
|
||||
p.spectrumSettings.SignalID = 1;
|
||||
p.spectrumSettings.WindowType = 3;
|
||||
p.spectrumSettings.trackingGenerator = 1;
|
||||
p.spectrumSettings.trackingPower = excitationAmplitude * 100.0;
|
||||
p.spectrumSettings.trackingGeneratorOffset = 0;
|
||||
// For a source cal, the tracking generator has to be enabled at the measurement port (calibrating the output power)
|
||||
// For a receiver cal, the tracking generator has to be enabled at the other port (calibrating received power)
|
||||
if(isSourceCal) {
|
||||
if(automatic.measuringPort2) {
|
||||
p.spectrumSettings.trackingGeneratorPort = 1;
|
||||
} else {
|
||||
p.spectrumSettings.trackingGeneratorPort = 0;
|
||||
}
|
||||
} else {
|
||||
if(automatic.measuringPort2) {
|
||||
p.spectrumSettings.trackingGeneratorPort = 0;
|
||||
} else {
|
||||
p.spectrumSettings.trackingGeneratorPort = 1;
|
||||
}
|
||||
}
|
||||
if(isSourceCal) {
|
||||
// Calibrating the source which means the receiver is already calibrated -> use receiver corrections but no source corrections
|
||||
p.spectrumSettings.applyReceiverCorrection = 1;
|
||||
p.spectrumSettings.applySourceCorrection = 0;
|
||||
} else {
|
||||
// the other way around
|
||||
p.spectrumSettings.applyReceiverCorrection = 0;
|
||||
p.spectrumSettings.applySourceCorrection = 1;
|
||||
}
|
||||
automatic.settlingCount = averages + automaticSettling;
|
||||
dev->SendPacket(p);
|
||||
sweepMeasurements.clear();
|
||||
sweepMeasurements.reserve(automaticSweepPoints);
|
||||
}
|
||||
|
||||
void AmplitudeCalDialog::ReceivedAutomaticMeasurementResult(Protocol::SpectrumAnalyzerResult res)
|
||||
{
|
||||
if(res.pointNum != automaticSweepPoints - 1) {
|
||||
// ignore everything except end of sweep
|
||||
return;
|
||||
}
|
||||
if(automatic.settlingCount > 0) {
|
||||
automatic.settlingCount--;
|
||||
return;
|
||||
}
|
||||
if(automatic.isSourceCal) {
|
||||
// grab correct measurement and convert to dbm
|
||||
auto m = averageMeasurement();
|
||||
double measurement = automatic.measuringPort2 ? m.port1 : m.port2;
|
||||
// receiver cal already done, the measurement result is accurate and can be used to determine actual output power
|
||||
setAmplitude(measurement, automatic.measuringCount, automatic.measuringPort2);
|
||||
} else {
|
||||
// source cal already done, the output power is accurate while the measurement might be off.
|
||||
// The measurement result is already extracted by the ReceiverCalDialog
|
||||
setAmplitude(excitationAmplitude, automatic.measuringCount, automatic.measuringPort2);
|
||||
}
|
||||
// update progress bar
|
||||
automatic.progress->setValue(100 * (automatic.measuringCount * 2 + automatic.measuringPort2 + 1) / (points.size() * 2));
|
||||
// advance state machine
|
||||
// switch ports
|
||||
automatic.measuringPort2 = !automatic.measuringPort2;
|
||||
if(!automatic.measuringPort2) {
|
||||
// now measuring port1, this means we have to advance to the next point
|
||||
automatic.measuringCount++;
|
||||
if(automatic.measuringCount >= points.size()) {
|
||||
// all done, close dialog
|
||||
// Closing the dialog will disconnect the connection to this function and return the device into the idle state
|
||||
automatic.dialog->reject();
|
||||
automatic.dialog = nullptr;
|
||||
qDebug() << "Automatic source/receiver calibration complete";
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Start next measurement
|
||||
SetupNextAutomaticPoint(automatic.isSourceCal);
|
||||
}
|
||||
|
||||
AmplitudeCalDialog::MeasurementResult AmplitudeCalDialog::averageMeasurement()
|
||||
{
|
||||
MeasurementResult ret = {};
|
||||
for(auto m : measured) {
|
||||
ret.port1 += m.port1;
|
||||
ret.port2 += m.port2;
|
||||
}
|
||||
ret.port1 /= measured.size();
|
||||
ret.port2 /= measured.size();
|
||||
return ret;
|
||||
}
|
||||
|
||||
AmplitudeCalDialog::CalibrationMode AmplitudeCalDialog::getMode() const
|
||||
{
|
||||
return mode;
|
||||
}
|
||||
|
||||
AmplitudeModel::AmplitudeModel(AmplitudeCalDialog *c) :
|
||||
QAbstractTableModel(),
|
||||
c(c)
|
||||
{
|
||||
}
|
||||
|
||||
int AmplitudeModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return c->getPoints().size();
|
||||
}
|
||||
|
||||
int AmplitudeModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return ColIndexLast;
|
||||
}
|
||||
|
||||
QVariant AmplitudeModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if(role == Qt::DisplayRole && static_cast<unsigned int>(index.row()) < c->getPoints().size()) {
|
||||
auto p = c->getPoints()[index.row()];
|
||||
switch(index.column()) {
|
||||
case ColIndexFreq:
|
||||
return Unit::ToString(p.frequency, "Hz", " kMG", 6);
|
||||
break;
|
||||
case ColIndexCorrectionFactors:
|
||||
return QString::number(p.correctionPort1) + ", " + QString::number(p.correctionPort2);
|
||||
break;
|
||||
case ColIndexPort1:
|
||||
if (p.port1set) {
|
||||
return Unit::ToString(p.amplitudePort1, "dbm", " ", 4);
|
||||
} else {
|
||||
return "No data";
|
||||
}
|
||||
break;
|
||||
case ColIndexPort2:
|
||||
if (p.port2set) {
|
||||
return Unit::ToString(p.amplitudePort2, "dbm", " ", 4);
|
||||
} else {
|
||||
return "No data";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool AmplitudeModel::setData(const QModelIndex &index, const QVariant &value, int)
|
||||
{
|
||||
if((unsigned int) index.row() >= c->getPoints().size()) {
|
||||
return false;
|
||||
}
|
||||
switch(index.column()) {
|
||||
case ColIndexPort1:
|
||||
c->setAmplitude(value.toDouble(), index.row(), false);
|
||||
return true;
|
||||
case ColIndexPort2:
|
||||
c->setAmplitude(value.toDouble(), index.row(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant AmplitudeModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
||||
switch(section) {
|
||||
case ColIndexFreq: return "Frequency"; break;
|
||||
case ColIndexCorrectionFactors: return "Correction Factors"; break;
|
||||
case ColIndexPort1: return "Amplitude Port 1"; break;
|
||||
case ColIndexPort2: return "Amplitude Port 2"; break;
|
||||
default: return QVariant(); break;
|
||||
}
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags AmplitudeModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
int flags = Qt::NoItemFlags;
|
||||
switch(index.column()) {
|
||||
case ColIndexPort1:
|
||||
if(c->getMode() != AmplitudeCalDialog::CalibrationMode::OnlyPort2) {
|
||||
flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
||||
}
|
||||
break;
|
||||
case ColIndexPort2:
|
||||
if(c->getMode() != AmplitudeCalDialog::CalibrationMode::OnlyPort1) {
|
||||
flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return (Qt::ItemFlags) flags;
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
#ifndef AMPLITUDECALDIALOG_H
|
||||
#define AMPLITUDECALDIALOG_H
|
||||
|
||||
#include "librevnadriver.h"
|
||||
|
||||
#include <deque>
|
||||
#include <QDialog>
|
||||
#include <QAbstractTableModel>
|
||||
#include <QProgressBar>
|
||||
|
||||
namespace Ui {
|
||||
class AmplitudeCalDialog;
|
||||
}
|
||||
|
||||
class AmplitudeCalDialog;
|
||||
|
||||
class AmplitudeModel : public QAbstractTableModel
|
||||
{
|
||||
friend class AmplitudeCalDialog;
|
||||
Q_OBJECT
|
||||
public:
|
||||
AmplitudeModel(AmplitudeCalDialog *c);
|
||||
|
||||
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;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
private:
|
||||
enum {
|
||||
ColIndexFreq,
|
||||
ColIndexCorrectionFactors,
|
||||
ColIndexPort1,
|
||||
ColIndexPort2,
|
||||
ColIndexLast
|
||||
};
|
||||
AmplitudeCalDialog *c;
|
||||
};
|
||||
|
||||
class AmplitudeCalDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AmplitudeCalDialog(LibreVNADriver *dev, QWidget *parent = nullptr);
|
||||
~AmplitudeCalDialog();
|
||||
void reject() override;
|
||||
|
||||
class CorrectionPoint {
|
||||
public:
|
||||
double frequency;
|
||||
int16_t correctionPort1;
|
||||
int16_t correctionPort2;
|
||||
double amplitudePort1;
|
||||
double amplitudePort2;
|
||||
bool port1set;
|
||||
bool port2set;
|
||||
};
|
||||
std::vector<CorrectionPoint> getPoints() const;
|
||||
void setAmplitude(double amplitude, unsigned int point, bool port2);
|
||||
|
||||
enum class CalibrationMode {
|
||||
BothPorts,
|
||||
OnlyPort1,
|
||||
OnlyPort2,
|
||||
};
|
||||
|
||||
CalibrationMode getMode() const;
|
||||
|
||||
protected slots:
|
||||
void ReceivedPoint(Protocol::AmplitudeCorrectionPoint p);
|
||||
void LoadFromDevice();
|
||||
void SaveToDevice();
|
||||
void RemovePoint(unsigned int i);
|
||||
void RemoveAllPoints();
|
||||
bool AddPoint(CorrectionPoint& p);
|
||||
bool AddPoint(double frequency);
|
||||
void AddPointDialog();
|
||||
void AutomaticMeasurementDialog();
|
||||
signals:
|
||||
void pointsUpdated();
|
||||
void newPointCreated(CorrectionPoint& p);
|
||||
void AmplitudeCorrectionPointReceived(Protocol::AmplitudeCorrectionPoint p);
|
||||
void SpectrumResultReceived(Protocol::SpectrumAnalyzerResult res);
|
||||
protected:
|
||||
static constexpr double excitationAmplitude = -20.0;
|
||||
static constexpr int automaticSweepPoints = 31;
|
||||
static constexpr int averages = 3;
|
||||
static constexpr int automaticSettling = 1;
|
||||
|
||||
void ReceivedMeasurement(Protocol::SpectrumAnalyzerResult res);
|
||||
bool ConfirmActionIfEdited();
|
||||
void UpdateSaveButton();
|
||||
virtual Protocol::PacketType requestCommand() = 0;
|
||||
virtual Protocol::PacketType pointType() = 0;
|
||||
// will get called whenever a new cell is selected (frequency=0 means invalid selection)
|
||||
virtual void SelectedPoint(double frequency, bool port2) = 0;
|
||||
// will get called whenever the amplitude is changed. Derived class is responsible for updating correction factor
|
||||
virtual void AmplitudeChanged(CorrectionPoint& point, bool port2) = 0;
|
||||
// called whenver the correction factor have been retrieved from the device and the amplitudes need to be updated
|
||||
virtual void UpdateAmplitude(CorrectionPoint& point) = 0;
|
||||
std::vector<CorrectionPoint> points;
|
||||
Ui::AmplitudeCalDialog *ui;
|
||||
LibreVNADriver *dev;
|
||||
AmplitudeModel model;
|
||||
bool edited;
|
||||
CalibrationMode mode;
|
||||
|
||||
void SetupNextAutomaticPoint(bool isSourceCal);
|
||||
void ReceivedAutomaticMeasurementResult(Protocol::SpectrumAnalyzerResult res);
|
||||
struct {
|
||||
QDialog *dialog;
|
||||
std::vector<CorrectionPoint> points;
|
||||
bool measuringPort2; // true if port2 is being calibrated
|
||||
unsigned int measuringCount; // number of calibration point
|
||||
unsigned int settlingCount; // number of measurements still to ignore before taking measurement
|
||||
bool isSourceCal;
|
||||
QProgressBar *progress;
|
||||
} automatic;
|
||||
|
||||
// raw (uncorrected) measurements from device.
|
||||
// Used in reveicer calibration as well as in automatic calibrations
|
||||
struct MeasurementResult {
|
||||
double port1, port2;
|
||||
};
|
||||
std::vector<MeasurementResult> sweepMeasurements;
|
||||
std::deque<MeasurementResult> measured;
|
||||
MeasurementResult averageMeasurement();
|
||||
};
|
||||
|
||||
#endif // SOURCECALDIALOG_H
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AmplitudeCalDialog</class>
|
||||
<widget class="QDialog" name="AmplitudeCalDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>766</width>
|
||||
<height>599</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QTableView" name="view">
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectItems</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="add">
|
||||
<property name="text">
|
||||
<string>Add Point(s)</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add" resource="../icons.qrc">
|
||||
<normaloff>:/icons/add.png</normaloff>:/icons/add.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="remove">
|
||||
<property name="text">
|
||||
<string>Delete Point</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-remove" resource="../icons.qrc">
|
||||
<normaloff>:/icons/remove.png</normaloff>:/icons/remove.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="load">
|
||||
<property name="text">
|
||||
<string>Load from Device</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-open" resource="../icons.qrc">
|
||||
<normaloff>:/icons/open.png</normaloff>:/icons/open.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="save">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save to Device</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save" resource="../icons.qrc">
|
||||
<normaloff>:/icons/save.png</normaloff>:/icons/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="loadFile">
|
||||
<property name="text">
|
||||
<string>Load from File</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-open" resource="../icons.qrc">
|
||||
<normaloff>:/icons/open.png</normaloff>:/icons/open.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="saveFile">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save to File</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save" resource="../icons.qrc">
|
||||
<normaloff>:/icons/save.png</normaloff>:/icons/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Calibration Mode</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="modeBoth">
|
||||
<property name="text">
|
||||
<string>Both Ports</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">groupMode</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Best accuracy</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="modePort1">
|
||||
<property name="text">
|
||||
<string>Only Port 1</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">groupMode</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use same correction for port 2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="modePort2">
|
||||
<property name="text">
|
||||
<string>Only Port 2</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">groupMode</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use same correction for port 1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="automatic">
|
||||
<property name="text">
|
||||
<string>Automatic measurement</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>
|
||||
<include location="../icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="groupMode"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AutomaticAmplitudeDialog</class>
|
||||
<widget class="QDialog" name="AutomaticAmplitudeDialog">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>575</width>
|
||||
<height>215</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Automatic Measurement</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="explanation">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<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">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Status:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="status">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="abort">
|
||||
<property name="text">
|
||||
<string>Abort</string>
|
||||
</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="start">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start Measurement</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="media-playback-start" resource="../icons.qrc">
|
||||
<normaloff>:/icons/play.png</normaloff>:/icons/play.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
#include "deviceconfigurationdialogv1.h"
|
||||
#include "ui_deviceconfigurationdialogv1.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include "CustomWidgets/informationbox.h"
|
||||
|
||||
DeviceConfigurationDialogV1::DeviceConfigurationDialogV1(LibreVNADriver &dev, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::DeviceConfigurationDialogV1),
|
||||
dev(dev)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
emit dev.acquireControl();
|
||||
|
||||
|
||||
auto updateADCRate = [=]() {
|
||||
// update ADC rate, see FPGA protocol for calculation
|
||||
ui->ADCRate->setValue(102400000.0 / ui->ADCpresc->value());
|
||||
};
|
||||
auto updateIF2 = [=]() {
|
||||
auto ADCrate = ui->ADCRate->value();
|
||||
ui->IF2->setValue(ADCrate * ui->ADCphaseInc->value() / 4096);
|
||||
};
|
||||
|
||||
connect(ui->ADCpresc, qOverload<int>(&QSpinBox::valueChanged), updateADCRate);
|
||||
connect(ui->ADCpresc, qOverload<int>(&QSpinBox::valueChanged), updateIF2);
|
||||
connect(ui->ADCphaseInc, qOverload<int>(&QSpinBox::valueChanged), updateIF2);
|
||||
|
||||
ui->IF1->setUnit("Hz");
|
||||
ui->IF1->setPrefixes(" kM");
|
||||
ui->IF1->setPrecision(5);
|
||||
ui->ADCRate->setUnit("Hz");
|
||||
ui->ADCRate->setPrefixes(" kM");
|
||||
ui->ADCRate->setPrecision(5);
|
||||
ui->IF2->setUnit("Hz");
|
||||
ui->IF2->setPrefixes(" kM");
|
||||
ui->IF2->setPrecision(5);
|
||||
ui->IF1->setValue(62000000);
|
||||
ui->ADCpresc->setValue(128);
|
||||
ui->ADCphaseInc->setValue(1280);
|
||||
|
||||
updateADCRate();
|
||||
updateIF2();
|
||||
|
||||
connect(&dev, &LibreVNADriver::receivedPacket, this, [=](const Protocol::PacketInfo &p) {
|
||||
if(p.type == Protocol::PacketType::DeviceConfiguration) {
|
||||
updateGUI(p.deviceConfig);
|
||||
}
|
||||
});
|
||||
|
||||
dev.sendWithoutPayload(Protocol::PacketType::RequestDeviceConfiguration);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, [=](){
|
||||
updateDevice();
|
||||
accept();
|
||||
});
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [=](){
|
||||
reject();
|
||||
});
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [=](){
|
||||
resetDevice();
|
||||
this->dev.sendWithoutPayload(Protocol::PacketType::RequestDeviceConfiguration);
|
||||
});
|
||||
}
|
||||
|
||||
DeviceConfigurationDialogV1::~DeviceConfigurationDialogV1()
|
||||
{
|
||||
dev.releaseControl();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void DeviceConfigurationDialogV1::updateGUI(const Protocol::DeviceConfig &c)
|
||||
{
|
||||
ui->IF1->setEnabled(false);
|
||||
ui->IF1->setValue(c.V1.IF1);
|
||||
ui->IF1->setEnabled(true);
|
||||
ui->ADCpresc->setValue(c.V1.ADCprescaler);
|
||||
ui->ADCphaseInc->setValue(c.V1.DFTphaseInc);
|
||||
ui->PLLSettlingDelay->setValue(c.V1.PLLSettlingDelay);
|
||||
}
|
||||
|
||||
void DeviceConfigurationDialogV1::updateDevice()
|
||||
{
|
||||
Protocol::PacketInfo p = {};
|
||||
p.type = Protocol::PacketType::DeviceConfiguration;
|
||||
p.deviceConfig.V1.IF1 = ui->IF1->value();
|
||||
p.deviceConfig.V1.ADCprescaler = ui->ADCpresc->value();
|
||||
p.deviceConfig.V1.DFTphaseInc = ui->ADCphaseInc->value();
|
||||
p.deviceConfig.V1.PLLSettlingDelay = ui->PLLSettlingDelay->value();
|
||||
dev.SendPacket(p);
|
||||
}
|
||||
|
||||
void DeviceConfigurationDialogV1::resetDevice()
|
||||
{
|
||||
dev.sendWithoutPayload(Protocol::PacketType::ResetDeviceConfiguration, [=](LibreVNADriver::TransmissionResult res){
|
||||
if(res != LibreVNADriver::TransmissionResult::Ack) {
|
||||
InformationBox::ShowError("Error", "Failed to reset device configuration");
|
||||
}
|
||||
});
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#ifndef DEVICECONFIGURATIONDIALOGV1_H
|
||||
#define DEVICECONFIGURATIONDIALOGV1_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "librevnadriver.h"
|
||||
|
||||
namespace Ui {
|
||||
class DeviceConfigurationDialogV1;
|
||||
}
|
||||
|
||||
class DeviceConfigurationDialogV1 : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeviceConfigurationDialogV1(LibreVNADriver &dev, QWidget *parent = nullptr);
|
||||
~DeviceConfigurationDialogV1();
|
||||
|
||||
private:
|
||||
void updateGUI(const Protocol::DeviceConfig &c);
|
||||
void updateDevice();
|
||||
void resetDevice();
|
||||
|
||||
Ui::DeviceConfigurationDialogV1 *ui;
|
||||
LibreVNADriver &dev;
|
||||
};
|
||||
|
||||
#endif // DEVICECONFIGURATIONDIALOGV1_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user