Tuxi_Test_Qt/src/mainwindow.cpp
2025-09-24 16:01:16 +08:00

2054 lines
83 KiB
C++
Raw 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 "mainwindow.h"
#include "lightstripmanager.h"
#include "updatesettingsdialog.h"
#include <QDateTime>
#include <QJsonObject>
#include <QJsonDocument>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QFileInfo>
#include <QDir>
#include <QStandardPaths>
#include <QMessageBox>
#include <QUrl>
#include <QIcon>
#include <QDebug>
#include <QScreen>
#include <QGuiApplication>
#include <QProgressDialog>
#include <QFile>
#include <QProcess>
#include <QInputDialog>
#include <QStorageInfo>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), otaOperationPending(false), isUpgradeOperation(true)
{
// 初始化设置对象
settings = new QSettings("TuxiApp", "LightStripSN", this);
// 初始化更新相关变量
currentVersion = "1.5.2"; // 当前程序版本
updateServerUrl = settings->value("updateServerUrl", "http://180.163.74.83:8001/version").toString();
updateNetworkManager = new QNetworkAccessManager(this);
updateCheckReply = nullptr;
updateDownloadReply = nullptr;
downloadedUpdatePath = "";
setupUI();
// 创建MQTT客户端
mqttClient = new MqttClient(this);
// 连接信号和槽
connect(connectBtn, &QPushButton::clicked, this, &MainWindow::onConnectClicked);
connect(disconnectBtn, &QPushButton::clicked, this, &MainWindow::onDisconnectClicked);
connect(sendLightAllBtn, &QPushButton::clicked, this, &MainWindow::onSendLightAllClicked);
connect(clearSnBtn, &QPushButton::clicked, this, &MainWindow::onClearSnListClicked);
connect(searchLightStripBtn, &QPushButton::clicked, this, &MainWindow::onSearchLightStripClicked);
connect(mqttClient, &MqttClient::connected, this, &MainWindow::onMqttConnected);
connect(mqttClient, &MqttClient::disconnected, this, &MainWindow::onMqttDisconnected);
connect(mqttClient, &MqttClient::messageReceived, this, &MainWindow::onMessageReceived);
connect(mqttClient, &MqttClient::errorOccurred, this, &MainWindow::onMqttError);
connect(otaUpgradeBtn, &QPushButton::clicked, this, &MainWindow::onOtaUpgradeClicked);
connect(otaDowngradeBtn, &QPushButton::clicked, this, &MainWindow::onOtaDowngradeClicked);
connect(getVersionBtn, &QPushButton::clicked, this, &MainWindow::onGetVersionClicked);
// 添加设备SN输入框变化监听自动重新订阅主题
connect(deviceSnEdit, &QLineEdit::textChanged, this, &MainWindow::onDeviceSnChanged);
// 初始化灯条管理器
lightStripManager = nullptr;
// 加载已保存的灯条SN列表
loadSnList();
// 初始化状态
updateConnectionStatus(false);
currentVersionEdit->setText("请点击获取版本");
}
MainWindow::~MainWindow()
{
}
/*
void MainWindow::setupMenuBar()
{
// 获取主窗口的菜单栏
QMenuBar *mainMenuBar = this->menuBar();
// 创建帮助菜单
QMenu *helpMenu = mainMenuBar->addMenu("帮助(&H)");
// 创建版本更新说明菜单项
versionUpdateAction = new QAction("版本更新说明(&V)", this);
helpMenu->addAction(versionUpdateAction);
// 连接信号槽
connect(versionUpdateAction, &QAction::triggered, this, &MainWindow::showVersionUpdateInfo);
}
void MainWindow::showVersionUpdateInfo()
{
QString versionInfo =
"版本更新说明\n\n"
"版本 1.3.0 (2024-01-15)\n"
"• 新增版本更新说明菜单\n"
"• 优化MQTT连接稳定性\n"
"• 修复已知问题\n\n"
"版本 1.2.0 (2023-12-20)\n"
"• 改进用户界面\n"
"• 增强数据处理能力\n\n"
"版本 1.1.0 (2023-11-10)\n"
"• 初始版本发布\n"
"• 基础功能实现";
QMessageBox::information(this, "版本更新说明", versionInfo);
}
*/
void MainWindow::setupUI() {
setWindowTitle("兔喜Test1.5.2 Author:Zhangzhenghao Email:zzh9953477@gmail.com");
// 参考qt_bak的合理尺寸设置增加竖向高度
setMinimumSize(850, 720); // 增加最小高度
resize(900, 770); // 增加初始高度从750增加到850
// 获取屏幕尺寸并设置合适的窗口大小
QScreen *screen = QGuiApplication::primaryScreen();
if (screen) {
QRect screenGeometry = screen->geometry();
int screenWidth = screenGeometry.width();
int screenHeight = screenGeometry.height();
// 设置合理的窗口大小,增加竖向比例
int windowWidth = qMin(1000, static_cast<int>(screenWidth * 0.6));
int windowHeight = qMin(900, static_cast<int>(screenHeight * 0.7)); // 增加到75%
// 确保高度明显大于宽度
if (windowHeight < windowWidth * 0.95) {
windowHeight = static_cast<int>(windowWidth * 0.9); // 高度等于宽度
}
resize(windowWidth, windowHeight);
// 居中显示
move((screenWidth - windowWidth) / 2, (screenHeight - windowHeight) / 2 - 50);
}
setWindowIcon(QIcon(":/image/src/tuxi.ico"));
centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
mainLayout = new QVBoxLayout(centralWidget);
mainLayout->setSpacing(10); // 减少布局间距
mainLayout->setContentsMargins(15, 10, 15, 10); // 减少边距
// 获取适合当前主题的文字颜色
QString textColor = getTextColorForTheme();
// MQTT连接区域
connectionGroup = new QGroupBox("MQTT连接设置", this);
connectionGroup->setStyleSheet(QString("QGroupBox { font-weight: bold; font-size: 12px; padding-top: 10px; color: %1; }").arg(textColor));
QVBoxLayout *connectionLayout = new QVBoxLayout(connectionGroup);
connectionLayout->setSpacing(10);
// 服务器和端口 - 使用水平布局
QHBoxLayout *serverLayout = new QHBoxLayout();
QLabel *serverLabel = new QLabel("服务器:");
serverLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
serverLayout->addWidget(serverLabel);
brokerEdit = new QLineEdit("tx-mqtt.zt-express.com");
brokerEdit->setMinimumHeight(30);
serverLayout->addWidget(brokerEdit);
QLabel *portLabel = new QLabel("端口:");
portLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
serverLayout->addWidget(portLabel);
portEdit = new QLineEdit("1883");
portEdit->setMinimumHeight(30);
portEdit->setMaximumWidth(100);
serverLayout->addWidget(portEdit);
connectionLayout->addLayout(serverLayout);
// 用户名和密码
QHBoxLayout *authLayout = new QHBoxLayout();
QLabel *usernameLabel = new QLabel("用户名:");
usernameLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
authLayout->addWidget(usernameLabel);
usernameEdit = new QLineEdit("TJ251679787196");
usernameEdit->setMinimumHeight(30);
authLayout->addWidget(usernameEdit);
QLabel *passwordLabel = new QLabel("密码:");
passwordLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
authLayout->addWidget(passwordLabel);
passwordEdit = new QLineEdit();
passwordEdit->setEchoMode(QLineEdit::Password);
passwordEdit->setMinimumHeight(30);
authLayout->addWidget(passwordEdit);
connectionLayout->addLayout(authLayout);
// 连接按钮
QHBoxLayout *btnLayout = new QHBoxLayout();
connectBtn = new QPushButton("连接");
connectBtn->setMinimumHeight(35);
connectBtn->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; padding: 8px 16px; border-radius: 4px; }");
disconnectBtn = new QPushButton("断开");
disconnectBtn->setMinimumHeight(35);
disconnectBtn->setStyleSheet("QPushButton { background-color: #f44336; color: white; font-weight: bold; padding: 8px 16px; border-radius: 4px; }");
btnLayout->addWidget(connectBtn);
btnLayout->addWidget(disconnectBtn);
btnLayout->addStretch();
connectionLayout->addLayout(btnLayout);
mainLayout->addWidget(connectionGroup);
// 设备控制区域
deviceGroup = new QGroupBox("设备控制", this);
deviceGroup->setStyleSheet("QGroupBox { font-weight: bold; font-size: 12px; padding-top: 10px; }");
QHBoxLayout *deviceLayout = new QHBoxLayout(deviceGroup);
deviceLayout->setSpacing(10);
QLabel *deviceSnLabel = new QLabel("需要测试的设备SN:");
deviceSnLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
deviceLayout->addWidget(deviceSnLabel);
deviceSnEdit = new QLineEdit("TJ251617198122");
deviceSnEdit->setMinimumHeight(30);
deviceLayout->addWidget(deviceSnEdit);
mainLayout->addWidget(deviceGroup);
// 点亮参数控制区域
lightGroup = new QGroupBox("点亮参数设置", this);
lightGroup->setStyleSheet("QGroupBox { font-weight: bold; font-size: 12px; padding-top: 10px; }");
QVBoxLayout *lightLayout = new QVBoxLayout(lightGroup);
lightLayout->setSpacing(10);
// 第一行:颜色和闪烁
QHBoxLayout *row1Layout = new QHBoxLayout();
QLabel *colorLabel = new QLabel("颜色:");
colorLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
row1Layout->addWidget(colorLabel);
colorCombo = new QComboBox();
colorCombo->addItems({"8-灭", "1-红", "2-黄", "3-蓝", "4-绿", "5-青", "6-白", "7-紫"});
colorCombo->setCurrentIndex(6);
colorCombo->setMinimumHeight(30);
row1Layout->addWidget(colorCombo);
QLabel *flashLabel = new QLabel("闪烁:");
flashLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
row1Layout->addWidget(flashLabel);
flashCombo = new QComboBox();
flashCombo->addItems({"0-关闭", "1-开启"});
flashCombo->setCurrentIndex(0);
flashCombo->setMinimumHeight(30);
row1Layout->addWidget(flashCombo);
row1Layout->addStretch();
lightLayout->addLayout(row1Layout);
// 第二行:闪烁间隔和点亮时长
QHBoxLayout *row2Layout = new QHBoxLayout();
QLabel *flashIntervalLabel = new QLabel("闪烁间隔(秒):");
flashIntervalLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
row2Layout->addWidget(flashIntervalLabel);
flashIntervalSpin = new QSpinBox();
flashIntervalSpin->setRange(1, 60);
flashIntervalSpin->setValue(4);
flashIntervalSpin->setMinimumHeight(30);
row2Layout->addWidget(flashIntervalSpin);
QLabel *lightDurationLabel = new QLabel("点亮时长(秒):");
lightDurationLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
row2Layout->addWidget(lightDurationLabel);
lightDurationSpin = new QSpinBox();
lightDurationSpin->setRange(1, 300);
lightDurationSpin->setValue(30);
lightDurationSpin->setMinimumHeight(30);
row2Layout->addWidget(lightDurationSpin);
row2Layout->addStretch();
lightLayout->addLayout(row2Layout);
// 第三行:声音和发送按钮
QHBoxLayout *row3Layout = new QHBoxLayout();
QLabel *soundLabel = new QLabel("声音:");
soundLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
row3Layout->addWidget(soundLabel);
soundCombo = new QComboBox();
soundCombo->addItems({"0-关闭", "1-开启"});
soundCombo->setCurrentIndex(0);
soundCombo->setMinimumHeight(30);
row3Layout->addWidget(soundCombo);
row3Layout->addStretch();
sendLightAllBtn = new QPushButton("发送全部点亮");
sendLightAllBtn->setMinimumHeight(35);
sendLightAllBtn->setMinimumWidth(120);
sendLightAllBtn->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; padding: 10px 20px; border-radius: 6px; font-size: 14px; }");
row3Layout->addWidget(sendLightAllBtn);
lightLayout->addLayout(row3Layout);
mainLayout->addWidget(lightGroup);
// 灯条SN管理区域 - 简化为一个按钮
snGroup = new QGroupBox("灯条SN管理", this);
snGroup->setStyleSheet(
"QGroupBox { "
" font-weight: bold; "
" font-size: 14px; "
" padding-top: 15px; "
" margin: 5px; "
" border: 2px solid #ddd; "
" border-radius: 8px; "
"}"
);
snGroup->setMinimumHeight(120); // 减少最小高度
snGroup->setMaximumHeight(140); // 设置最大高度限制
QVBoxLayout *snLayout = new QVBoxLayout(snGroup);
snLayout->setSpacing(8); // 减少组件间距
snLayout->setContentsMargins(10, 20, 10, 10); // 减少内边距
// 初始化snHorizontalLayout
snHorizontalLayout = new QHBoxLayout();
// 统计信息和搜索按钮布局
QHBoxLayout *snHeaderLayout = new QHBoxLayout();
snHeaderLayout->setSpacing(8);
snCountLabel = new QLabel("已发现灯条: 0 个");
snCountLabel->setStyleSheet(
"QLabel { "
" font-weight: bold; "
" font-size: 13px; "
" color: #2196F3; "
" padding: 3px 8px; "
" background-color: #f0f8ff; "
" border: 1px solid #ddd; "
" border-radius: 6px; "
" min-height: 12px; "
"}"
);
snHeaderLayout->addWidget(snCountLabel);
snHeaderLayout->addStretch();
searchLightStripBtn = new QPushButton("搜索灯条", this);
searchLightStripBtn->setMinimumHeight(26); // 减少按钮高度
searchLightStripBtn->setMinimumWidth(90);
searchLightStripBtn->setStyleSheet(
"QPushButton { "
" background-color: #2196F3; "
" color: white; "
" padding: 4px 10px; "
" font-weight: bold; "
" border-radius: 6px; "
" font-size: 12px; "
" border: none; "
"} "
"QPushButton:hover { "
" background-color: #1976D2; "
"}"
);
snHeaderLayout->addWidget(searchLightStripBtn);
// 添加清除按钮
clearSnBtn = new QPushButton("清除列表", this);
clearSnBtn->setMinimumHeight(26);
clearSnBtn->setMinimumWidth(80);
clearSnBtn->setStyleSheet(
"QPushButton { "
" background-color: #f44336; "
" color: white; "
" padding: 4px 10px; "
" font-weight: bold; "
" border-radius: 6px; "
" font-size: 12px; "
" border: none; "
"} "
"QPushButton:hover { "
" background-color: #d32f2f; "
"}"
);
snHeaderLayout->addWidget(clearSnBtn);
snLayout->addLayout(snHeaderLayout);
// 添加snHorizontalLayout到snLayout中
snLayout->addLayout(snHorizontalLayout);
// 打开管理器按钮
openManagerBtn = new QPushButton("打开灯条SN管理器");
openManagerBtn->setMinimumHeight(32); // 减少按钮高度
openManagerBtn->setStyleSheet(
"QPushButton { "
" background-color: #2196F3; "
" color: white; "
" font-weight: bold; "
" font-size: 14px; "
" padding: 6px 16px; "
" border-radius: 8px; "
" border: none; "
" margin: 1px; "
"} "
"QPushButton:hover { "
" background-color: #1976D2; "
"} "
"QPushButton:pressed { "
" background-color: #1565C0; "
"}"
);
connect(openManagerBtn, &QPushButton::clicked, this, &MainWindow::openLightStripManager);
snLayout->addWidget(openManagerBtn);
mainLayout->addWidget(snGroup);
// 消息显示区域
messageGroup = new QGroupBox("消息日志", this);
QVBoxLayout *msgLayout = new QVBoxLayout(messageGroup);
messageDisplay = new QTextEdit();
messageDisplay->setReadOnly(true);
msgLayout->addWidget(messageDisplay);
mainLayout->addWidget(messageGroup);
// 状态栏
statusLabel = new QLabel("未连接");
statusBar()->addWidget(statusLabel);
// OTA升级区域
otaGroup = new QGroupBox("OTA升降级", this);
QVBoxLayout *otaLayout = new QVBoxLayout(otaGroup);
// 当前版本显示
QHBoxLayout *versionLayout = new QHBoxLayout();
QLabel *versionLabel = new QLabel("当前版本:");
versionLabel->setStyleSheet(QString("QLabel { color: %1; }").arg(textColor));
versionLayout->addWidget(versionLabel);
currentVersionEdit = new QLineEdit(this);
currentVersionEdit->setReadOnly(true);
versionLayout->addWidget(currentVersionEdit);
// 创建获取版本按钮
getVersionBtn = new QPushButton("获取版本", this);
versionLayout->addWidget(getVersionBtn);
otaLayout->addLayout(versionLayout);
// OTA按钮
QHBoxLayout *otaButtonLayout = new QHBoxLayout();
otaUpgradeBtn = new QPushButton("升级", this);
otaDowngradeBtn = new QPushButton("降级", this);
otaButtonLayout->addWidget(otaUpgradeBtn);
otaButtonLayout->addWidget(otaDowngradeBtn);
otaLayout->addLayout(otaButtonLayout);
// 下载进度条
otaProgressBar = new QProgressBar(this); // 修正变量名
otaProgressBar->setVisible(false);
otaLayout->addWidget(otaProgressBar);
// OTA状态标签
otaStatusLabel = new QLabel("就绪", this);
otaLayout->addWidget(otaStatusLabel);
mainLayout->addWidget(otaGroup);
// 在setupUI的最后调用菜单创建
createMenus();
}
void MainWindow::onConnectClicked() {
QString broker = brokerEdit->text().trimmed();
int portNum = portEdit->text().toInt();
QString username = usernameEdit->text().trimmed();
QString password = passwordEdit->text();
QString deviceSn = deviceSnEdit->text().trimmed();
if (broker.isEmpty()) {
QMessageBox::warning(this, "警告", "请输入MQTT服务器地址");
return;
}
if (portNum <= 0 || portNum > 65535) {
QMessageBox::warning(this, "警告", "请输入有效的端口号 (1-65535)");
return;
}
// 设置用户名和密码(如果有的话)
if (!username.isEmpty()) {
mqttClient->setUsername(username);
}
if (!password.isEmpty()) {
mqttClient->setPassword(password);
}
// 连接到MQTT服务器
mqttClient->connectToHost(broker, static_cast<quint16>(portNum));
// 更新状态
statusLabel->setText("正在连接...");
connectBtn->setEnabled(false);
// 注意订阅操作应该在连接成功后进行在onMqttConnected()函数中处理
}
void MainWindow::onDisconnectClicked()
{
mqttClient->disconnectFromHost();
}
void MainWindow::onSendLightAllClicked()
{
QString deviceSn = deviceSnEdit->text().trimmed();
if (deviceSn.isEmpty()) {
messageDisplay->append(QString("[%1] 错误: 请输入设备SN")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
return;
}
if (!mqttClient->isConnected()) {
messageDisplay->append(QString("[%1] 错误: MQTT未连接")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
return;
}
// 获取参数值
QString color = QString::number(colorCombo->currentIndex());
QString flash = QString::number(flashCombo->currentIndex());
QString flashInterval = QString::number(flashIntervalSpin->value());
QString lightDuration = QString::number(lightDurationSpin->value());
QString sound = QString::number(soundCombo->currentIndex());
// 构造消息内容
QJsonObject dataObj;
dataObj["color"] = color;
dataObj["flash"] = flash;
dataObj["flashInterval"] = flashInterval;
dataObj["lightDuration"] = lightDuration;
dataObj["sound"] = sound;
QJsonObject msgObj;
msgObj["data"] = dataObj;
msgObj["msgType"] = "3027";
QJsonObject rootObj;
rootObj["deviceId"] = deviceSn;
rootObj["messageId"] = "1933039995430551552";
// 修复将QByteArray转换为QString
rootObj["msg"] = QString::fromUtf8(QJsonDocument(msgObj).toJson(QJsonDocument::Compact));
rootObj["timestamp"] = 2147483647;
QString message = QJsonDocument(rootObj).toJson(QJsonDocument::Compact);
// 发送到指定主题
QString topic = QString("iot/10045/%1/message/adviceDevice").arg(deviceSn);
if (mqttClient->publish(topic, message)) {
messageDisplay->append(QString("[%1] 发送全部点亮指令到设备 %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(deviceSn));
messageDisplay->append(QString("主题: %1").arg(topic));
messageDisplay->append(QString("参数: 颜色=%1, 闪烁=%2, 间隔=%3s, 时长=%4s, 声音=%5")
.arg(colorCombo->currentText())
.arg(flashCombo->currentText())
.arg(flashInterval)
.arg(lightDuration)
.arg(soundCombo->currentText()));
} else {
messageDisplay->append(QString("[%1] 发送全部点亮指令失败")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
}
}
void MainWindow::onMqttConnected() {
qDebug() << "MQTT连接成功";
updateConnectionStatus(true);
// 连接成功后如果设备SN不为空则订阅主题
QString deviceSn = deviceSnEdit->text().trimmed();
if (!deviceSn.isEmpty()) {
qDebug() << "重新订阅";
resubscribeTopics();
}
}
void MainWindow::onMqttDisconnected()
{
messageDisplay->append(QString("[%1] MQTT连接断开")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
updateConnectionStatus(false);
}
void MainWindow::onMqttError(const QString &error)
{
messageDisplay->append(QString("[%1] MQTT错误: %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(error));
updateConnectionStatus(false);
}
void MainWindow::onMessageReceived(const QString &topic, const QString &message) {
// 显示接收到的消息
messageDisplay->append(QString("[%1] 收到消息").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
messageDisplay->append(QString("Topic: %1").arg(topic));
messageDisplay->append(QString("Message: %1").arg(message));
messageDisplay->append("---");
// 处理消息格式问题提取纯JSON部分
QString jsonMessage = message;
QString realTopic = topic;
// 如果topic是默认的incoming/topic尝试从消息中提取真实主题
if (topic == "incoming/topic" && message.contains("{")) {
// 查找消息中的主题信息(格式:%topic{json}
if (message.startsWith("%")) {
int jsonStart = message.indexOf("{");
if (jsonStart > 1) {
realTopic = message.mid(1, jsonStart - 1); // 去掉开头的%
messageDisplay->append(QString("[DEBUG] 从消息中提取到真实主题: %1").arg(realTopic));
}
}
}
// 去除开头的单引号
if (jsonMessage.startsWith("'")) {
jsonMessage = jsonMessage.mid(1);
}
// 去除结尾的单引号
if (jsonMessage.endsWith("'")) {
jsonMessage = jsonMessage.left(jsonMessage.length() - 1);
}
// 检查消息是否包含主题信息(格式:%topic{json}或topic{json}
if (jsonMessage.contains("{")) {
int jsonStart = jsonMessage.indexOf("{");
if (jsonStart != -1) {
jsonMessage = jsonMessage.mid(jsonStart);
}
}
// 解析JSON消息
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(jsonMessage.toUtf8(), &error);
if (error.error != QJsonParseError::NoError) {
messageDisplay->append(QString("[%1] JSON解析错误: %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(error.errorString()));
return;
}
QJsonObject root = doc.object();
messageDisplay->append(QString("[%1] JSON解析成功").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
// 检查是否包含msg字段优先处理
if (root.contains("msg")) {
messageDisplay->append(QString("[%1] 找到msg字段").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
QJsonValue msgValue = root["msg"];
QJsonObject msgObj;
// msg可能是字符串或对象
if (msgValue.isString()) {
QJsonDocument msgDoc = QJsonDocument::fromJson(msgValue.toString().toUtf8());
if (!msgDoc.isNull()) {
msgObj = msgDoc.object();
}
} else if (msgValue.isObject()) {
msgObj = msgValue.toObject();
messageDisplay->append(QString("[%1] msg是对象类型").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
}
// 解析data中的baseVersion
if (msgObj.contains("data")) {
messageDisplay->append(QString("[%1] 找到data字段").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
QJsonObject dataObj = msgObj["data"].toObject();
if (dataObj.contains("baseVersion")) {
QString version = dataObj["baseVersion"].toString();
messageDisplay->append(QString("[%1] 找到baseVersion: %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(version));
// 确保currentVersionEdit存在
if (currentVersionEdit) {
currentVersionEdit->setText(version);
messageDisplay->append(QString("[%1] 版本已设置到输入框: %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(version));
} else {
messageDisplay->append(QString("[%1] 错误: currentVersionEdit为空")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
}
statusLabel->setText(QString("获取到当前版本: %1").arg(version));
return;
}
}
}
// 检查是否包含resource字段直接在根对象中
if (root.contains("resource")) {
QString resource = root["resource"].toString();
QString version = resource.trimmed();
messageDisplay->append(QString("[%1] 找到resource字段: %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(version));
if (currentVersionEdit) {
currentVersionEdit->setText(version);
messageDisplay->append(QString("[%1] 版本已设置到输入框: %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(version));
}
statusLabel->setText(QString("获取到当前版本: %1").arg(version));
return;
}
// 检查是否是light/report主题使用提取的真实主题
if (realTopic.contains("/light/report")) {
messageDisplay->append("[DEBUG] 检测到light/report消息开始解析SN");
processLightReportMessage(jsonMessage);
}
// 检查是否是station/report主题且有待处理的OTA操作
if (otaOperationPending && (realTopic.contains("/station/report") || realTopic.contains("/resource/report"))) {
QString operationType = isUpgradeOperation ? "升级" : "降级";
QString successMessage = QString("OTA%1成功 版本: %2")
.arg(operationType)
.arg(pendingOtaVersion);
messageDisplay->append(QString("[%1] %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(successMessage));
// 重置OTA状态
otaOperationPending = false;
pendingOtaVersion.clear();
isUpgradeOperation = false;
statusLabel->setText(successMessage);
}
}
void MainWindow::processOtaMessage(const QJsonObject &otaData)
{
QString version = otaData["version"].toString();
QString zipPath = otaData["zipPath"].toString();
int installType = otaData["installType"].toInt();
int immediately = otaData["immediately"].toInt();
otaStatusLabel->setText(QString("收到OTA任务: 版本 %1").arg(version));
}
void MainWindow::onOtaUpgradeClicked() {
// 获取设备SN
QString deviceSn = deviceSnEdit->text().trimmed();
if (deviceSn.isEmpty()) {
QMessageBox::warning(this, "警告", "请先输入设备SN");
return;
}
// 设置OTA状态跟踪
otaOperationPending = true;
pendingOtaVersion = "1.1.41";
isUpgradeOperation = true;
// 构造符合您格式的OTA升级消息
QJsonObject data;
data["msgType"] = "1014";
QJsonObject otaData;
otaData["needWifi"] = 2;
otaData["zipPath"] = "http://180.163.74.83:8000/tx_ota_1.1.41.zip";
otaData["installType"] = 2;
otaData["immediately"] = 0;
otaData["installTime"] = QDateTime::currentMSecsSinceEpoch() + 300000; // 5分钟后安装
otaData["version"] = "1.1.41";
data["data"] = otaData;
QJsonObject root;
root["deviceId"] = deviceSn; // 使用输入的设备SN
root["header"] = QJsonValue::Null;
root["messageId"] = QString::number(QDateTime::currentMSecsSinceEpoch());
root["msg"] = QString::fromUtf8(QJsonDocument(data).toJson(QJsonDocument::Compact));
root["productId"] = "";
root["timestamp"] = QDateTime::currentMSecsSinceEpoch();
QJsonDocument doc(root);
QString message = QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
// 构建正确的MQTT主题
QString topic = QString("iot/10045/%1/message/adviceDevice").arg(deviceSn);
if (mqttClient && mqttClient->isConnected()) {
mqttClient->publish(topic, message);
statusLabel->setText(QString("已发送OTA升级命令到设备 %1 (版本 1.1.41)").arg(deviceSn));
// 显示发送的消息内容
QMessageBox::information(this, "OTA升级命令",
QString("已发送升级命令:\n设备SN: %1\n版本: 1.1.41\n主题: %2\n消息: %3")
.arg(deviceSn, topic, message));
} else {
statusLabel->setText("MQTT未连接无法发送OTA命令");
// 如果发送失败,重置状态
otaOperationPending = false;
}
}
void MainWindow::onOtaDowngradeClicked() {
// 获取设备SN
QString deviceSn = deviceSnEdit->text().trimmed();
if (deviceSn.isEmpty()) {
QMessageBox::warning(this, "警告", "请先输入设备SN");
return;
}
// 设置OTA状态跟踪
otaOperationPending = true;
pendingOtaVersion = "1.1.40";
isUpgradeOperation = false;
// 构造符合您格式的OTA降级消息
QJsonObject data;
data["msgType"] = "1014";
QJsonObject otaData;
otaData["needWifi"] = 2;
otaData["zipPath"] = "http://180.163.74.83:8000/tx_ota_1.1.40.zip";
otaData["installType"] = 2;
otaData["immediately"] = 0;
otaData["installTime"] = QDateTime::currentMSecsSinceEpoch() + 300000; // 5分钟后安装
otaData["version"] = "1.1.40";
data["data"] = otaData;
QJsonObject root;
root["deviceId"] = deviceSn; // 使用输入的设备SN
root["header"] = QJsonValue::Null;
root["messageId"] = QString::number(QDateTime::currentMSecsSinceEpoch());
root["msg"] = QString::fromUtf8(QJsonDocument(data).toJson(QJsonDocument::Compact));
root["productId"] = "";
root["timestamp"] = QDateTime::currentMSecsSinceEpoch();
QJsonDocument doc(root);
QString message = QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
// 构建正确的MQTT主题
QString topic = QString("iot/10045/%1/message/adviceDevice").arg(deviceSn);
if (mqttClient && mqttClient->isConnected()) {
mqttClient->publish(topic, message);
statusLabel->setText(QString("已发送OTA降级命令到设备 %1 (版本 1.1.40)").arg(deviceSn));
// 显示发送的消息内容
QMessageBox::information(this, "OTA降级命令",
QString("已发送降级命令:\n设备SN: %1\n版本: 1.1.40\n主题: %2\n消息: %3")
.arg(deviceSn, topic, message));
} else {
statusLabel->setText("MQTT未连接无法发送OTA命令");
// 如果发送失败,重置状态
otaOperationPending = false;
}
}
void MainWindow::onSearchLightStripClicked() {
sendSearchLightStripCommand();
}
void MainWindow::sendSearchLightStripCommand() {
QString deviceSn = deviceSnEdit->text().trimmed();
if (deviceSn.isEmpty()) {
messageDisplay->append(QString("[%1] 错误: 设备SN不能为空")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
return;
}
QString topic = QString("iot/10045/%1/message/adviceDevice").arg(deviceSn);
// 构造消息
QJsonObject message;
message["deviceId"] = "";
message["header"] = QJsonValue::Null;
message["messageId"] = "1958474134031921152";
message["productId"] = "";
message["timestamp"] = QDateTime::currentMSecsSinceEpoch();
// 构造msg字段
QJsonObject msgData;
msgData["scene"] = "1";
msgData["timeout"] = "120";
QJsonObject msgObj;
msgObj["data"] = msgData;
msgObj["msgType"] = "5005";
QJsonDocument msgDoc(msgObj);
message["msg"] = QString::fromUtf8(msgDoc.toJson(QJsonDocument::Compact));
QJsonDocument doc(message);
QString jsonString = doc.toJson(QJsonDocument::Compact);
// 发送消息
if (mqttClient->publish(topic, jsonString)) {
messageDisplay->append(QString("[%1] 发送搜索灯条命令")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
messageDisplay->append(QString("Topic: %1").arg(topic));
messageDisplay->append(QString("Message: %1").arg(jsonString));
messageDisplay->append("---");
// 弹出提示框
QMessageBox::information(this, "搜索灯条", "不要做其他指令正在搜索灯条请等待2分钟返回结果...");
} else {
messageDisplay->append(QString("[%1] 发送搜索灯条命令失败")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
messageDisplay->append("---");
// 弹出错误提示框
QMessageBox::warning(this, "错误", "发送搜索灯条命令失败,请检查网络连接");
}
}
void MainWindow::sendOtaCommand(const QString& version, bool isUpgrade) {
QJsonObject msg;
msg["action"] = isUpgrade ? "upgrade" : "downgrade";
msg["version"] = version;
QJsonObject root;
root["type"] = "ota_command";
root["msg"] = QString::fromUtf8(QJsonDocument(msg).toJson(QJsonDocument::Compact));
QJsonDocument doc(root);
QString message = QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
if (mqttClient && mqttClient->isConnected()) {
mqttClient->publish("device/ota/command", message);
statusLabel->setText(QString("已发送OTA%1命令 (版本 %2)").arg(isUpgrade ? "升级" : "降级", version));
} else {
statusLabel->setText("MQTT未连接无法发送OTA命令");
}
}
void MainWindow::onGetVersionClicked() {
// 获取设备SN
QString deviceSn = deviceSnEdit->text().trimmed();
if (deviceSn.isEmpty()) {
QMessageBox::warning(this, "警告", "请先输入设备SN");
return;
}
// 构造获取版本的消息
QJsonObject data;
data["msgType"] = "2335";
QJsonObject cmdData;
cmdData["cmd"] = "cat /userdata/tx_version";
data["data"] = cmdData;
QJsonObject root;
root["deviceId"] = deviceSn;
root["header"] = QJsonValue::Null;
root["messageId"] = QString::number(QDateTime::currentMSecsSinceEpoch());
root["msg"] = QString::fromUtf8(QJsonDocument(data).toJson(QJsonDocument::Compact));
root["productId"] = "";
root["timestamp"] = QDateTime::currentMSecsSinceEpoch();
QJsonDocument doc(root);
QString message = QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
// 使用正确的发送主题
QString topic = QString("iot/10045/%1/message/adviceDevice").arg(deviceSn);
if (mqttClient && mqttClient->isConnected()) {
mqttClient->publish(topic, message);
statusLabel->setText(QString("已发送获取版本命令到设备 %1").arg(deviceSn));
} else {
statusLabel->setText("MQTT未连接无法发送命令");
}
}
void MainWindow::onDeviceSnChanged()
{
// 只有在MQTT连接状态下才重新订阅主题
if (mqttClient && mqttClient->isConnected()) {
resubscribeTopics();
}
}
void MainWindow::resubscribeTopics()
{
if (!mqttClient || !mqttClient->isConnected()) {
return;
}
QString deviceSn = deviceSnEdit->text().trimmed();
if (deviceSn.isEmpty()) {
qDebug() << "设备SN为空不进行订阅";
return;
}
qDebug() << "重新订阅主题设备SN:" << deviceSn;
// 订阅相关主题
mqttClient->subscribe(QString("iot/10045/%1/light/report").arg(deviceSn));
mqttClient->subscribe(QString("iot/10045/%1/station/report").arg(deviceSn));
mqttClient->subscribe(QString("iot/10045/%1/version/report").arg(deviceSn));
mqttClient->subscribe(QString("iot/10045/%1/response").arg(deviceSn));
mqttClient->subscribe(QString("iot/10045/%1/resource/report").arg(deviceSn));
}
void MainWindow::updateConnectionStatus(bool connected)
{
connectBtn->setEnabled(!connected);
disconnectBtn->setEnabled(connected);
sendLightAllBtn->setEnabled(connected);
// 控制设备SN输入框只有连接成功后才能输入
deviceSnEdit->setEnabled(connected);
// 新增:连接成功后禁用连接参数输入框,断开后重新启用
usernameEdit->setEnabled(!connected);
portEdit->setEnabled(!connected);
passwordEdit->setEnabled(!connected);
brokerEdit->setEnabled(!connected); // 同时也禁用服务器地址输入框
// 修改连接按钮样式:连接成功后设置灰色背景,断开后恢复绿色
if (connected) {
connectBtn->setStyleSheet("QPushButton { background-color: #cccccc; color: #666666; font-weight: bold; padding: 8px 16px; border-radius: 4px; }");
statusLabel->setText("已连接");
statusLabel->setStyleSheet("color: green;");
} else {
connectBtn->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; padding: 8px 16px; border-radius: 4px; }");
statusLabel->setText("未连接");
statusLabel->setStyleSheet("color: red;");
}
}
void MainWindow::processLightReportMessage(const QString &message) {
messageDisplay->append(QString("[DEBUG] 开始处理light/report消息: %1").arg(message));
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error != QJsonParseError::NoError) {
messageDisplay->append(QString("[ERROR] JSON解析失败: %1").arg(error.errorString()));
return;
}
QJsonObject rootObj = doc.object();
messageDisplay->append(QString("[DEBUG] 根对象键: %1").arg(QStringList(rootObj.keys()).join(", ")));
QJsonObject msgObj = rootObj["msg"].toObject();
messageDisplay->append(QString("[DEBUG] msg对象键: %1").arg(QStringList(msgObj.keys()).join(", ")));
QJsonObject dataObj = msgObj["data"].toObject();
messageDisplay->append(QString("[DEBUG] data对象键: %1").arg(QStringList(dataObj.keys()).join(", ")));
QJsonArray lightsArray = dataObj["lights"].toArray();
messageDisplay->append(QString("[DEBUG] lights数组长度: %1").arg(lightsArray.size()));
int newSnCount = 0;
for (const QJsonValue &lightValue : lightsArray) {
QJsonObject lightObj = lightValue.toObject();
QString sn = lightObj["sn"].toString();
QString battery = lightObj["battery"].toString();
messageDisplay->append(QString("[DEBUG] 处理灯条SN: %1, 电量: %2").arg(sn, battery));
if (!sn.isEmpty() && !uniqueSnSet.contains(sn)) {
messageDisplay->append(QString("[DEBUG] 添加新SN到列表: %1").arg(sn));
addSnToList(sn);
newSnCount++;
} else if (sn.isEmpty()) {
messageDisplay->append("[DEBUG] SN为空跳过");
} else if (uniqueSnSet.contains(sn)) {
messageDisplay->append(QString("[DEBUG] SN已存在跳过: %1").arg(sn));
}
// 更新电量信息到LightStripManager
if (!sn.isEmpty() && !battery.isEmpty() && lightStripManager) {
lightStripManager->updateBatteryInfo(sn, battery);
messageDisplay->append(QString("[DEBUG] 更新电量信息: SN=%1, 电量=%2").arg(sn, battery));
}
}
if (newSnCount > 0) {
messageDisplay->append(QString("[INFO] 新发现 %1 个灯条SN").arg(newSnCount));
saveSnList();
} else {
messageDisplay->append("[DEBUG] 没有发现新的灯条SN");
}
}
void MainWindow::addSnToList(const QString &sn)
{
if (uniqueSnSet.contains(sn)) {
return;
}
uniqueSnSet.insert(sn);
// 更新主窗口的计数显示
snCountLabel->setText(QString("已发现灯条: %1 个").arg(uniqueSnSet.size()));
// 如果灯条管理器已打开同步添加但不重复更新MainWindow的计数
if (lightStripManager) {
// 临时断开信号连接,避免重复更新
disconnect(lightStripManager, &LightStripManager::snCountChanged, this, nullptr);
lightStripManager->addSnToList(sn);
// 重新连接信号
connect(lightStripManager, &LightStripManager::snCountChanged,
this, [this](int count) {
snCountLabel->setText(QString("已发现灯条: %1 个").arg(count));
});
}
// 移除这里的saveSnList()调用,让调用方决定何时保存
// saveSnList();
}
void MainWindow::saveSnList() {
QStringList snList = uniqueSnSet.values();
settings->setValue("lightStripSnList", snList);
settings->sync();
}
void MainWindow::loadSnList() {
QStringList snList = settings->value("lightStripSnList").toStringList();
for (const QString &sn : snList) {
addSnToList(sn);
}
}
void MainWindow::onClearSnListClicked()
{
// 检查snHorizontalLayout是否存在避免段错误
if (snHorizontalLayout) {
// 清空所有灯条widget
QLayoutItem *item;
while ((item = snHorizontalLayout->takeAt(0)) != nullptr) {
if (item->widget()) {
delete item->widget();
}
delete item;
}
}
// 清空复选框列表和SN集合
lightStripCheckBoxes.clear();
uniqueSnSet.clear();
// 更新计数显示
if (snCountLabel) {
snCountLabel->setText("已发现灯条: 0 个");
}
// 不再调用saveSnList()让LightStripManager管理数据
// saveSnList(); // 删除这行
// 如果LightStripManager存在调用公有的清空方法
if (lightStripManager) {
lightStripManager->clearAllData();
}
}
void MainWindow::openLightStripManager()
{
if (!lightStripManager) {
lightStripManager = new LightStripManager(this);
// 连接信号
connect(lightStripManager, &LightStripManager::lightControlRequested,
this, [this](const QStringList &sns, const QString &color, bool flash, int interval, int duration, bool sound) {
// 处理点亮控制请求
// 实现MQTT发送逻辑
// 检查MQTT连接状态
if (!mqttClient->isConnected()) {
qDebug() << "MQTT未连接无法发送灯条控制命令";
QMessageBox::warning(this, "警告", "MQTT未连接请先连接MQTT服务器");
return;
}
// 获取设备SN从界面获取
QString deviceSn = deviceSnEdit->text().trimmed();
if (deviceSn.isEmpty()) {
QMessageBox::warning(this, "警告", "请先输入需要测试的设备SN");
return;
}
// 构建内层msg数据
QJsonObject msgData;
QRegularExpression re("\\d+");
QRegularExpressionMatch match = re.match(color);
if (match.hasMatch()) {
msgData["color"] = match.captured(0); // 提取到的数字字符串
} else {
msgData["color"] = "0"; // 默认值
}
msgData["flash"] = flash ? "1" : "0";
msgData["flashInterval"] = QString::number(interval);
msgData["lightDuration"] = QString::number(duration);
msgData["scene"] = "3"; // 根据业务需求设置
msgData["sound"] = sound ? "1" : "0";
// 构建lights数组
QJsonArray lightsArray;
for (const QString &sn : sns) {
QJsonObject lightObj;
lightObj["sn"] = sn;
lightsArray.append(lightObj);
}
msgData["lights"] = lightsArray;
// 构建完整的msg对象
QJsonObject fullMsg;
fullMsg["data"] = msgData;
fullMsg["msgType"] = "3015"; // 根据业务协议设置
// 转换msg为JSON字符串
QJsonDocument msgDoc(fullMsg);
QString msgString = msgDoc.toJson(QJsonDocument::Compact);
// 构建最外层消息
QJsonObject outerMessage;
outerMessage["deviceId"] = ""; // 根据需要设置
outerMessage["messageId"] = QString::number(QDateTime::currentMSecsSinceEpoch()); // 生成唯一消息ID
outerMessage["msg"] = msgString;
outerMessage["timestamp"] = QDateTime::currentMSecsSinceEpoch();
// 转换为最终JSON字符串
QJsonDocument finalDoc(outerMessage);
QString finalMessage = finalDoc.toJson(QJsonDocument::Compact);
// 构建MQTT主题
QString topic = QString("iot/10045/%1/message/adviceDevice").arg(deviceSn);
// 发送MQTT消息
bool success = mqttClient->publish(topic, finalMessage);
if (success) {
qDebug() << "成功发送灯条控制命令";
qDebug() << "主题:" << topic;
qDebug() << "消息:" << finalMessage;
// 显示成功提示
QString resultMessage = QString("已向设备 %1 发送控制命令,控制 %2 个灯条")
.arg(deviceSn)
.arg(sns.size());
QMessageBox::information(this, "成功", resultMessage);
} else {
qDebug() << "发送灯条控制命令失败";
QMessageBox::warning(this, "错误", "发送MQTT消息失败请检查网络连接");
}
});
connect(lightStripManager, &LightStripManager::snSelectionChanged,
this, [this](const QStringList &selectedSns) {
// 处理选择变化
qDebug() << "Selected SNs changed:" << selectedSns;
});
// 新增:连接数量变化信号
connect(lightStripManager, &LightStripManager::snCountChanged,
this, [this](int count) {
// 同步更新MainWindow中的灯条数量显示
snCountLabel->setText(QString("已发现灯条: %1 个").arg(count));
});
// 新增:连接身份信息绑定信号
connect(lightStripManager, &LightStripManager::identityBindingRequested,
this, [this](const QString &sn, const QString &label1, const QString &label2, const QString &label3) {
// 处理身份信息绑定请求
// 检查MQTT连接状态
if (!mqttClient->isConnected()) {
qDebug() << "MQTT未连接无法发送身份信息绑定命令";
QMessageBox::warning(this, "警告", "MQTT未连接请先连接MQTT服务器");
return;
}
// 获取设备SN从界面获取
QString deviceSn = deviceSnEdit->text().trimmed();
if (deviceSn.isEmpty()) {
QMessageBox::warning(this, "警告", "请先输入需要测试的设备SN");
return;
}
// 构建身份信息绑定消息
QJsonObject msgData;
msgData["sn"] = sn;
msgData["label1"] = label1;
msgData["label2"] = label2;
msgData["label3"] = label3;
// 构建完整的msg对象
QJsonObject fullMsg;
fullMsg["data"] = msgData;
fullMsg["msgType"] = "3022"; // 身份信息绑定消息类型
// 转换msg为JSON字符串
QJsonDocument msgDoc(fullMsg);
QString msgString = msgDoc.toJson(QJsonDocument::Compact);
// 构建最外层消息
QJsonObject outerMessage;
outerMessage["deviceId"] = "";
outerMessage["messageId"] = QString::number(QDateTime::currentMSecsSinceEpoch());
outerMessage["msg"] = msgString;
outerMessage["timestamp"] = QDateTime::currentMSecsSinceEpoch();
// 转换为最终JSON字符串
QJsonDocument finalDoc(outerMessage);
QString finalMessage = finalDoc.toJson(QJsonDocument::Compact);
// 构建MQTT主题
QString topic = QString("iot/10045/%1/message/adviceDevice").arg(deviceSn);
// 发送MQTT消息
bool success = mqttClient->publish(topic, finalMessage);
if (success) {
qDebug() << "成功发送身份信息绑定命令";
qDebug() << "主题:" << topic;
qDebug() << "消息:" << finalMessage;
// 在消息显示区域显示发送的消息
messageDisplay->append(QString("[%1] 发送身份信息绑定到设备 %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(deviceSn));
messageDisplay->append(QString("主题: %1").arg(topic));
messageDisplay->append(QString("灯条SN: %1, Label1: %2, Label2: %3, Label3: %4")
.arg(sn).arg(label1).arg(label2).arg(label3));
} else {
qDebug() << "发送身份信息绑定命令失败";
QMessageBox::warning(this, "错误", "发送MQTT消息失败请检查网络连接");
}
});
// 新增:连接分组点亮信号
connect(lightStripManager, &LightStripManager::groupLightRequested,
this, [this](const QString &label1, const QString &label2, const QString &label3,
int rule1, int rule2, int rule3, const QString &color, int flash, int duration, bool sound, int flashInterval) {
// 检查MQTT连接状态
if (!mqttClient->isConnected()) {
qDebug() << "MQTT未连接无法发送分组点亮命令";
QMessageBox::warning(this, "警告", "MQTT未连接请先连接MQTT服务器");
return;
}
// 获取设备SN
QString deviceSn = deviceSnEdit->text().trimmed();
if (deviceSn.isEmpty()) {
QMessageBox::warning(this, "警告", "请先输入需要测试的设备SN");
return;
}
// 构建labelConfig对象
QJsonObject labelConfig;
labelConfig["label1"] = label1;
labelConfig["label2"] = label2;
labelConfig["label3"] = label3;
labelConfig["label1Rule"] = QString("%1").arg(rule1, 2, 10, QChar('0'));
labelConfig["label2Rule"] = QString("%1").arg(rule2, 2, 10, QChar('0'));
labelConfig["label3Rule"] = QString("%1").arg(rule3, 2, 10, QChar('0'));
// 构建分组点亮消息数据
QJsonObject msgData;
msgData["labelConfig"] = labelConfig;
msgData["color"] = color;
msgData["sound"] = sound ? "1" : "0";
msgData["flash"] = QString::number(flash);
msgData["flashInterval"] = QString::number(flashInterval);
msgData["lightDuration"] = QString::number(duration);
// 构建完整的msg对象
QJsonObject fullMsg;
fullMsg["msgType"] = "3023";
fullMsg["data"] = msgData;
// 转换msg为JSON字符串
QJsonDocument msgDoc(fullMsg);
QString msgString = msgDoc.toJson(QJsonDocument::Compact);
// 构建最外层消息
QJsonObject outerMessage;
outerMessage["deviceId"] = "";
outerMessage["messageId"] = QString::number(QDateTime::currentMSecsSinceEpoch());
outerMessage["msg"] = msgString;
outerMessage["timestamp"] = QDateTime::currentMSecsSinceEpoch();
// 转换为最终JSON字符串
QJsonDocument finalDoc(outerMessage);
QString finalMessage = finalDoc.toJson(QJsonDocument::Compact);
// 构建MQTT主题
QString topic = QString("iot/10045/%1/message/adviceDevice").arg(deviceSn);
// 发送MQTT消息
bool success = mqttClient->publish(topic, finalMessage);
if (success) {
qDebug() << "成功发送分组点亮命令";
qDebug() << "主题:" << topic;
qDebug() << "消息:" << finalMessage;
// 在消息显示区域显示发送的消息
messageDisplay->append(QString("[%1] 发送分组点亮命令到设备 %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(deviceSn));
messageDisplay->append(QString("主题: %1").arg(topic));
messageDisplay->append(QString("匹配规则: Label1=%1(rule:%2), Label2=%3(rule:%4), Label3=%5(rule:%6)")
.arg(label1).arg(rule1, 2, 10, QChar('0'))
.arg(label2).arg(rule2, 2, 10, QChar('0'))
.arg(label3).arg(rule3, 2, 10, QChar('0')));
} else {
qDebug() << "发送分组点亮命令失败";
QMessageBox::warning(this, "错误", "发送MQTT消息失败请检查网络连接");
}
});
// 连接关闭信号
connect(lightStripManager, &QWidget::destroyed,
this, &MainWindow::onLightStripManagerClosed);
// 同步当前的SN列表到管理器
QStringList snList = uniqueSnSet.values();
lightStripManager->syncSnListFromMainWindow(snList);
}
lightStripManager->show();
lightStripManager->raise();
lightStripManager->activateWindow();
}
void MainWindow::onLightStripManagerClosed()
{
// 灯条管理器关闭时的处理
lightStripManager = nullptr;
qDebug() << "Light strip manager closed";
}
QString MainWindow::getDeviceSn() const
{
return deviceSnEdit->text().trimmed();
}
bool MainWindow::isMqttConnected() const
{
return mqttClient && mqttClient->isConnected();
}
void MainWindow::publishMqttMessage(const QString &topic, const QString &message)
{
if (mqttClient && mqttClient->isConnected()) {
mqttClient->publish(topic, message);
messageDisplay->append(QString("[%1] 发送消息到主题: %2")
.arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
.arg(topic));
} else {
QMessageBox::warning(this, "错误", "MQTT未连接无法发送消息");
}
}
void MainWindow::createMenus()
{
// 直接创建菜单,不使用成员变量
QMenu *helpMenu = menuBar()->addMenu("帮助(&H)");
QAction *aboutAction = helpMenu->addAction("关于程序(&A)");
connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout);
QAction *useGuideAction = helpMenu->addAction("使用说明(&U)");
connect(useGuideAction, &QAction::triggered, this, &MainWindow::showUseGuide);
// 添加分隔线
helpMenu->addSeparator();
// 添加手动检查更新菜单项
QAction *checkUpdateAction = helpMenu->addAction("检查更新(&C)");
connect(checkUpdateAction, &QAction::triggered, this, &MainWindow::checkForUpdates);
// 添加更新设置菜单项
QAction *updateSettingsAction = helpMenu->addAction("更新设置(&S)");
connect(updateSettingsAction, &QAction::triggered, this, &MainWindow::onUpdateSettingsClicked);
}
void MainWindow::showAbout()
{
QMessageBox::about(this, "关于程序",
"兔喜MQTT测试程序\n\n"
"版本: 1.5.2\n"
"构建日期: 2025-09-12\n\n"
"功能特性:\n"
"• 新增灯条备份恢复和程序ota升级功能\n"
"• 修复清空全部sn未生效的问题\n"
"• 修复label匹配值错误的问题\n"
"• 匹配系统颜色。修复浅色模式下字体看不清\n"
"• 增加窗口版本显示\n");
}
void MainWindow::showUseGuide()
{
QMessageBox::about(this, "使用说明",
"切换主题后看不清文字,请重启应用!!\n"
"1. 连接MQTT服务器(用户名默认TJ251679787196)\n"
"2. 输入需要测试的设备SN\n"
"3. 首次使用先升级版本1.1.16及之前的版本可能不支持升级\n"
"4. 可以正常操作其他指令查询版本、全部点亮、搜索灯带、灯带SN管理\n\n"
);
}
// 新增:检测系统主题并返回适合的文字颜色
QString MainWindow::getTextColorForTheme() const {
// 获取系统调色板
QPalette palette = QApplication::palette();
// 检查窗口背景色的亮度来判断是否为深色主题
QColor backgroundColor = palette.color(QPalette::Window);
// 计算亮度 (使用相对亮度公式)
double luminance = (0.299 * backgroundColor.red() +
0.587 * backgroundColor.green() +
0.114 * backgroundColor.blue()) / 255.0;
// 如果背景较暗亮度小于0.5),使用白色文字;否则使用黑色文字
return (luminance < 0.5) ? "white" : "black";
}
// 更新功能实现
void MainWindow::checkForUpdates()
{
if (updateCheckReply) {
updateCheckReply->abort();
updateCheckReply = nullptr;
}
QNetworkRequest request{QUrl(updateServerUrl)};
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
updateCheckReply = updateNetworkManager->get(request);
connect(updateCheckReply, &QNetworkReply::finished, this, &MainWindow::onUpdateCheckFinished);
}
void MainWindow::onUpdateCheckFinished()
{
if (!updateCheckReply) return;
if (updateCheckReply->error() == QNetworkReply::NoError) {
QByteArray data = updateCheckReply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject obj = doc.object();
QString latestVersion = obj["version"].toString();
QString changelog = obj["changelog"].toString();
// 根据平台选择合适的下载URL
QString downloadUrl;
#ifdef Q_OS_WIN
downloadUrl = obj["downloadUrlWindows"].toString();
if (downloadUrl.isEmpty()) {
// 如果没有Windows专用URL回退到通用URL
downloadUrl = obj["downloadUrl"].toString();
}
#else
downloadUrl = obj["downloadUrl"].toString();
if (downloadUrl.isEmpty()) {
// 如果没有Linux专用URL尝试Windows URL作为备选
downloadUrl = obj["downloadUrlWindows"].toString();
}
#endif
if (compareVersions(currentVersion, latestVersion) < 0) {
showUpdateDialog(latestVersion, downloadUrl, changelog);
} else {
QMessageBox::information(this, "检查更新", "当前已是最新版本!");
}
} else {
QMessageBox::warning(this, "检查更新失败",
QString("无法连接到更新服务器:%1").arg(updateCheckReply->errorString()));
}
updateCheckReply->deleteLater();
updateCheckReply = nullptr;
}
void MainWindow::showUpdateDialog(const QString &version, const QString &downloadUrl, const QString &changelog)
{
QMessageBox msgBox(this);
msgBox.setWindowTitle("发现新版本");
msgBox.setText(QString("发现新版本 %1").arg(version));
msgBox.setDetailedText(changelog);
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
QAbstractButton *yesButton = msgBox.button(QMessageBox::Yes);
QAbstractButton *noButton = msgBox.button(QMessageBox::No);
if (yesButton) yesButton->setText("立即更新");
if (noButton) noButton->setText("稍后更新");
if (msgBox.exec() == QMessageBox::Yes) {
downloadUpdate(downloadUrl);
}
}
void MainWindow::downloadUpdate(const QString &downloadUrl)
{
if (updateDownloadReply) {
updateDownloadReply->abort();
updateDownloadReply = nullptr;
}
QNetworkRequest request{QUrl(downloadUrl)};
updateDownloadReply = updateNetworkManager->get(request);
connect(updateDownloadReply, &QNetworkReply::downloadProgress,
this, &MainWindow::onUpdateDownloadProgress);
connect(updateDownloadReply, &QNetworkReply::finished,
this, &MainWindow::onUpdateDownloadFinished);
// 显示下载进度对话框
QProgressDialog *progressDialog = new QProgressDialog("正在下载更新...", "取消", 0, 100, this);
progressDialog->setWindowModality(Qt::WindowModal);
progressDialog->show();
connect(progressDialog, &QProgressDialog::canceled, [this]() {
if (updateDownloadReply) {
updateDownloadReply->abort();
}
});
}
void MainWindow::onUpdateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
if (bytesTotal > 0) {
int progress = static_cast<int>((bytesReceived * 100) / bytesTotal);
// 更新进度条
QProgressDialog *progressDialog = findChild<QProgressDialog*>();
if (progressDialog) {
progressDialog->setValue(progress);
}
}
}
void MainWindow::onUpdateDownloadFinished()
{
if (!updateDownloadReply) return;
QProgressDialog *progressDialog = findChild<QProgressDialog*>();
if (progressDialog) {
progressDialog->close();
progressDialog->deleteLater();
}
if (updateDownloadReply->error() == QNetworkReply::NoError) {
// 保存下载的文件
QByteArray data = updateDownloadReply->readAll();
QString fileName = QUrl(updateDownloadReply->url()).fileName();
// 确保文件名不为空
if (fileName.isEmpty()) {
fileName = "QtDemo.exe"; // 默认文件名
}
downloadedUpdatePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + fileName;
// 如果文件已存在,先删除
if (QFile::exists(downloadedUpdatePath)) {
QFile::remove(downloadedUpdatePath);
}
QFile file(downloadedUpdatePath);
if (file.open(QIODevice::WriteOnly)) {
qint64 bytesWritten = file.write(data);
file.close();
// 验证文件是否完整写入
if (bytesWritten != data.size()) {
QMessageBox::critical(this, "下载失败",
QString("文件写入不完整\n期望:%1 字节\n实际:%2 字节")
.arg(data.size()).arg(bytesWritten));
QFile::remove(downloadedUpdatePath); // 清理不完整的文件
return;
}
// 验证下载的文件是否存在且可读
QFileInfo fileInfo(downloadedUpdatePath);
if (!fileInfo.exists() || !fileInfo.isReadable()) {
QMessageBox::critical(this, "下载失败",
QString("下载的文件无法访问:%1").arg(downloadedUpdatePath));
return;
}
QMessageBox msgBox(this);
msgBox.setWindowTitle("下载完成");
msgBox.setText(QString("更新包下载完成(%1 MB是否立即安装")
.arg(QString::number(fileInfo.size() / 1024.0 / 1024.0, 'f', 2)));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
QAbstractButton *yesButton = msgBox.button(QMessageBox::Yes);
QAbstractButton *noButton = msgBox.button(QMessageBox::No);
if (yesButton) yesButton->setText("立即安装");
if (noButton) noButton->setText("稍后安装");
if (msgBox.exec() == QMessageBox::Yes) {
installUpdate();
}
} else {
QMessageBox::critical(this, "下载失败",
QString("无法保存更新文件到:%1\n错误:%2")
.arg(downloadedUpdatePath)
.arg(file.errorString()));
}
} else {
QMessageBox::warning(this, "下载失败",
QString("下载更新失败:%1").arg(updateDownloadReply->errorString()));
}
updateDownloadReply->deleteLater();
updateDownloadReply = nullptr;
}
void MainWindow::installUpdate()
{
if (downloadedUpdatePath.isEmpty() || !QFile::exists(downloadedUpdatePath)) {
QMessageBox::warning(this, "安装失败", "找不到更新文件!");
return;
}
// 根据文件类型执行不同的安装逻辑
if (downloadedUpdatePath.endsWith(".deb")) {
// Debian包安装
QProcess::startDetached("pkexec", QStringList() << "dpkg" << "-i" << downloadedUpdatePath);
} else if (downloadedUpdatePath.endsWith(".rpm")) {
// RPM包安装
QProcess::startDetached("pkexec", QStringList() << "rpm" << "-i" << downloadedUpdatePath);
} else if (downloadedUpdatePath.endsWith(".AppImage")) {
// AppImage替换
QString currentPath = QCoreApplication::applicationFilePath();
QString backupPath = currentPath + ".backup";
// 备份当前程序
if (!QFile::copy(currentPath, backupPath)) {
QString errorDetail = QFile(currentPath).errorString();
if (errorDetail.isEmpty()) {
errorDetail = "Unknown error";
}
// 在Windows上可能是因为文件正在使用中
#ifdef Q_OS_WIN
// 尝试检查文件是否被锁定
QFile testFile(currentPath);
if (!testFile.open(QIODevice::ReadOnly)) {
errorDetail += "\n可能原因:程序文件正在使用中,请确保没有其他实例在运行";
} else {
testFile.close();
// 检查目标目录权限
QFileInfo dirInfo(QFileInfo(backupPath).absolutePath());
if (!dirInfo.isWritable()) {
errorDetail += "\n可能原因:目标目录没有写入权限,请以管理员身份运行";
}
}
#endif
QMessageBox::warning(this, "安装失败", QString("无法备份当前程序:%1").arg(errorDetail));
return;
}
// 替换程序文件
QFile::remove(currentPath);
QFile::copy(downloadedUpdatePath, currentPath);
QFile::setPermissions(currentPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
QFile::ReadGroup | QFile::ExeGroup |
QFile::ReadOther | QFile::ExeOther);
QMessageBox::information(this, "安装完成", "更新安装完成,请重启程序!");
QApplication::quit();
} else {
// 普通可执行文件替换如QtDemo
QString currentPath = QCoreApplication::applicationFilePath();
QString currentDir = QFileInfo(currentPath).absolutePath();
QString currentFileName = QFileInfo(currentPath).fileName();
QString backupPath = currentPath + ".backup";
QString tempPath = currentPath + ".new";
// 备份当前程序
#ifdef Q_OS_WIN
// Windows平台正在运行的exe文件被锁定需要特殊处理
// 先检查备份文件是否已存在,如果存在则删除
if (QFile::exists(backupPath)) {
if (!QFile::remove(backupPath)) {
QMessageBox::warning(this, "安装失败",
QString("无法删除旧的备份文件:%1\n错误:%2")
.arg(backupPath)
.arg(QFile(backupPath).errorString()));
return;
}
}
// 在Windows上尝试使用系统命令复制正在运行的文件
QProcess copyProcess;
QString copyCmd = QString("copy /Y \"%1\" \"%2\"")
.arg(QDir::toNativeSeparators(currentPath))
.arg(QDir::toNativeSeparators(backupPath));
copyProcess.start("cmd.exe", QStringList() << "/c" << copyCmd);
copyProcess.waitForFinished(5000);
if (copyProcess.exitCode() != 0 || !QFile::exists(backupPath)) {
// 如果系统命令也失败尝试使用Qt的方式但先尝试解锁文件
QFile currentFile(currentPath);
if (!currentFile.copy(backupPath)) {
QMessageBox::warning(this, "安装失败",
QString("无法备份当前程序\n当前程序:%1\n备份位置:%2\n\n这可能是因为程序正在运行被系统锁定。\n建议:\n1. 关闭所有程序实例\n2. 以管理员身份运行\n3. 或手动复制程序文件进行备份")
.arg(currentPath)
.arg(backupPath));
return;
}
}
#else
// Linux/Unix平台的备份逻辑
// 先检查备份文件是否已存在,如果存在则删除
if (QFile::exists(backupPath)) {
if (!QFile::remove(backupPath)) {
QMessageBox::warning(this, "安装失败",
QString("无法删除旧的备份文件:%1\n错误:%2")
.arg(backupPath)
.arg(QFile(backupPath).errorString()));
return;
}
}
if (!QFile::copy(currentPath, backupPath)) {
QString errorDetail = QFile(currentPath).errorString();
if (errorDetail.isEmpty()) {
errorDetail = "Unknown error";
}
// 添加Linux平台的详细诊断信息
QFileInfo currentFileInfo(currentPath);
QFileInfo backupDirInfo(QFileInfo(backupPath).absolutePath());
QString diagnosticInfo = QString("\n\n诊断信息:\n");
diagnosticInfo += QString("- 当前程序路径:%1\n").arg(currentPath);
diagnosticInfo += QString("- 备份路径:%1\n").arg(backupPath);
diagnosticInfo += QString("- 当前程序是否存在:%1\n").arg(currentFileInfo.exists() ? "" : "");
diagnosticInfo += QString("- 当前程序是否可读:%1\n").arg(currentFileInfo.isReadable() ? "" : "");
diagnosticInfo += QString("- 备份目录是否可写:%1\n").arg(backupDirInfo.isWritable() ? "" : "");
diagnosticInfo += QString("- 当前程序大小:%1 字节\n").arg(currentFileInfo.size());
// 尝试更详细的错误检查
QFile sourceFile(currentPath);
QFile targetFile(backupPath);
// 检查源文件是否可以打开
if (!sourceFile.open(QIODevice::ReadOnly)) {
diagnosticInfo += QString("- 源文件打开失败:%1\n").arg(sourceFile.errorString());
} else {
sourceFile.close();
diagnosticInfo += "- 源文件可以正常打开\n";
}
// 检查目标文件是否可以创建
if (!targetFile.open(QIODevice::WriteOnly)) {
diagnosticInfo += QString("- 目标文件创建失败:%1\n").arg(targetFile.errorString());
} else {
targetFile.close();
targetFile.remove(); // 清理测试文件
diagnosticInfo += "- 目标文件可以正常创建\n";
}
// 检查是否有权限问题
if (!backupDirInfo.isWritable()) {
diagnosticInfo += "\n可能的解决方案:\n";
diagnosticInfo += "1. 检查目录写入权限\n";
diagnosticInfo += "2. 尝试以sudo权限运行程序\n";
diagnosticInfo += "3. 更改程序安装目录的权限";
}
// 检查磁盘空间
QStorageInfo storage(backupDirInfo.absolutePath());
if (storage.isValid()) {
qint64 availableBytes = storage.bytesAvailable();
qint64 requiredBytes = currentFileInfo.size();
diagnosticInfo += QString("- 可用磁盘空间:%1 字节\n").arg(availableBytes);
if (availableBytes < requiredBytes) {
diagnosticInfo += "\n错误:磁盘空间不足!";
}
}
// 尝试使用系统命令作为备选方案
diagnosticInfo += "\n尝试使用系统命令备份...\n";
QProcess cpProcess;
QString cpCmd = QString("cp \"%1\" \"%2\"").arg(currentPath).arg(backupPath);
cpProcess.start("sh", QStringList() << "-c" << cpCmd);
cpProcess.waitForFinished(5000);
if (cpProcess.exitCode() == 0 && QFile::exists(backupPath)) {
diagnosticInfo += "系统命令备份成功!继续安装过程...\n";
// 如果系统命令成功,继续执行后续逻辑
} else {
diagnosticInfo += QString("系统命令也失败了,退出码:%1\n").arg(cpProcess.exitCode());
diagnosticInfo += QString("系统命令错误输出:%1\n").arg(QString::fromLocal8Bit(cpProcess.readAllStandardError()));
QMessageBox::warning(this, "安装失败", QString("无法备份当前程序:%1%2").arg(errorDetail).arg(diagnosticInfo));
return;
}
}
#endif
// 先将新文件复制到临时位置
// 确保临时文件不存在
if (QFile::exists(tempPath)) {
if (!QFile::remove(tempPath)) {
QMessageBox::warning(this, "安装失败",
QString("无法删除旧的临时文件:%1").arg(tempPath));
return;
}
}
if (!QFile::copy(downloadedUpdatePath, tempPath)) {
QString errorMsg = QString("无法复制更新文件到临时位置\n源文件:%1\n目标文件:%2\n错误:%3")
.arg(downloadedUpdatePath)
.arg(tempPath)
.arg(QFile(downloadedUpdatePath).errorString());
QMessageBox::warning(this, "安装失败", errorMsg);
return;
}
// 验证复制的文件
QFileInfo sourceInfo(downloadedUpdatePath);
QFileInfo tempInfo(tempPath);
if (!tempInfo.exists() || tempInfo.size() != sourceInfo.size()) {
QMessageBox::warning(this, "安装失败",
QString("复制的临时文件不完整\n源文件大小:%1 字节\n临时文件大小:%2 字节")
.arg(sourceInfo.size())
.arg(tempInfo.exists() ? tempInfo.size() : 0));
QFile::remove(tempPath); // 清理不完整的文件
return;
}
#ifdef Q_OS_WIN
// Windows平台的更新和重启逻辑
// 在Windows上检查目标目录是否可写
QFileInfo dirInfo(currentDir);
if (!dirInfo.isWritable()) {
QMessageBox::warning(this, "安装失败",
QString("目标目录没有写入权限:%1\n请以管理员身份运行程序或选择其他安装位置。")
.arg(currentDir));
QFile::remove(tempPath); // 清理临时文件
return;
}
QString scriptPath = currentDir + "/update_and_restart.bat";
QFile scriptFile(scriptPath);
if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&scriptFile);
out << "@echo off\r\n";
out << "timeout /t 2 /nobreak >nul\r\n"; // 等待当前进程完全退出
out << "REM 移除旧文件并重命名新文件\r\n";
out << "del /f /q \"" << QDir::toNativeSeparators(currentPath) << "\"\r\n";
out << "move \"" << QDir::toNativeSeparators(tempPath) << "\" \"" << QDir::toNativeSeparators(currentPath) << "\"\r\n";
out << "cd /d \"" << QDir::toNativeSeparators(currentDir) << "\"\r\n";
out << "REM 启动新程序\r\n";
out << "start \"\" \"" << QDir::toNativeSeparators(currentFileName) << "\"\r\n";
out << "REM 清理脚本文件\r\n";
out << "timeout /t 1 /nobreak >nul\r\n";
out << "del /f /q \"" << QDir::toNativeSeparators(scriptPath) << "\"\r\n";
scriptFile.close();
// 启动更新脚本并退出当前程序
QProcess::startDetached("cmd.exe", QStringList() << "/c" << QDir::toNativeSeparators(scriptPath));
QMessageBox::information(this, "安装完成", "更新安装完成,程序将自动重启!");
// 延迟退出,确保脚本有时间启动
QTimer::singleShot(500, []() {
QApplication::quit();
});
} else {
QMessageBox::warning(this, "安装失败",
QString("无法创建更新脚本:%1\n错误:%2")
.arg(scriptPath)
.arg(scriptFile.errorString()));
QFile::remove(tempPath); // 清理临时文件
}
#else
// Linux/Unix平台的更新和重启逻辑
// 设置临时文件权限
QFile::setPermissions(tempPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
QFile::ReadGroup | QFile::ExeGroup |
QFile::ReadOther | QFile::ExeOther);
// 创建更新脚本
QString scriptPath = currentDir + "/update_and_restart.sh";
QFile scriptFile(scriptPath);
if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&scriptFile);
out << "#!/bin/bash\n";
out << "sleep 2\n"; // 等待当前进程完全退出
out << "# 移除旧文件并重命名新文件\n";
out << "rm -f \"" << currentPath << "\"\n";
out << "mv \"" << tempPath << "\" \"" << currentPath << "\"\n";
out << "chmod +x \"" << currentPath << "\"\n";
out << "cd \"" << currentDir << "\"\n";
out << "# 启动新程序\n";
out << "nohup ./" << currentFileName << " > /dev/null 2>&1 &\n";
out << "# 清理脚本文件\n";
out << "sleep 1\n";
out << "rm -f \"" << scriptPath << "\"\n";
scriptFile.close();
// 设置脚本可执行权限
QFile::setPermissions(scriptPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
QFile::ReadGroup | QFile::ExeGroup |
QFile::ReadOther | QFile::ExeOther);
// 启动更新脚本并退出当前程序
QProcess::startDetached("/bin/bash", QStringList() << scriptPath);
QMessageBox::information(this, "安装完成", "更新安装完成,程序将自动重启!");
// 延迟退出,确保脚本有时间启动
QTimer::singleShot(500, []() {
QApplication::quit();
});
} else {
QMessageBox::warning(this, "安装失败", "无法创建更新脚本!");
QFile::remove(tempPath); // 清理临时文件
}
#endif
}
}
int MainWindow::compareVersions(const QString &version1, const QString &version2)
{
QStringList v1Parts = version1.split('.');
QStringList v2Parts = version2.split('.');
int maxParts = qMax(v1Parts.size(), v2Parts.size());
for (int i = 0; i < maxParts; ++i) {
int v1Part = (i < v1Parts.size()) ? v1Parts[i].toInt() : 0;
int v2Part = (i < v2Parts.size()) ? v2Parts[i].toInt() : 0;
if (v1Part < v2Part) return -1;
if (v1Part > v2Part) return 1;
}
return 0;
}
void MainWindow::onUpdateSettingsClicked()
{
UpdateSettingsDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
// 更新本地设置
updateServerUrl = dialog.getUpdateServerUrl();
settings->setValue("updateServerUrl", updateServerUrl);
settings->setValue("autoCheckUpdates", dialog.isAutoCheckEnabled());
settings->setValue("checkInterval", dialog.getCheckInterval());
QMessageBox::information(this, "设置保存", "更新设置已保存!");
}
}