371 lines
12 KiB
C++
371 lines
12 KiB
C++
#include "UpdateFPGA.h"
|
||
#include "myinclude.h"
|
||
#include "Public.h"
|
||
#include <QCoreApplication>
|
||
#include <QMessageBox>
|
||
#include <QLayout>
|
||
#include <QStandardPaths>
|
||
#include <QFile>
|
||
|
||
#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"));
|
||
}
|
||
|
||
UpdateFPGA::~UpdateFPGA()
|
||
{
|
||
if (!conversionProcess) return;
|
||
if (conversionProcess->state() == QProcess::Running)
|
||
{
|
||
conversionProcess->terminate();
|
||
conversionProcess->waitForFinished();
|
||
conversionProcess->deleteLater();
|
||
conversionProcess = nullptr;
|
||
}
|
||
}
|
||
|
||
void UpdateFPGA::init()
|
||
{
|
||
QFile file(SYS_ROOT_DIR"UseEn");
|
||
showChinese = true;
|
||
if(file.exists())
|
||
{
|
||
QString langurageType = Public::ReadLanguageName();
|
||
if(langurageType != "Chinese")
|
||
showChinese = false;
|
||
}
|
||
|
||
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(":/icon/keydelete.png"));
|
||
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::clicked,this,&UpdateFPGA::closeUI);
|
||
|
||
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::closeUI(){
|
||
close();
|
||
emit upgradeFPGAFinished(true);
|
||
}
|
||
|
||
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(true);
|
||
}
|
||
else // 有错误码的失败
|
||
{
|
||
QString errorDesc = getErrorDescription(exitCode);
|
||
logTextEdit->append(tr("升级失败,错误码: %1,原因: %2")
|
||
.arg(exitCode).arg(errorDesc));
|
||
|
||
// 询问用户是否重试(针对可恢复错误)
|
||
QMessageBox::StandardButton 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(true);
|
||
m_UpdateButton->setEnabled(true); // 允许手动再次尝试
|
||
m_CloseBtn->setEnabled(true);
|
||
}
|
||
}
|
||
}
|
||
else // 进程异常退出(无错误码)
|
||
{
|
||
logTextEdit->append(tr("升级进程异常退出,退出码: %1").arg(exitCode));
|
||
QMessageBox::StandardButton reply = QMessageBox::question(this,
|
||
tr("进程异常"),
|
||
tr("升级过程中程序崩溃,是否重新尝试?"),
|
||
QMessageBox::Yes | QMessageBox::No);
|
||
|
||
if (reply == QMessageBox::Yes)
|
||
{
|
||
startUpdate();
|
||
}
|
||
else
|
||
{
|
||
close();
|
||
emit upgradeFPGAFinished(true);
|
||
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("错误详情: ") + conversionProcess->readAllStandardError());
|
||
|
||
// 进程错误时也提示重试
|
||
QMessageBox::StandardButton reply = QMessageBox::question(this, tr("操作错误"), errorMsg + "\n" + tr("是否重新尝试?"), QMessageBox::Yes | QMessageBox::No);
|
||
|
||
if (reply == QMessageBox::Yes)
|
||
{
|
||
startUpdate();
|
||
}
|
||
else
|
||
{
|
||
close();
|
||
emit upgradeFPGAFinished(true);
|
||
m_UpdateButton->setEnabled(true);
|
||
m_CloseBtn->setEnabled(true);
|
||
}
|
||
}
|