AutoSpa/examples/rp2040_port/host/src/MainWindow_Serial.cpp

267 lines
7.8 KiB
C++

// File: host/src/MainWindow_Serial.cpp
#include "MainWindow.h"
#include <QDateTime>
#include <complex>
void MainWindow::refreshPorts() {
portSelector->clear();
const auto infos = QSerialPortInfo::availablePorts();
bool foundTarget = false;
QString targetPort;
for (const QSerialPortInfo &info : infos) {
portSelector->addItem(info.portName());
bool isCafe =
(info.hasVendorIdentifier() && info.vendorIdentifier() == 0xCAFE);
bool isUsbModem = info.portName().contains("usbmodem", Qt::CaseInsensitive);
if ((isCafe || isUsbModem) && !foundTarget) {
targetPort = info.portName();
foundTarget = true;
logWidget->append(">> Found Target Device: " + targetPort);
}
}
if (foundTarget) {
portSelector->setCurrentText(targetPort);
if (!serial->isOpen())
connectToPort();
}
}
void MainWindow::connectToPort() {
if (serial->isOpen()) {
serial->close();
connectBtn->setText("Connect");
logWidget->append("--- Disconnected ---");
// 1. Disable Global Controls
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
comboTiaRange->setEnabled(false);
// 2. Disable Tabs
mainTabWidget->widget(0)->setEnabled(false);
mainTabWidget->widget(1)->setEnabled(false);
mainTabWidget->widget(2)->setEnabled(false);
isMeasuringImp = false;
isMeasuringAmp = false;
isSweeping = false;
lsvState = LSV_IDLE;
currentSequence = SEQ_IDLE;
setButtonBlinking(nullptr, false);
measureBtn->setText("Measure");
ampBtn->setText("Start Amp");
lsvBlankBtn->setText("Run Blank");
lsvSampleBtn->setText("Run Sample");
return;
}
if (portSelector->currentText().isEmpty())
return;
serial->setPortName(portSelector->currentText());
serial->setBaudRate(500000);
if (serial->open(QIODevice::ReadWrite)) {
connectBtn->setText("Disconnect");
logWidget->append("--- Connected and Synchronized ---");
// 1. Enable Global Controls
checkIdBtn->setEnabled(true);
calibrateBtn->setEnabled(true);
comboTiaRange->setEnabled(true);
// 2. Enable Tabs
mainTabWidget->widget(0)->setEnabled(true);
mainTabWidget->widget(1)->setEnabled(true);
mainTabWidget->widget(2)->setEnabled(true);
// Sync LPF
onLPFChanged(comboLPF->currentIndex());
} else {
logWidget->append(">> Connection Error: " + serial->errorString());
}
}
void MainWindow::onPortError(QSerialPort::SerialPortError error) {
if (error == QSerialPort::ResourceError) {
logWidget->append(">> Critical Error: Connection Lost.");
serial->close();
connectBtn->setText("Connect");
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
comboTiaRange->setEnabled(false);
mainTabWidget->widget(0)->setEnabled(false);
mainTabWidget->widget(1)->setEnabled(false);
mainTabWidget->widget(2)->setEnabled(false);
setButtonBlinking(nullptr, false);
currentSequence = SEQ_IDLE;
}
}
void MainWindow::handleSerialData() {
while (serial->canReadLine()) {
QByteArray line = serial->readLine();
QString str = QString::fromUtf8(line).trimmed();
if (str.isEmpty())
continue;
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz");
logWidget->append(QString("[%1] %2").arg(timestamp, str));
logWidget->moveCursor(QTextCursor::End);
// --- Sequence State Machine ---
if (currentSequence == SEQ_WAIT_BOOT) {
if (str.contains("AD5940LIB Version:v0.2.1")) {
logWidget->append(">> Sequence: Boot detected. Configuring...");
// 1. Restore Settings
int hpVal = comboTiaRange->currentData().toInt();
serial->write(QString("r %1\n").arg(hpVal).toUtf8());
serial->write(QString("t 0\n").toUtf8());
// 2. Start Calibration
logWidget->append(">> Sequence: Calibrating...");
serial->write("c\n");
currentSequence = SEQ_WAIT_CALIB;
}
} else if (currentSequence == SEQ_WAIT_CALIB) {
if (str.contains("RCAL,HSTIA")) {
logWidget->append(">> Sequence: Calibration Done. Sending Command.");
serial->write(pendingCommand.toUtf8());
serial->write("\n");
currentSequence = SEQ_IDLE;
}
}
// ------------------------------
if (str.startsWith("DATA,")) {
parseData(str);
} else if (str.startsWith("RCAL,")) {
parseData(str);
} else if (str.startsWith("AMP,")) {
parseData(str);
} else if (str.startsWith("RAMP,")) {
parseData(str);
} else if (str == "STOPPED") {
// Reset UI state
if (lsvState != LSV_IDLE)
stopLSV();
if (isSweeping) {
isSweeping = false;
sweepBtn->setText("Sweep");
setButtonBlinking(nullptr, false);
}
if (isMeasuringImp) {
isMeasuringImp = false;
measureBtn->setText("Measure");
setButtonBlinking(nullptr, false);
}
if (isMeasuringAmp) {
isMeasuringAmp = false;
ampBtn->setText("Start Amp");
setButtonBlinking(nullptr, false);
}
}
}
}
void MainWindow::parseData(const QString &data) {
QStringList parts = data.split(',');
if (parts[0] == "AMP" && parts.size() >= 3) {
bool okIdx, okCurr;
double index = parts[1].toDouble(&okIdx);
double current = parts[2].toDouble(&okCurr);
if (okIdx && okCurr)
ampGraph->addAmperometricData(index, current);
return;
}
if (parts[0] == "RAMP" && parts.size() >= 3) {
bool okIdx, okCurr;
double index = parts[1].toDouble(&okIdx);
double current = parts[2].toDouble(&okCurr);
if (okIdx && okCurr) {
// Calculate Voltage based on UI parameters (Host-side calculation)
double start = spinLsvStart->value();
double stop = spinLsvStop->value();
int steps = spinLsvSteps->value();
double voltage = start + (index * (stop - start) / steps);
if (lsvState == LSV_RUNNING_BLANK) {
lsvGraph->addLSVData(voltage, current, GraphWidget::LSV_BLANK);
lsvBlankData.append({voltage, current});
} else if (lsvState == LSV_RUNNING_SAMPLE) {
lsvGraph->addLSVData(voltage, current, GraphWidget::LSV_SAMPLE);
lsvSampleData.append({voltage, current});
}
}
return;
}
if (parts[0] == "RCAL" && parts.size() >= 4) {
QString type = parts[1];
if (type == "FAIL") {
logWidget->append(QString(">> RCAL FAIL: %1").arg(data));
return;
}
bool okM, okP;
double mag = parts[2].toDouble(&okM);
double phase = parts[3].toDouble(&okP);
if (okM && okP) {
logWidget->append(QString(">> Calibration [%1]: Mag=%.2f Phase=%.2f")
.arg(type)
.arg(mag)
.arg(phase));
// TODO: Store this if needed for UI display
}
return;
}
if (parts[0] == "DATA" && parts.size() >= 6) {
bool okF, okM, okP;
double freq = parts[1].toDouble(&okF);
double mag = parts[4].toDouble(&okM);
double phase = parts[5].toDouble(&okP);
if (okF && okM && okP) {
// 1. Plot Bode (Magnitude/Phase)
// Note: Phase from AD5940 is in Radians.
rawGraph->addBodeData(freq, mag, phase);
// 2. Convert to Cartesian for Nyquist (Real/Imaginary)
// Z = Mag * e^(j*Phase) = Mag * (cos(Phase) + j*sin(Phase))
// Note: Nyquist Plot expects Z' vs -Z''.
std::complex<double> z = std::polar(mag, phase);
double real = z.real();
double imag =
z.imag(); // This is Z''. GraphWidget negates it for the plot.
nyquistGraph->addNyquistData(real, imag, real, imag, false);
if (sweepFreqs.size() >= expectedSweepPoints) {
// Discard extra points (Artifact removal)
return;
}
sweepFreqs.append(freq);
sweepReals.append(real);
sweepImags.append(imag);
if (sweepReals.size() > 10 && sweepReals.size() % 10 == 0) {
computeHilbert();
performCircleFit();
}
}
}
}