PC Application: partial firmware update dialog

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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