LibreVNA/Software/PC_Application/Calibration/amplitudecaldialog.cpp

527 lines
19 KiB
C++

#include "amplitudecaldialog.h"
#include "ui_amplitudecaldialog.h"
#include "mode.h"
#include "unit.h"
#include <QDebug>
#include "ui_addamplitudepointsdialog.h"
#include "ui_automaticamplitudedialog.h"
#include <QMessageBox>
using namespace std;
AmplitudeCalDialog::AmplitudeCalDialog(Device *dev, QWidget *parent) :
QDialog(parent),
ui(new Ui::AmplitudeCalDialog),
dev(dev),
model(this),
mode(CalibrationMode::BothPorts)
{
activeMode = Mode::getActiveMode();
activeMode->deactivate();
dev->SetIdle();
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->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 &current, 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);
}
AmplitudeCalDialog::~AmplitudeCalDialog()
{
delete ui;
activeMode->activate();
}
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;
} 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;
}
model.dataChanged(model.index(point, model.ColIndexCorrectionFactors), model.index(point, model.ColIndexPort2));
UpdateSaveButton();
}
void AmplitudeCalDialog::ReceivedPoint(Protocol::AmplitudeCorrectionPoint p)
{
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(dev, &Device::AmplitudeCorrectionPointReceived, this, nullptr);
ui->load->setEnabled(true);
}
}
void AmplitudeCalDialog::LoadFromDevice()
{
ui->load->setEnabled(false);
dev->SetIdle();
RemoveAllPoints();
qDebug() << "Asking for amplitude calibration";
connect(dev, &Device::AmplitudeCorrectionPointReceived, this, &AmplitudeCalDialog::ReceivedPoint);
dev->SendCommandWithoutPayload(requestCommand());
edited = false;
ui->save->setEnabled(false);
}
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);
}
edited = false;
ui->save->setEnabled(false);
}
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;
ui->save->setEnabled(false);
UpdateSaveButton();
}
void AmplitudeCalDialog::AddPoint(double frequency)
{
if(points.size() >= Device::Info().limits_maxAmplitudePoints) {
qWarning() << "Unable to add amplitude point, already maximum limit (" << Device::Info().limits_maxAmplitudePoints << ")";
return;
}
// find position at which this frequency gets inserted
auto index = upper_bound(points.begin(), points.end(), frequency, [](double value, const CorrectionPoint& p){
return value < p.frequency;
});
model.beginInsertRows(QModelIndex(), index - points.begin(), index - points.begin());
CorrectionPoint newPoint;
newPoint.frequency = frequency;
newPoint.correctionPort1 = 0;
newPoint.correctionPort2 = 0;
newPoint.port1set = false;
newPoint.port2set = false;
emit newPointCreated(newPoint);
points.insert(index, newPoint);
model.endInsertRows();
edited = true;
UpdateSaveButton();
}
void AmplitudeCalDialog::AddPointDialog()
{
auto d = new QDialog();
auto ui = new Ui::AddAmplitudePointsDialog();
ui->setupUi(d);
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(Device::Info().limits_minFreq);
ui->stopFreq->setValue(Device::Info().limits_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()) {
AddPoint(ui->frequency->value());
} 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++) {
AddPoint(freq_start + i * freq_step);
}
}
});
connect(d, &QDialog::rejected, ui->buttonBox, &QDialogButtonBox::rejected);
connect(ui->buttonBox, &QDialogButtonBox::rejected, [=](){
// aborted, nothing to do
delete d;
});
dev->SendCommandWithoutPayload(requestCommand());
d->show();
}
void AmplitudeCalDialog::AutomaticMeasurementDialog()
{
bool isSourceCal = pointType() == Protocol::PacketType::SourceCalPoint;
const QString ownCal = isSourceCal ? "Source" : "Receiver";
const QString otherCal = 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.";
auto d = new QDialog(this);
auto ui = new Ui::AutomaticAmplitudeDialog();
ui->setupUi(d);
ui->explanation->setText(info);
ui->status->setText("Gathering information about "+otherCal+" Calibration...");
automatic.points.clear();
connect(d, &QDialog::rejected, ui->abort, &QPushButton::click);
connect(ui->abort, &QPushButton::clicked, [=](){
// aborted, clean up
delete d;
});
dev->SetIdle();
auto receiveConn = connect(dev, &Device::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(dev, &Device::AmplitudeCorrectionPointReceived, this, nullptr);
}
});
// request points of otherCal
// switch between source/receiver calibration
auto request = isSourceCal ? Protocol::PacketType::RequestReceiverCal : Protocol::PacketType::RequestSourceCal;
dev->SendCommandWithoutPayload(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
automatic.resultConnection = connect(dev, &Device::SpectrumResultReceived, [=](Protocol::SpectrumAnalyzerResult res) {
if(res.pointNum != 1) {
// ignore first and last point, only use the middle one
return;
}
if(automatic.settlingCount > 0) {
automatic.settlingCount--;
return;
}
// Grab correct measurement
double measurement;
if(isSourceCal) {
// For a source calibration we need the measurement of the other port (measuring the ouput power of the port to calibrate)
measurement = automatic.measuringPort2 ? res.port1 : res.port2;
} else {
// For a receiver calibration we need the measurement of the port to calibrate
measurement = automatic.measuringPort2 ? res.port2 : res.port1;
}
// convert to dbm
double reported_dbm = 20*log10(measurement);
if(isSourceCal) {
// receiver cal already done, the measurement result is accurate and can be used to determine actual output power
setAmplitude(reported_dbm, automatic.measuringCount, automatic.measuringPort2);
} else {
// source cal already done, the output power is accurate while the measurement might be off
setAmplitude(excitationAmplitude, automatic.measuringCount, automatic.measuringPort2);
}
// 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, disconnect this lambda and close dialog
disconnect(automatic.resultConnection);
delete d;
dev->SetIdle();
} else {
// update progress bar
ui->progress->setValue(100 * automatic.measuringCount / points.size());
// Start next measurement
SetupNextAutomaticPoint(isSourceCal);
}
}
});
automatic.measuringPort2 = false;
automatic.measuringCount = 0;
SetupNextAutomaticPoint(isSourceCal);
});
d->show();
}
bool AmplitudeCalDialog::ConfirmActionIfEdited()
{
if(edited) {
auto reply = QMessageBox::question(this, "Confirm action", "Some points have been edited but not saved in the device yet. If you continue, all changes will be lost. Do you want to continue?",
QMessageBox::Yes|QMessageBox::No);
return reply == QMessageBox::Yes;
} 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;
}
}
ui->save->setEnabled(enable);
}
void AmplitudeCalDialog::SetupNextAutomaticPoint(bool isSourceCal)
{
auto point = automatic.points[automatic.measuringCount];
Protocol::PacketInfo p = {};
p.type = Protocol::PacketType::SpectrumAnalyzerSettings;
p.spectrumSettings.RBW = 10000;
p.spectrumSettings.UseDFT = 0;
// setup 3 points centered around the measurement frequency (zero span not supported yet)
p.spectrumSettings.f_stop = point.frequency + 1.0;
p.spectrumSettings.f_start = point.frequency - 1.0;
p.spectrumSettings.pointNum = 3;
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)
p.spectrumSettings.trackingGeneratorPort = isSourceCal ? automatic.measuringPort2 : !automatic.measuringPort2;
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 = 30;
dev->SendPacket(p);
}
AmplitudeCalDialog::CalibrationMode AmplitudeCalDialog::getMode() const
{
return mode;
}
AmplitudeModel::AmplitudeModel(AmplitudeCalDialog *c) :
QAbstractTableModel(),
c(c)
{
}
int AmplitudeModel::rowCount(const QModelIndex &parent) const
{
return c->getPoints().size();
}
int AmplitudeModel::columnCount(const QModelIndex &parent) const
{
return ColIndexLast;
}
QVariant AmplitudeModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::DisplayRole && 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 role)
{
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;
}