Files

354 lines
12 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "UpdateFPGA.h"
#include <QCoreApplication>
#include <QMessageBox>
#include <QLayout>
#include <QStandardPaths>
#pragma execution_character_set("utf-8")
UpdateFPGA::UpdateFPGA(const QString& pythonScriptPath, QWidget* parent)
: QDialog(parent), m_PythonScriptPath(pythonScriptPath)
{
this->setWindowFlags(this->windowFlags() | Qt::FramelessWindowHint);
this->setObjectName("UpdateFPGA");
this->setStyleSheet("QDialog#UpdateFPGA{background-color:#FFFFFF;border:0px solid #D6D6D6;}");
this->setWindowIcon(QPixmap(":/UpdateFPGA/logo.png"));
// init();
}
UpdateFPGA::~UpdateFPGA()
{
if (conversionProcess->state() == QProcess::Running)
{
conversionProcess->terminate();
conversionProcess->waitForFinished();
}
}
void UpdateFPGA::init()
{
setWindowTitle(tr("升级工具"));
setMinimumSize(600, 400);
QLabel* Logo = new QLabel(this);
Logo->setFixedSize(67, 25);
Logo->setPixmap(QPixmap(":/UpdateFPGA/logo.png"));
Logo->setScaledContents(true);
m_Title = new QLabel(this);
m_Title->setStyleSheet("font-family:\"Microsoft YaHei\";color:#333333;font-size:16px;");
m_Title->setFixedHeight(25);
m_Title->setText(tr("升级工具"));
m_CloseBtn = new QPushButton(this);
m_CloseBtn->setFixedSize(25, 25);
m_CloseBtn->setIconSize(QSize(20, 20));
m_CloseBtn->setIcon(QPixmap(":/UpdateFPGA/close.svg"));
m_CloseBtn->setCursor(Qt::PointingHandCursor);
m_CloseBtn->setStyleSheet("QPushButton{background-color:#ffffff;border-radius:3px;border:none;}\
QPushButton::hover{background-color:#EEEEEE;}");
connect(m_CloseBtn, &QPushButton::released, this, [this]() {
this->close();
});
QHBoxLayout* hlayout = new QHBoxLayout;
hlayout->addSpacing(5);
hlayout->addWidget(Logo);
hlayout->addWidget(m_Title);
hlayout->addStretch();
hlayout->addWidget(m_CloseBtn);
hlayout->addSpacing(10);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
m_UpdateButton = new QPushButton(tr("开始升级"), this);
m_UpdateButton->setFixedHeight(30);
logLabel = new QLabel(tr("升级日志:"), this);
logTextEdit = new QTextEdit(this);
logTextEdit->setReadOnly(true);
mainLayout->addLayout(hlayout);
mainLayout->addWidget(m_UpdateButton);
mainLayout->addWidget(logLabel);
mainLayout->addWidget(logTextEdit, 1);
conversionProcess = new QProcess(this);
connect(m_UpdateButton, &QPushButton::clicked, this, &UpdateFPGA::startUpdate);
connect(conversionProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &UpdateFPGA::processFinished);
connect(conversionProcess, &QProcess::errorOccurred, this, &UpdateFPGA::processError);
connect(conversionProcess, &QProcess::readyReadStandardOutput, [this]() {
logTextEdit->append(conversionProcess->readAllStandardOutput());
}
);
connect(conversionProcess, &QProcess::readyReadStandardError, [this]() {
logTextEdit->append(conversionProcess->readAllStandardError());
}
);
}
void UpdateFPGA::startUpdate()
{
m_CloseBtn->setEnabled(false);
if (m_PythonScriptPath.isEmpty())
{
logTextEdit->append(tr("错误: 脚本为空"));
return;
}
QFile inputFile(m_PythonScriptPath);
if (!inputFile.exists())
{
logTextEdit->append(tr("错误: 脚本不存在 - %1").arg(m_PythonScriptPath));
return;
}
logTextEdit->append(tr("开始升级..."));
logTextEdit->append(tr("文件:%1").arg(m_PythonScriptPath));
// 自动查找Ubuntu 22.04中可用的Python解释器
QString pythonPath = findValidPythonInterpreter();
if (pythonPath.isEmpty())
{
logTextEdit->append(tr("错误: 未找到可用的Python解释器或缺少serial模块"));
m_CloseBtn->setEnabled(true);
return;
}
logTextEdit->append(tr("使用Python解释器: %1").arg(pythonPath));
QStringList pythonArgs;
pythonArgs << m_PythonScriptPath;
conversionProcess->start(pythonPath, pythonArgs);
m_UpdateButton->setEnabled(false); // 运行中禁用按钮
}
// 查找并验证Ubuntu系统中有效的Python解释器
QString UpdateFPGA::findValidPythonInterpreter()
{
QStringList pythonNames = { "python3", "python3.10", "python3.11", "python3.9" };
QStringList searchPaths;
QString home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
searchPaths << home + "/.local/bin"
<< home + "/.pyenv/shims"
<< home + "/.virtualenvs/*/bin"
<< home + "/venv/bin"
<< home + "/env/bin";
searchPaths << "/usr/bin"
<< "/usr/local/bin"
<< "/bin"
<< "/opt/bin"
<< "/snap/bin";
QByteArray pathEnvBytes = qgetenv("PATH");
QString pathEnvStr = QString::fromLocal8Bit(pathEnvBytes);
QStringList pathEnv = pathEnvStr.split(QDir::listSeparator());
foreach(const QString & path, pathEnv)
{
if (!searchPaths.contains(path))
{
searchPaths << path;
}
}
// 遍历所有可能的组合查找有效解释器
foreach(const QString & path, searchPaths)
{
foreach(const QString & name, pythonNames)
{
if (path.contains("*"))
{
QDir dir(path.left(path.indexOf("*")));
if (dir.exists())
{
QStringList subDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
foreach(const QString & subDir, subDirs)
{
QString fullPath = dir.filePath(subDir + "/bin/" + name);
if (QFile::exists(fullPath) && isValidPythonWithSerial(fullPath))
{
return fullPath;
}
}
}
}
else
{
QString fullPath = QDir(path).filePath(name);
if (QFile::exists(fullPath) && isValidPythonWithSerial(fullPath))
{
return fullPath;
}
}
}
}
return "";
}
// 验证Python解释器是否包含serial模块
bool UpdateFPGA::isValidPythonWithSerial(const QString& pythonPath)
{
// 检查Python是否可运行
QProcess testProcess;
testProcess.setProcessChannelMode(QProcess::MergedChannels); // 合并输出通道
testProcess.start(pythonPath, { "-c", "import sys; sys.exit(0)" });
if (!testProcess.waitForFinished(3000) || testProcess.exitCode() != 0)
{
logTextEdit->append(tr("调试: 无效的Python解释器 - %1").arg(pythonPath));
return false;
}
// 检查是否安装了serial模块
testProcess.start(pythonPath, { "-c", "import serial; print('serial found: ' + serial.__version__)" });
if (testProcess.waitForFinished(3000))
{
if (testProcess.exitCode() == 0)
{
QString output = testProcess.readAll().trimmed();
logTextEdit->append(tr("调试: %1").arg(output));
return true;
}
else
{
logTextEdit->append(tr("调试: %1 缺少serial模块").arg(pythonPath));
}
}
else
{
logTextEdit->append(tr("调试: %1 响应超时").arg(pythonPath));
}
return false;
}
// 根据错误码解析具体错误信息
QString UpdateFPGA::getErrorDescription(int errorCode)
{
switch (errorCode)
{
case 1: return tr("文件不存在(请检查FPGA固件文件路径)");
case 2: return tr("无可用串口(请检查设备连接)");
case 3: return tr("串口通信错误(可能是波特率不匹配或设备断开)");
case 4: return tr("数据发送失败(尝试重新发送)");
case 5: return tr("数据校验失败(数据传输过程中损坏)");
case 6: return tr("接收数据不完整(可能是传输中断)");
case 7: return tr("文件大小发送失败(协议握手错误)");
case 8: return tr("文件内容发送失败(大文件传输超时)");
case 9: return tr("未知错误(请查看详细日志)");
default: return tr("未定义错误");
}
}
void UpdateFPGA::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
if (exitStatus == QProcess::NormalExit)
{
if (exitCode == 0) // 成功(错误码0)
{
logTextEdit->append(tr("升级成功!"));
QMessageBox::information(this, tr("成功"), tr("FPGA升级完成,请重启QtUI"), QMessageBox::Ok);
close(); // 成功后关闭界面
emit upgradeFPGAFinished();
}
else // 有错误码的失败
{
QString errorDesc = getErrorDescription(exitCode);
logTextEdit->append(tr("升级失败,错误码: %1,原因: %2").arg(exitCode).arg(errorDesc));
// 询问用户是否重试(针对可恢复错误)
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this,
tr("升级失败"),
tr("错误码%1: %2\n是否重新尝试升级?").arg(exitCode).arg(errorDesc),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes)
{
startUpdate(); // 重新运行
}
else
{
close();
emit upgradeFPGAFinished();
m_UpdateButton->setEnabled(true); // 允许手动再次尝试
m_CloseBtn->setEnabled(true);
}
}
}
else // 进程异常退出(无错误码)
{
logTextEdit->append(tr("升级进程异常退出,退出码: %1").arg(exitCode));
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this,
tr("进程异常"),
tr("升级过程中程序崩溃,是否重新尝试?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes)
{
startUpdate();
}
else
{
close();
emit upgradeFPGAFinished();
m_UpdateButton->setEnabled(true);
m_CloseBtn->setEnabled(true);
}
}
}
void UpdateFPGA::processError(QProcess::ProcessError error)
{
QString errorMsg;
switch (error)
{
case QProcess::FailedToStart:
errorMsg = tr("错误: 无法启动脚本(可能是Python环境未配置)");
break;
case QProcess::Crashed:
errorMsg = tr("错误: 脚本进程崩溃");
break;
case QProcess::Timedout:
errorMsg = tr("错误: 启动超时(设备无响应)");
break;
case QProcess::WriteError:
errorMsg = tr("错误: 写入进程失败(权限不足)");
break;
case QProcess::ReadError:
errorMsg = tr("错误: 读取进程输出失败");
break;
case QProcess::UnknownError:
default:
errorMsg = tr("错误: 未知错误");
break;
}
logTextEdit->append(errorMsg);
logTextEdit->append(tr("错误详情: ") + QString::fromLocal8Bit(conversionProcess->readAllStandardError()));
// 进程错误时也提示重试
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, tr("操作错误"), errorMsg + tr("\n是否重新尝试?"), QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes)
{
startUpdate();
}
else
{
close();
emit upgradeFPGAFinished();
m_UpdateButton->setEnabled(true);
m_CloseBtn->setEnabled(true);
}
}