2054 lines
83 KiB
C++
2054 lines
83 KiB
C++
#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, "设置保存", "更新设置已保存!");
|
||
}
|
||
}
|