commit e84cef6c95c4c5ea9a1ee79256887e806729c38b
Author: zzh <838331105@qq.com>
Date: Thu Sep 11 09:35:03 2025 +0800
Initial commit: Qt MQTT Light Strip Manager
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..56f9230
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,57 @@
+# Build directories
+build/
+CMakeFiles/
+.qt/
+
+# CMake generated files
+CMakeCache.txt
+cmake_install.cmake
+Makefile
+
+# Qt generated files
+*_autogen/
+moc_*.cpp
+moc_*.h
+qrc_*.cpp
+ui_*.h
+*.moc
+
+# Executable files
+QtDemo
+*.exe
+
+# IDE files
+*.user
+*.pro.user*
+.qtc/
+.qtc_clangd/
+
+# Temporary files
+*.tmp
+*.temp
+*~
+
+# Log files
+*.log
+
+# Archive files
+*.tar.gz
+*.zip
+*.xz
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Ninja build files
+.ninja_deps
+.ninja_log
+build.ninja
+
+# Testing
+Testing/
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..930de8b
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,54 @@
+cmake_minimum_required(VERSION 3.16)
+project(QtDemo VERSION 1.0.0 LANGUAGES CXX)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# 设置Qt版本检查选项
+set(QT_NO_PACKAGE_VERSION_CHECK TRUE)
+set(QT_NO_PACKAGE_VERSION_INCOMPATIBLE_WARNING TRUE)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Widgets Network)
+
+# 启用Qt的MOC、UIC、RCC自动处理
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTORCC ON)
+
+set(SOURCES
+ src/main.cpp
+ src/mainwindow.cpp
+ src/mqttclient.cpp
+ src/lightstripmanager.cpp
+)
+
+set(HEADERS
+ src/mainwindow.h
+ src/mqttclient.h
+ src/lightstripmanager.h
+)
+
+add_executable(QtDemo ${SOURCES} ${HEADERS}
+ resources.qrc logo.rc
+)
+
+target_link_libraries(QtDemo
+ Qt6::Core
+ Qt6::Widgets
+ Qt6::Network
+)
+
+# Windows平台设置
+if(WIN32)
+ # 添加交叉编译支持(注意:这些设置应该在项目配置之前)
+ set_target_properties(QtDemo PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ )
+endif()
+
+# macOS平台设置
+if(APPLE)
+ set_target_properties(QtDemo PROPERTIES
+ MACOSX_BUNDLE TRUE
+ )
+endif()
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..31b783e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+# Qt MQTT Demo
+
+这是一个基于Qt6的MQTT客户端演示程序,支持连接MQTT服务器并进行设备通信。
+
+## 功能特性
+
+- MQTT服务器连接/断开
+- 设备任务下发
+- 消息订阅和接收
+- 实时状态显示
+- 用户友好的图形界面
+
+## 编译要求
+
+- Qt 6.4+
+- CMake 3.16+
+- C++17编译器
+- Qt6 MQTT模块
+
+## 编译步骤
+
+### 使用CMake编译
+
+```bash
+# 创建构建目录
+mkdir -p build
+cd build
+
+# 配置项目
+cmake ..
+
+# 编译
+cmake --build . -j$(nproc)
+
+# 运行
+./QtDemo
+```
+
+### 安装Qt6 MQTT依赖
+
+在Ubuntu/Debian系统上:
+
+```bash
+sudo apt update
+sudo apt install qt6-base-dev qt6-tools-dev libqt6mqtt6-dev
+```
+
+## 使用说明
+
+1. **连接MQTT服务器**
+ - 输入服务器地址和端口
+ - 可选:输入用户名和密码
+ - 点击"连接"按钮
+
+2. **发送设备任务**
+ - 确保MQTT已连接
+ - 输入设备SN号
+ - 点击"发送任务"按钮
+
+3. **查看消息**
+ - 所有MQTT消息和系统日志显示在消息区域
+ - 包含时间戳和详细信息
+
+## 项目结构
\ No newline at end of file
diff --git a/logo.ico b/logo.ico
new file mode 100644
index 0000000..28fab1e
Binary files /dev/null and b/logo.ico differ
diff --git a/logo.rc b/logo.rc
new file mode 100644
index 0000000..4e6f23f
--- /dev/null
+++ b/logo.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON DISCARDABLE "logo.ico"
\ No newline at end of file
diff --git a/resources.qrc b/resources.qrc
new file mode 100644
index 0000000..7be0bf1
--- /dev/null
+++ b/resources.qrc
@@ -0,0 +1,5 @@
+
+
+ src/tuxi.ico
+
+
diff --git a/src/lightstripmanager.cpp b/src/lightstripmanager.cpp
new file mode 100644
index 0000000..942608d
--- /dev/null
+++ b/src/lightstripmanager.cpp
@@ -0,0 +1,939 @@
+#include "lightstripmanager.h"
+#include
+// 如果不再需要可以删除: #include
+
+LightStripManager::LightStripManager(QWidget *parent)
+ : QWidget(parent)
+ , settings(new QSettings("LightStripManager", "Config", this))
+ , columnsPerRow(4)
+ , resizeTimer(new QTimer(this))
+{
+ setupUI();
+ loadSnList();
+
+ // 设置窗口属性
+ setWindowTitle("灯条SN管理器");
+ setWindowIcon(QIcon(":/icons/lightstrip.png"));
+
+ // 设置初始窗口大小和位置
+ resize(1000, 650);
+
+ // 居中显示到父窗口
+ if (parent) {
+ QWidget *parentWidget = qobject_cast(parent);
+ if (parentWidget) {
+ QRect parentGeometry = parentWidget->geometry();
+ int x = parentGeometry.x() + (parentGeometry.width() - width()) / 2;
+ int y = parentGeometry.y() + (parentGeometry.height() - height()) / 2;
+ move(x, y);
+ }
+ } else {
+ // 如果没有父窗口,则居中到屏幕
+ QScreen *screen = QGuiApplication::primaryScreen();
+ if (screen) {
+ QRect screenGeometry = screen->geometry();
+ int x = (screenGeometry.width() - width()) / 2;
+ int y = (screenGeometry.height() - height()) / 2;
+ move(x, y);
+ }
+ }
+
+ // 设置最小尺寸
+ setMinimumSize(700, 500);
+
+ // 彻底修复背景透明问题
+ setStyleSheet(
+ "QWidget { "
+ " background-color: #f0f0f0; "
+ "} "
+ "QGroupBox { "
+ " background-color: #f0f0f0; "
+ "} "
+ "QSplitter { "
+ " background-color: #f0f0f0; "
+ "}"
+ );
+ setAutoFillBackground(true);
+
+ // 设置窗口属性,确保有标题栏和关闭按钮
+ setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint);
+
+ // 连接调整大小定时器
+ resizeTimer->setSingleShot(true);
+ resizeTimer->setInterval(100);
+ connect(resizeTimer, &QTimer::timeout, this, &LightStripManager::applyResponsiveLayout);
+}
+
+LightStripManager::~LightStripManager()
+{
+ saveSnList();
+}
+
+void LightStripManager::setupUI()
+{
+ mainLayout = new QVBoxLayout(this);
+ mainLayout->setSpacing(15);
+ mainLayout->setContentsMargins(20, 20, 20, 20);
+
+ // 创建主分割器
+ mainSplitter = new QSplitter(Qt::Vertical, this);
+ mainLayout->addWidget(mainSplitter);
+
+ // 添加顶部关闭按钮区域
+ QHBoxLayout *topLayout = new QHBoxLayout();
+ topLayout->addStretch();
+
+ QPushButton *closeBtn = new QPushButton("关闭");
+ closeBtn->setMaximumWidth(80);
+ closeBtn->setStyleSheet(
+ "QPushButton { "
+ " background-color: #f44336; "
+ " color: white; "
+ " font-weight: bold; "
+ " border-radius: 6px; "
+ " padding: 8px 16px; "
+ "} "
+ "QPushButton:hover { background-color: #d32f2f; }"
+ );
+ connect(closeBtn, &QPushButton::clicked, this, &QWidget::close);
+ topLayout->addWidget(closeBtn);
+
+ mainLayout->addLayout(topLayout);
+
+ // 设置各个区域
+ setupControlPanel();
+ setupSnDisplayArea();
+ setupLightControlArea();
+
+ // 设置分割器比例
+ mainSplitter->setStretchFactor(0, 0); // 控制面板固定高度
+ mainSplitter->setStretchFactor(1, 1); // SN显示区域可伸缩
+ mainSplitter->setStretchFactor(2, 0); // 点亮控制区域固定高度
+}
+
+void LightStripManager::setupControlPanel()
+{
+ controlGroup = new QGroupBox("控制面板", this);
+ controlGroup->setStyleSheet(
+ "QGroupBox { "
+ " color: #000000; "
+ " font-weight: bold; "
+ " font-size: 14px; "
+ " padding-top: 15px; "
+ " border: 2px solid #ddd; "
+ " border-radius: 8px; "
+ " margin-top: 10px; "
+ "}"
+ );
+ controlGroup->setMaximumHeight(120);
+
+ QVBoxLayout *controlMainLayout = new QVBoxLayout(controlGroup);
+
+ // 第一行:选择和删除操作
+ QHBoxLayout *row1Layout = new QHBoxLayout();
+
+ selectAllBtn = new QPushButton("全选");
+ selectAllBtn->setMinimumHeight(35);
+ selectAllBtn->setStyleSheet(
+ "QPushButton { "
+ " background-color: #2196F3; "
+ " color: white; "
+ " font-weight: bold; "
+ " border-radius: 6px; "
+ " padding: 8px 16px; "
+ "} "
+ "QPushButton:hover { background-color: #1976D2; }"
+ );
+ connect(selectAllBtn, &QPushButton::clicked, this, &LightStripManager::onSelectAllClicked);
+
+ deselectAllBtn = new QPushButton("取消全选");
+ deselectAllBtn->setMinimumHeight(35);
+ deselectAllBtn->setStyleSheet(
+ "QPushButton { "
+ " background-color: #FF9800; "
+ " color: white; "
+ " font-weight: bold; "
+ " border-radius: 6px; "
+ " padding: 8px 16px; "
+ "} "
+ "QPushButton:hover { background-color: #F57C00; }"
+ );
+ connect(deselectAllBtn, &QPushButton::clicked, this, &LightStripManager::onDeselectAllClicked);
+
+ deleteSelectedBtn = new QPushButton("删除选中");
+ deleteSelectedBtn->setMinimumHeight(35);
+ deleteSelectedBtn->setStyleSheet(
+ "QPushButton { "
+ " background-color: #f44336; "
+ " color: white; "
+ " font-weight: bold; "
+ " border-radius: 6px; "
+ " padding: 8px 16px; "
+ "} "
+ "QPushButton:hover { background-color: #d32f2f; }"
+ );
+ connect(deleteSelectedBtn, &QPushButton::clicked, this, &LightStripManager::onDeleteSelectedClicked);
+
+ clearAllBtn = new QPushButton("清空全部");
+ clearAllBtn->setMinimumHeight(35);
+ clearAllBtn->setStyleSheet(
+ "QPushButton { "
+ " background-color: #9E9E9E; "
+ " color: white; "
+ " font-weight: bold; "
+ " border-radius: 6px; "
+ " padding: 8px 16px; "
+ "} "
+ "QPushButton:hover { background-color: #757575; }"
+ );
+ connect(clearAllBtn, &QPushButton::clicked, this, &LightStripManager::onClearSnListClicked);
+
+ refreshBtn = new QPushButton("刷新");
+ refreshBtn->setMinimumHeight(35);
+ refreshBtn->setStyleSheet(
+ "QPushButton { "
+ " background-color: #4CAF50; "
+ " color: white; "
+ " font-weight: bold; "
+ " border-radius: 6px; "
+ " padding: 8px 16px; "
+ "} "
+ "QPushButton:hover { background-color: #388E3C; }"
+ );
+ connect(refreshBtn, &QPushButton::clicked, this, &LightStripManager::onRefreshClicked);
+
+ row1Layout->addWidget(selectAllBtn);
+ row1Layout->addWidget(deselectAllBtn);
+ row1Layout->addWidget(deleteSelectedBtn);
+ row1Layout->addWidget(clearAllBtn);
+ row1Layout->addWidget(refreshBtn);
+ row1Layout->addStretch();
+
+ // 统计标签
+ snCountLabel = new QLabel("已发现灯条: 0 个");
+ snCountLabel->setStyleSheet(
+ "QLabel { "
+ " font-weight: bold; "
+ " font-size: 14px; "
+ " color: #2196F3; "
+ " padding: 5px; "
+ "}"
+ );
+ row1Layout->addWidget(snCountLabel);
+
+ controlMainLayout->addLayout(row1Layout);
+
+ // 第二行:搜索和手动添加
+ QHBoxLayout *row2Layout = new QHBoxLayout();
+
+ QLabel *searchLabel = new QLabel("搜索:");
+ searchLabel->setStyleSheet("QLabel { color: #000000; }");
+ row2Layout->addWidget(searchLabel);
+ searchEdit = new QLineEdit();
+ searchEdit->setPlaceholderText("输入SN进行搜索...");
+ searchEdit->setMinimumHeight(30);
+ searchEdit->setStyleSheet(
+ "QLineEdit { "
+ " color: #000000; "
+ " border: 2px solid #ddd; "
+ " border-radius: 6px; "
+ " padding: 5px 10px; "
+ " font-size: 12px; "
+ "} "
+ "QLineEdit:focus { border-color: #2196F3; }"
+ );
+ connect(searchEdit, &QLineEdit::textChanged, this, &LightStripManager::onSearchTextChanged);
+ row2Layout->addWidget(searchEdit);
+
+ QLabel *manualAddLabel = new QLabel("手动添加:");
+ manualAddLabel->setStyleSheet("QLabel { color: #000000; }");
+ row2Layout->addWidget(manualAddLabel);
+ manualSnEdit = new QLineEdit();
+ manualSnEdit->setPlaceholderText("输入SN手动添加...");
+ manualSnEdit->setMinimumHeight(30);
+ manualSnEdit->setStyleSheet(
+ "QLineEdit { "
+ " color: #000000; "
+ " border: 2px solid #ddd; "
+ " border-radius: 6px; "
+ " padding: 5px 10px; "
+ " font-size: 12px; "
+ "} "
+ "QLineEdit:focus { border-color: #2196F3; }"
+ );
+ row2Layout->addWidget(manualSnEdit);
+
+ addSnBtn = new QPushButton("添加");
+ addSnBtn->setMinimumHeight(30);
+ addSnBtn->setStyleSheet(
+ "QPushButton { "
+ " background-color: #4CAF50; "
+ " color: white; "
+ " font-weight: bold; "
+ " border-radius: 6px; "
+ " padding: 5px 15px; "
+ "} "
+ "QPushButton:hover { background-color: #388E3C; }"
+ );
+ connect(addSnBtn, &QPushButton::clicked, this, &LightStripManager::onAddSnManuallyClicked);
+ row2Layout->addWidget(addSnBtn);
+
+ controlMainLayout->addLayout(row2Layout);
+
+ mainSplitter->addWidget(controlGroup);
+}
+
+void LightStripManager::setupSnDisplayArea()
+{
+ snDisplayGroup = new QGroupBox("灯条SN列表", this);
+ snDisplayGroup->setStyleSheet(
+ "QGroupBox { "
+ " color: #000000; "
+ " font-weight: bold; "
+ " font-size: 14px; "
+ " padding-top: 15px; "
+ " border: 2px solid #ddd; "
+ " border-radius: 8px; "
+ " margin-top: 10px; "
+ "}"
+ );
+
+ QVBoxLayout *snDisplayLayout = new QVBoxLayout(snDisplayGroup);
+
+ // 创建滚动区域
+ snScrollArea = new QScrollArea(this);
+ snScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ snScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ snScrollArea->setWidgetResizable(true);
+ snScrollArea->setStyleSheet(
+ "QScrollArea { "
+ " border: 1px solid #ddd; "
+ " border-radius: 6px; "
+ " background-color: #fafafa; "
+ "} "
+ "QScrollBar:vertical { "
+ " border: none; "
+ " background: #f0f0f0; "
+ " width: 12px; "
+ " border-radius: 6px; "
+ "} "
+ "QScrollBar::handle:vertical { "
+ " background: #c0c0c0; "
+ " border-radius: 6px; "
+ " min-height: 20px; "
+ "} "
+ "QScrollBar::handle:vertical:hover { "
+ " background: #a0a0a0; "
+ "}"
+ );
+
+ // 创建容器widget和网格布局
+ snContainerWidget = new QWidget();
+ snGridLayout = new QGridLayout(snContainerWidget);
+ snGridLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+ snGridLayout->setSpacing(15);
+ snGridLayout->setContentsMargins(15, 15, 15, 15);
+
+ snScrollArea->setWidget(snContainerWidget);
+ snDisplayLayout->addWidget(snScrollArea);
+
+ mainSplitter->addWidget(snDisplayGroup);
+}
+
+void LightStripManager::setupLightControlArea()
+{
+ lightControlGroup = new QGroupBox("点亮控制", this);
+ lightControlGroup->setStyleSheet(
+ "QGroupBox { "
+ " color: #000000; "
+ " font-weight: bold; "
+ " font-size: 14px; "
+ " padding-top: 15px; "
+ " border: 2px solid #ddd; "
+ " border-radius: 8px; "
+ " margin-top: 10px; "
+ "}"
+ );
+ lightControlGroup->setMaximumHeight(150);
+
+ QVBoxLayout *lightControlLayout = new QVBoxLayout(lightControlGroup);
+
+ // 第一行:颜色和闪烁
+ QHBoxLayout *row1Layout = new QHBoxLayout();
+
+ QLabel *colorLabel = new QLabel("颜色:");
+ colorLabel->setStyleSheet("QLabel { color: #000000; }");
+ row1Layout->addWidget(colorLabel);
+
+ colorCombo = new QComboBox();
+ colorCombo->addItems({"8-灭", "1-红", "2-黄", "3-蓝", "4-绿", "5-青", "6-白", "7-紫"});
+ colorCombo->setCurrentIndex(6);
+ colorCombo->setMinimumHeight(30);
+ colorCombo->setStyleSheet("QComboBox { color: #000000; } QComboBox QAbstractItemView { color: #000000; }");
+ row1Layout->addWidget(colorCombo);
+
+ // 删除以下随机颜色按钮相关代码:
+ // randomColorBtn = new QPushButton("随机颜色");
+ // randomColorBtn->setMinimumHeight(30);
+ // randomColorBtn->setStyleSheet(...);
+ // connect(randomColorBtn, &QPushButton::clicked, this, &LightStripManager::onRandomColorClicked);
+ // row1Layout->addWidget(randomColorBtn);
+
+ QLabel *flashLabel = new QLabel("闪烁:");
+ flashLabel->setStyleSheet("QLabel { color: #000000; }");
+ row1Layout->addWidget(flashLabel);
+
+ flashCombo = new QComboBox();
+ flashCombo->addItems({"0-关闭", "1-开启"});
+ flashCombo->setCurrentIndex(0);
+ flashCombo->setMinimumHeight(30);
+ flashCombo->setStyleSheet("QComboBox { color: #000000; } QComboBox QAbstractItemView { color: #000000; }");
+ row1Layout->addWidget(flashCombo);
+
+ QLabel *soundLabel = new QLabel("声音:");
+ soundLabel->setStyleSheet("QLabel { color: #000000; }");
+ row1Layout->addWidget(soundLabel);
+
+ soundCombo = new QComboBox();
+ soundCombo->addItems({"0-关闭", "1-开启"});
+ soundCombo->setCurrentIndex(0);
+ soundCombo->setMinimumHeight(30);
+ soundCombo->setStyleSheet("QComboBox { color: #000000; } QComboBox QAbstractItemView { color: #000000; }");
+ row1Layout->addWidget(soundCombo);
+ row1Layout->addStretch();
+
+ lightControlLayout->addLayout(row1Layout);
+
+ // 第二行:时间参数和按钮
+ QHBoxLayout *row2Layout = new QHBoxLayout();
+
+ QLabel *flashIntervalLabel = new QLabel("闪烁间隔(秒):");
+ flashIntervalLabel->setStyleSheet("QLabel { color: #000000; }");
+ row2Layout->addWidget(flashIntervalLabel);
+
+ flashIntervalSpin = new QSpinBox();
+ flashIntervalSpin->setRange(1, 60);
+ flashIntervalSpin->setValue(4);
+ flashIntervalSpin->setMinimumHeight(30);
+ flashIntervalSpin->setStyleSheet("QSpinBox { color: #000000; }");
+ row2Layout->addWidget(flashIntervalSpin);
+
+ QLabel *lightDurationLabel = new QLabel("点亮时长(秒):");
+ lightDurationLabel->setStyleSheet("QLabel { color: #000000; }");
+ row2Layout->addWidget(lightDurationLabel);
+
+ lightDurationSpin = new QSpinBox();
+ lightDurationSpin->setRange(1, 300);
+ lightDurationSpin->setValue(30);
+ lightDurationSpin->setMinimumHeight(30);
+ lightDurationSpin->setStyleSheet("QSpinBox { color: #000000; }");
+ row2Layout->addWidget(lightDurationSpin);
+
+ row2Layout->addStretch();
+
+ sendLightSelectedBtn = new QPushButton("点亮选中");
+ sendLightSelectedBtn->setMinimumHeight(40);
+ sendLightSelectedBtn->setMinimumWidth(120);
+ sendLightSelectedBtn->setStyleSheet(
+ "QPushButton { "
+ " background-color: #FF9800; "
+ " color: white; "
+ " font-weight: bold; "
+ " padding: 10px 20px; "
+ " border-radius: 6px; "
+ " font-size: 14px; "
+ "} "
+ "QPushButton:hover { background-color: #F57C00; }"
+ );
+ connect(sendLightSelectedBtn, &QPushButton::clicked, this, &LightStripManager::onSendLightSelectedClicked);
+ row2Layout->addWidget(sendLightSelectedBtn);
+
+ sendLightAllBtn = new QPushButton("点亮全部");
+ sendLightAllBtn->setMinimumHeight(40);
+ sendLightAllBtn->setMinimumWidth(120);
+ sendLightAllBtn->setStyleSheet(
+ "QPushButton { "
+ " background-color: #4CAF50; "
+ " color: white; "
+ " font-weight: bold; "
+ " padding: 10px 20px; "
+ " border-radius: 6px; "
+ " font-size: 14px; "
+ "} "
+ "QPushButton:hover { background-color: #388E3C; }"
+ );
+ connect(sendLightAllBtn, &QPushButton::clicked, this, &LightStripManager::onSendLightAllClicked);
+ row2Layout->addWidget(sendLightAllBtn);
+
+ lightControlLayout->addLayout(row2Layout);
+
+ mainSplitter->addWidget(lightControlGroup);
+}
+
+void LightStripManager::addSnToList(const QString &sn)
+{
+ if (uniqueSnSet.contains(sn)) {
+ return; // 已存在,不重复添加
+ }
+
+ uniqueSnSet.insert(sn);
+
+ // 创建SN widget
+ QWidget *lightStripWidget = createSnWidget(sn);
+ lightStripWidgets.append(lightStripWidget);
+
+ // 应用响应式布局
+ applyResponsiveLayout();
+
+ // 更新计数
+ updateSnCount();
+
+ // 保存到设置
+ saveSnList();
+}
+
+QWidget* LightStripManager::createSnWidget(const QString &sn)
+{
+ // 创建灯条widget容器
+ QWidget *lightStripWidget = new QWidget();
+ lightStripWidget->setFixedSize(200, 90); // 适中的尺寸
+ lightStripWidget->setStyleSheet(
+ "QWidget { "
+ " background-color: white; "
+ " border: 2px solid #e0e0e0; "
+ " border-radius: 10px; "
+ " margin: 5px; "
+ "} "
+ "QWidget:hover { "
+ " border-color: #2196F3; "
+ " background-color: #f5f5f5; "
+ "}"
+ );
+
+ // 创建垂直布局
+ QVBoxLayout *itemLayout = new QVBoxLayout(lightStripWidget);
+ itemLayout->setContentsMargins(10, 10, 10, 10);
+ itemLayout->setSpacing(8);
+
+ // 创建复选框
+ QCheckBox *checkBox = new QCheckBox();
+ checkBox->setStyleSheet(
+ "QCheckBox { "
+ " font-weight: bold; "
+ " font-size: 12px; "
+ "} "
+ "QCheckBox::indicator { "
+ " width: 20px; "
+ " height: 20px; "
+ "} "
+ "QCheckBox::indicator:unchecked { "
+ " border: 2px solid #ccc; "
+ " border-radius: 4px; "
+ " background-color: white; "
+ "} "
+ "QCheckBox::indicator:checked { "
+ " border: 2px solid #4CAF50; "
+ " border-radius: 4px; "
+ " background-color: #4CAF50; "
+ "}"
+ );
+ connect(checkBox, &QCheckBox::stateChanged, this, &LightStripManager::onCheckBoxStateChanged);
+ lightStripCheckBoxes.append(checkBox);
+
+ // 创建SN标签
+ QLabel *snLabel = new QLabel(sn);
+ snLabel->setAlignment(Qt::AlignCenter);
+ snLabel->setStyleSheet(
+ "QLabel { "
+ " color: #333; "
+ " font-weight: bold; "
+ " font-size: 11px; " // 稍微减小字体
+ " background-color: #f0f8ff; "
+ " border: 1px solid #ddd; "
+ " border-radius: 5px; "
+ " padding: 6px 4px; " // 减少左右padding
+ " min-height: 12px; " // 相应调整最小高度
+ " line-height: 0.3; " // 稍微紧凑的行高
+ "}"
+ );
+ snLabel->setWordWrap(true);
+
+ // 添加到布局
+ itemLayout->addWidget(checkBox, 0, Qt::AlignCenter);
+ itemLayout->addWidget(snLabel);
+ itemLayout->addStretch();
+
+ return lightStripWidget;
+}
+
+void LightStripManager::applyResponsiveLayout()
+{
+ // 清空当前布局
+ QLayoutItem *item;
+ while ((item = snGridLayout->takeAt(0)) != nullptr) {
+ // 不删除widget,只是从布局中移除
+ }
+
+ // 计算每行的列数
+ int containerWidth = snScrollArea->viewport()->width() - 30; // 减去边距
+ int widgetWidth = 220; // widget宽度 + 间距
+ columnsPerRow = qMax(1, containerWidth / widgetWidth);
+
+ // 重新排列所有widget
+ for (int i = 0; i < lightStripWidgets.size(); ++i) {
+ int row = i / columnsPerRow;
+ int col = i % columnsPerRow;
+ snGridLayout->addWidget(lightStripWidgets[i], row, col);
+ }
+}
+
+void LightStripManager::resizeEvent(QResizeEvent *event)
+{
+ QWidget::resizeEvent(event);
+ resizeTimer->start(); // 延迟应用响应式布局
+}
+
+void LightStripManager::onClearSnListClicked()
+{
+ if (uniqueSnSet.isEmpty()) {
+ return;
+ }
+
+ int ret = QMessageBox::question(this, "确认清空",
+ QString("确定要清空所有 %1 个灯条SN吗?").arg(uniqueSnSet.size()),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+
+ if (ret == QMessageBox::Yes) {
+ // 清空所有灯条widget
+ for (QWidget *widget : lightStripWidgets) {
+ delete widget;
+ }
+
+ // 清空列表和集合
+ lightStripWidgets.clear();
+ lightStripCheckBoxes.clear();
+ uniqueSnSet.clear();
+
+ // 更新显示
+ updateSnCount();
+ updateControlButtons();
+
+ // 保存设置
+ saveSnList();
+
+ emit clearAllRequested();
+ }
+}
+
+void LightStripManager::onSelectAllClicked()
+{
+ for (QCheckBox *checkBox : lightStripCheckBoxes) {
+ checkBox->setChecked(true);
+ }
+}
+
+void LightStripManager::onDeselectAllClicked()
+{
+ for (QCheckBox *checkBox : lightStripCheckBoxes) {
+ checkBox->setChecked(false);
+ }
+}
+
+void LightStripManager::onDeleteSelectedClicked()
+{
+ QStringList selectedSns = getSelectedSns();
+ if (selectedSns.isEmpty()) {
+ QMessageBox::information(this, "提示", "请先选择要删除的灯条SN。");
+ return;
+ }
+
+ int ret = QMessageBox::question(this, "确认删除",
+ QString("确定要删除选中的 %1 个灯条SN吗?").arg(selectedSns.size()),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+
+ if (ret == QMessageBox::Yes) {
+ // 从后往前删除,避免索引问题
+ for (int i = lightStripWidgets.size() - 1; i >= 0; --i) {
+ if (lightStripCheckBoxes[i]->isChecked()) {
+ // 从集合中移除SN
+ QLabel *snLabel = lightStripWidgets[i]->findChild();
+ if (snLabel) {
+ uniqueSnSet.remove(snLabel->text());
+ }
+
+ // 删除widget
+ delete lightStripWidgets[i];
+ lightStripWidgets.removeAt(i);
+ lightStripCheckBoxes.removeAt(i);
+ }
+ }
+
+ // 重新应用布局
+ applyResponsiveLayout();
+
+ // 更新显示
+ updateSnCount();
+ updateControlButtons();
+
+ // 保存设置
+ saveSnList();
+ }
+}
+
+void LightStripManager::onAddSnManuallyClicked()
+{
+ QString sn = manualSnEdit->text().trimmed();
+ if (sn.isEmpty()) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle("提示");
+ msgBox.setText("请输入要添加的灯条SN。");
+ msgBox.setIcon(QMessageBox::Information);
+ msgBox.setStyleSheet(
+ "QMessageBox { "
+ " color: #000000; " // 消息文本颜色设为黑色
+ "} "
+ "QPushButton { "
+ " color: #000000; " // 按钮文字颜色设为黑色
+ " background-color: #f0f0f0; "
+ " border: 1px solid #ccc; "
+ " border-radius: 4px; "
+ " padding: 6px 12px; "
+ " min-width: 60px; "
+ "} "
+ "QPushButton:hover { "
+ " background-color: #e0e0e0; "
+ "} "
+ "QPushButton:pressed { "
+ " background-color: #d0d0d0; "
+ "}"
+ );
+ msgBox.exec();
+ return;
+ }
+
+ if (uniqueSnSet.contains(sn)) {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle("提示");
+ msgBox.setText("该灯条SN已存在。");
+ msgBox.setIcon(QMessageBox::Information);
+ msgBox.setStyleSheet(
+ "QMessageBox { "
+ " color: #000000; " // 消息文本颜色设为黑色
+ "} "
+ "QPushButton { "
+ " color: #000000; " // 按钮文字颜色设为黑色
+ " background-color: #f0f0f0; "
+ " border: 1px solid #ccc; "
+ " border-radius: 4px; "
+ " padding: 6px 12px; "
+ " min-width: 60px; "
+ "} "
+ "QPushButton:hover { "
+ " background-color: #e0e0e0; "
+ "} "
+ "QPushButton:pressed { "
+ " background-color: #d0d0d0; "
+ "}"
+ );
+ msgBox.exec();
+ return;
+ }
+
+ addSnToList(sn);
+ manualSnEdit->clear();
+
+ QMessageBox msgBox;
+ msgBox.setWindowTitle("成功");
+ msgBox.setText(QString("已添加灯条SN: %1").arg(sn));
+ msgBox.setIcon(QMessageBox::Information);
+ msgBox.setStyleSheet(
+ "QMessageBox { "
+ " color: #000000; " // 消息文本颜色设为黑色
+ "} "
+ "QPushButton { "
+ " color: #000000; " // 按钮文字颜色设为黑色
+ " background-color: #f0f0f0; "
+ " border: 1px solid #ccc; "
+ " border-radius: 4px; "
+ " padding: 6px 12px; "
+ " min-width: 60px; "
+ "} "
+ "QPushButton:hover { "
+ " background-color: #e0e0e0; "
+ "} "
+ "QPushButton:pressed { "
+ " background-color: #d0d0d0; "
+ "}"
+ );
+ msgBox.exec();
+}
+
+void LightStripManager::onSearchTextChanged(const QString &text)
+{
+ filterSnDisplay(text);
+}
+
+void LightStripManager::onSendLightSelectedClicked()
+{
+ QStringList selectedSns = getSelectedSns();
+ if (selectedSns.isEmpty()) {
+ QMessageBox::information(this, "提示", "请先选择要点亮的灯条。");
+ return;
+ }
+
+ QString color = colorCombo->currentText();
+ bool flash = flashCombo->currentIndex() == 1;
+ int interval = flashIntervalSpin->value();
+ int duration = lightDurationSpin->value();
+ bool sound = soundCombo->currentIndex() == 1;
+
+ emit lightControlRequested(selectedSns, color, flash, interval, duration, sound);
+
+ QMessageBox::information(this, "发送成功",
+ QString("已向 %1 个选中的灯条发送点亮指令。").arg(selectedSns.size()));
+}
+
+void LightStripManager::onSendLightAllClicked()
+{
+ if (uniqueSnSet.isEmpty()) {
+ QMessageBox::information(this, "提示", "没有可点亮的灯条。");
+ return;
+ }
+
+ QStringList allSns = getAllSns();
+ QString color = colorCombo->currentText();
+ bool flash = flashCombo->currentIndex() == 1;
+ int interval = flashIntervalSpin->value();
+ int duration = lightDurationSpin->value();
+ bool sound = soundCombo->currentIndex() == 1;
+
+ // 记录总数量用于提示
+ int totalCount = allSns.size();
+
+ // 按20个一批分组发送
+ const int batchSize = 20;
+ for (int i = 0; i < allSns.size(); i += batchSize) {
+ QStringList batch;
+ for (int j = i; j < qMin(i + batchSize, allSns.size()); ++j) {
+ batch.append(allSns[j]);
+ }
+
+ // 发送当前批次
+ emit lightControlRequested(batch, color, flash, interval, duration, sound);
+
+ // 添加小延迟避免发送过快(可选)
+ QThread::msleep(100);
+ }
+
+ // 显示总数量的提示信息
+ QMessageBox::information(this, "发送成功",
+ QString("已向所有灯条发送点亮指令。"));
+}
+
+void LightStripManager::onCheckBoxStateChanged()
+{
+ updateControlButtons();
+ emit snSelectionChanged(getSelectedSns());
+}
+
+void LightStripManager::onRefreshClicked()
+{
+ // 重新应用响应式布局
+ applyResponsiveLayout();
+
+ // 更新显示
+ updateSnCount();
+ updateControlButtons();
+
+ QMessageBox::information(this, "刷新完成", "界面已刷新。");
+}
+
+// 删除整个函数实现:
+// void LightStripManager::onRandomColorClicked()
+// {
+// int randomIndex = QRandomGenerator::global()->bounded(1, colorCombo->count());
+// colorCombo->setCurrentIndex(randomIndex);
+// QString selectedColor = colorCombo->currentText();
+// QMessageBox::information(this, "随机颜色",
+// QString("已随机选择颜色: %1").arg(selectedColor));
+// }
+
+void LightStripManager::filterSnDisplay(const QString &searchText)
+{
+ for (int i = 0; i < lightStripWidgets.size(); ++i) {
+ QWidget *widget = lightStripWidgets[i];
+ QLabel *snLabel = widget->findChild();
+
+ if (snLabel) {
+ bool visible = searchText.isEmpty() ||
+ snLabel->text().contains(searchText, Qt::CaseInsensitive);
+ widget->setVisible(visible);
+ }
+ }
+
+ // 重新应用布局
+ applyResponsiveLayout();
+}
+
+void LightStripManager::updateSnCount()
+{
+ int count = uniqueSnSet.size();
+ snCountLabel->setText(QString("已发现灯条: %1 个").arg(count));
+
+ // 发出数量变化信号
+ emit snCountChanged(count);
+}
+
+void LightStripManager::updateControlButtons()
+{
+ int selectedCount = getSelectedSns().size();
+ int totalCount = uniqueSnSet.size();
+
+ deleteSelectedBtn->setEnabled(selectedCount > 0);
+ sendLightSelectedBtn->setEnabled(selectedCount > 0);
+ sendLightAllBtn->setEnabled(totalCount > 0);
+ clearAllBtn->setEnabled(totalCount > 0);
+ selectAllBtn->setEnabled(totalCount > 0);
+ deselectAllBtn->setEnabled(selectedCount > 0);
+}
+
+QStringList LightStripManager::getSelectedSns() const
+{
+ QStringList selectedSns;
+ for (int i = 0; i < lightStripCheckBoxes.size(); ++i) {
+ if (lightStripCheckBoxes[i]->isChecked()) {
+ QLabel *snLabel = lightStripWidgets[i]->findChild();
+ if (snLabel) {
+ selectedSns.append(snLabel->text());
+ }
+ }
+ }
+ return selectedSns;
+}
+
+QStringList LightStripManager::getAllSns() const
+{
+ return uniqueSnSet.values();
+}
+
+int LightStripManager::getSnCount() const
+{
+ return uniqueSnSet.size();
+}
+
+void LightStripManager::saveSnList()
+{
+ QStringList snList = uniqueSnSet.values();
+ settings->setValue("lightStripSnList", snList);
+ settings->sync();
+}
+
+void LightStripManager::loadSnList()
+{
+ QStringList snList = settings->value("lightStripSnList").toStringList();
+ for (const QString &sn : snList) {
+ addSnToList(sn);
+ }
+}
\ No newline at end of file
diff --git a/src/lightstripmanager.h b/src/lightstripmanager.h
new file mode 100644
index 0000000..40cf9f9
--- /dev/null
+++ b/src/lightstripmanager.h
@@ -0,0 +1,134 @@
+#ifndef LIGHTSTRIPMANAGER_H
+#define LIGHTSTRIPMANAGER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class MainWindow; // 前向声明
+
+class LightStripManager : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit LightStripManager(QWidget *parent = nullptr);
+ ~LightStripManager();
+
+ // 公共接口
+ void addSnToList(const QString &sn);
+ QStringList getSelectedSns() const;
+ QStringList getAllSns() const;
+ int getSnCount() const;
+ void clearSnList(); // 添加这个函数声明
+
+ // 新增:设置主窗口引用
+ void setMainWindow(MainWindow *mainWindow);
+
+signals:
+ void snSelectionChanged(const QStringList &selectedSns);
+ void lightControlRequested(const QStringList &sns, const QString &color, bool flash, int interval, int duration, bool sound);
+ void clearAllRequested();
+ void snCountChanged(int count); // 新增:灯条数量变化信号
+
+private slots:
+ void onClearSnListClicked();
+ void onSelectAllClicked();
+ void onDeselectAllClicked();
+ void onDeleteSelectedClicked();
+ void onAddSnManuallyClicked();
+ void onSearchTextChanged(const QString &text);
+ void onSendLightSelectedClicked();
+ void onSendLightAllClicked();
+ void onCheckBoxStateChanged();
+ void onRefreshClicked();
+ // 删除这一行: void onRandomColorClicked();
+
+private:
+ void setupUI();
+ void setupControlPanel();
+ void setupSnDisplayArea();
+ void setupLightControlArea();
+ void saveSnList();
+ void loadSnList();
+ void updateSnCount();
+ void updateControlButtons();
+ void filterSnDisplay(const QString &searchText);
+ QWidget* createSnWidget(const QString &sn);
+ void applyResponsiveLayout();
+
+ // 新增:MQTT发送功能
+ void sendLightControlMessage(const QStringList &sns);
+
+protected:
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ // UI组件
+ QVBoxLayout *mainLayout;
+ QSplitter *mainSplitter;
+
+ // 控制面板
+ QGroupBox *controlGroup;
+ QHBoxLayout *controlLayout;
+ QPushButton *selectAllBtn;
+ QPushButton *deselectAllBtn;
+ QPushButton *deleteSelectedBtn;
+ QPushButton *clearAllBtn;
+ QPushButton *refreshBtn;
+ QLineEdit *searchEdit;
+ QLineEdit *manualSnEdit;
+ QPushButton *addSnBtn;
+ QLabel *snCountLabel;
+
+ // SN显示区域
+ QGroupBox *snDisplayGroup;
+ QScrollArea *snScrollArea;
+ QWidget *snContainerWidget;
+ QGridLayout *snGridLayout; // 改为网格布局以支持更好的响应式设计
+
+ // 点亮控制区域
+ QGroupBox *lightControlGroup;
+ QComboBox *colorCombo;
+ QComboBox *flashCombo;
+ QSpinBox *flashIntervalSpin;
+ QSpinBox *lightDurationSpin;
+ QComboBox *soundCombo;
+ QPushButton *sendLightSelectedBtn;
+ QPushButton *sendLightAllBtn;
+ // 删除这一行: QPushButton *randomColorBtn;
+
+ // 数据存储
+ QSettings *settings;
+ QSet snSet;
+
+ // 新增:主窗口引用
+ MainWindow *m_mainWindow;
+
+ // 新增:缺失的成员变量
+ QTimer *resizeTimer; // 调整大小定时器
+ QSet uniqueSnSet; // 唯一SN集合
+ QList lightStripWidgets; // 灯条控件列表
+ QList lightStripCheckBoxes; // 灯条复选框列表
+ int columnsPerRow; // 每行列数
+};
+
+#endif // LIGHTSTRIPMANAGER_H
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..a31759f
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,12 @@
+#include
+#include "mainwindow.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ MainWindow window;
+ window.setWindowIcon(QIcon(":/image/src/tuxi.ico"));
+ window.show();
+
+ return app.exec();
+}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
new file mode 100644
index 0000000..b5c6bf6
--- /dev/null
+++ b/src/mainwindow.cpp
@@ -0,0 +1,1166 @@
+#include "mainwindow.h"
+#include "lightstripmanager.h"
+#include
+#include
+#include
+//#include
+//#include
+//#include
+//#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent), otaOperationPending(false), isUpgradeOperation(true)
+{
+ // 初始化设置对象
+ settings = new QSettings("TuxiApp", "LightStripSN", this);
+
+ 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);
+
+ // 初始化灯条管理器
+ lightStripManager = nullptr;
+
+ // 加载已保存的灯条SN列表
+ loadSnList();
+
+ // 初始化状态
+ updateConnectionStatus(false);
+ currentVersionEdit->setText("请点击获取版本");
+}
+
+MainWindow::~MainWindow()
+{
+}
+
+void MainWindow::setupUI() {
+ setWindowTitle("兔喜Test 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(screenWidth * 0.6));
+ int windowHeight = qMin(900, static_cast(screenHeight * 0.7)); // 增加到75%
+
+ // 确保高度明显大于宽度
+ if (windowHeight < windowWidth * 0.95) {
+ windowHeight = static_cast(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); // 减少边距
+
+ // MQTT连接区域
+ connectionGroup = new QGroupBox("MQTT连接设置", this);
+ connectionGroup->setStyleSheet("QGroupBox { font-weight: bold; font-size: 12px; padding-top: 10px; }");
+ QVBoxLayout *connectionLayout = new QVBoxLayout(connectionGroup);
+ connectionLayout->setSpacing(10);
+
+ // 服务器和端口 - 使用水平布局
+ QHBoxLayout *serverLayout = new QHBoxLayout();
+ serverLayout->addWidget(new QLabel("服务器:"));
+ brokerEdit = new QLineEdit("tx-mqtt.zt-express.com");
+ brokerEdit->setMinimumHeight(30);
+ serverLayout->addWidget(brokerEdit);
+ serverLayout->addWidget(new QLabel("端口:"));
+ portEdit = new QLineEdit("1883");
+ portEdit->setMinimumHeight(30);
+ portEdit->setMaximumWidth(100);
+ serverLayout->addWidget(portEdit);
+ connectionLayout->addLayout(serverLayout);
+
+ // 用户名和密码
+ QHBoxLayout *authLayout = new QHBoxLayout();
+ authLayout->addWidget(new QLabel("用户名:"));
+ usernameEdit = new QLineEdit("TJ251679787196");
+ usernameEdit->setMinimumHeight(30);
+ authLayout->addWidget(usernameEdit);
+ authLayout->addWidget(new QLabel("密码:"));
+ 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);
+ deviceLayout->addWidget(new QLabel("需要测试的设备SN:"));
+ 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();
+ row1Layout->addWidget(new QLabel("颜色:"));
+ colorCombo = new QComboBox();
+ colorCombo->addItems({"8-灭", "1-红", "2-黄", "3-蓝", "4-绿", "5-青", "6-白", "7-紫"});
+ colorCombo->setCurrentIndex(6);
+ colorCombo->setMinimumHeight(30);
+ row1Layout->addWidget(colorCombo);
+
+ row1Layout->addWidget(new QLabel("闪烁:"));
+ 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();
+ row2Layout->addWidget(new QLabel("闪烁间隔(秒):"));
+ flashIntervalSpin = new QSpinBox();
+ flashIntervalSpin->setRange(1, 60);
+ flashIntervalSpin->setValue(4);
+ flashIntervalSpin->setMinimumHeight(30);
+ row2Layout->addWidget(flashIntervalSpin);
+
+ row2Layout->addWidget(new QLabel("点亮时长(秒):"));
+ 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();
+ row3Layout->addWidget(new QLabel("声音:"));
+ soundCombo = new QComboBox();
+ soundCombo->addItems({"0-关闭", "1-开启"});
+ soundCombo->setCurrentIndex(0);
+ soundCombo->setMinimumHeight(30);
+ row3Layout->addWidget(soundCombo);
+
+ row3Layout->addStretch();
+ sendLightAllBtn = new QPushButton("发送全部点亮");
+ sendLightAllBtn->setMinimumHeight(40);
+ 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); // 减少内边距
+
+ // 统计信息和搜索按钮布局
+ 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);
+ snLayout->addLayout(snHeaderLayout);
+
+ // 打开管理器按钮
+ 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();
+ versionLayout->addWidget(new QLabel("当前版本:"));
+ 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("升级到1.1.41", this);
+ otaDowngradeBtn = new QPushButton("降级到1.1.40", 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);
+}
+
+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(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() {
+ statusLabel->setText("MQTT连接成功");
+ statusLabel->setStyleSheet("QLabel { color: green; font-weight: bold; }");
+ updateConnectionStatus(true);
+
+ QString deviceSn = deviceSnEdit->text().trimmed();
+ if (!deviceSn.isEmpty()) {
+ // 订阅原有主题
+ QString responseTopic = QString("iot/10045/%1/response").arg(deviceSn);
+ QString stationTopic = QString("iot/10045/%1/station/report").arg(deviceSn);
+
+ // 订阅light report主题
+ QString lightReportTopic = QString("iot/10045/%1/light/report").arg(deviceSn);
+
+ // 订阅adviceDevice主题(用于搜索灯条响应)
+ QString adviceDeviceTopic = QString("iot/10045/%1/message/adviceDevice").arg(deviceSn);
+
+ QString resourceReportTopic = QString("iot/10045/%1/resource/report").arg(deviceSn);
+
+ mqttClient->subscribe(responseTopic);
+ mqttClient->subscribe(stationTopic);
+ mqttClient->subscribe(lightReportTopic);
+ mqttClient->subscribe(adviceDeviceTopic);
+ mqttClient->subscribe(resourceReportTopic);
+
+ messageDisplay->append(QString("[%1] 已订阅主题: %2")
+ .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
+ .arg(responseTopic));
+ messageDisplay->append(QString("[%1] 已订阅主题: %2")
+ .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
+ .arg(stationTopic));
+ messageDisplay->append(QString("[%1] 已订阅主题: %2")
+ .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
+ .arg(lightReportTopic));
+ messageDisplay->append(QString("[%1] 已订阅主题: %2")
+ .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
+ .arg(adviceDeviceTopic));
+ messageDisplay->append(QString("[%1] 已订阅主题: %2")
+ .arg(QDateTime::currentDateTime().toString("hh:mm:ss"))
+ .arg(resourceReportTopic));
+ }
+}
+
+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);
+
+ // 发送消息
+ 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("---");
+}
+
+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::updateConnectionStatus(bool connected)
+{
+ connectBtn->setEnabled(!connected);
+ disconnectBtn->setEnabled(connected);
+ sendLightAllBtn->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();
+ messageDisplay->append(QString("[DEBUG] 处理灯条SN: %1").arg(sn));
+
+ 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));
+ }
+ }
+
+ 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()
+{
+ // 清空所有灯条widget
+ QLayoutItem *item;
+ while ((item = snHorizontalLayout->takeAt(0)) != nullptr) {
+ delete item->widget();
+ delete item;
+ }
+
+ // 清空复选框列表和SN集合
+ lightStripCheckBoxes.clear();
+ uniqueSnSet.clear();
+
+ // 更新计数显示
+ snCountLabel->setText("已发现灯条: 0 个");
+
+ // 移除对lightStripManager的直接调用
+ // 让灯条管理器独立管理自己的数据
+
+ // 保存设置
+ saveSnList();
+}
+
+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, &QWidget::destroyed,
+ this, &MainWindow::onLightStripManagerClosed);
+
+ // 同步当前的SN列表到管理器
+ for (const QString &sn : uniqueSnSet) {
+ lightStripManager->addSnToList(sn);
+ }
+ }
+
+ 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未连接,无法发送消息");
+ }
+}
diff --git a/src/mainwindow.h b/src/mainwindow.h
new file mode 100644
index 0000000..f99e59b
--- /dev/null
+++ b/src/mainwindow.h
@@ -0,0 +1,149 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include // 添加滚动区域头文件
+#include // 添加复选框头文件
+#include
+#include
+#include "mqttclient.h"
+#include "lightstripmanager.h"
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget *parent = nullptr);
+ ~MainWindow();
+
+ // 新增:公共接口
+ QString getDeviceSn() const;
+ bool isMqttConnected() const;
+ void publishMqttMessage(const QString &topic, const QString &message);
+
+private slots:
+ // MQTT相关槽函数
+ void onConnectClicked();
+ void onDisconnectClicked();
+ void onSendLightAllClicked();
+ void onMessageReceived(const QString &topic, const QString &message);
+ void onMqttConnected();
+ void onMqttDisconnected();
+ void onMqttError(const QString &error);
+
+ // OTA相关槽函数
+ void onOtaUpgradeClicked();
+ void onOtaDowngradeClicked();
+ void onGetVersionClicked();
+
+ // 灯条SN管理相关槽函数
+ void onClearSnListClicked();
+ void onSearchLightStripClicked();
+ void openLightStripManager(); // 新增:打开灯条管理器
+ void onLightStripManagerClosed(); // 新增:灯条管理器关闭时的处理
+
+private:
+ void setupUI();
+ void setupMenuBar();
+ void setupToolBar();
+ void updateConnectionStatus(bool connected);
+ void processOtaMessage(const QJsonObject &otaData);
+ void sendOtaCommand(const QString &version, bool isUpgrade = true);
+
+ // 灯条SN管理相关函数
+ void processLightReportMessage(const QString &message);
+ void addSnToList(const QString &sn);
+ void saveSnList();
+ void loadSnList();
+ void sendSearchLightStripCommand();
+
+ // 添加OTA状态跟踪
+ bool otaOperationPending;
+ QString pendingOtaVersion;
+ bool isUpgradeOperation;
+
+ // 菜单和工具栏
+ QWidget *centralWidget;
+ QVBoxLayout *mainLayout;
+
+ // MQTT连接区域
+ QGroupBox *connectionGroup;
+ QLineEdit *brokerEdit;
+ QLineEdit *portEdit;
+ QLineEdit *usernameEdit;
+ QLineEdit *passwordEdit;
+ QPushButton *connectBtn;
+ QPushButton *disconnectBtn;
+
+ // 设备控制区域
+ QGroupBox *deviceGroup;
+ QLineEdit *deviceSnEdit;
+
+ // 点亮参数控制区域
+ QGroupBox *lightGroup;
+ QComboBox *colorCombo;
+ QComboBox *flashCombo;
+ QSpinBox *flashIntervalSpin;
+ QSpinBox *lightDurationSpin;
+ QComboBox *soundCombo;
+ QPushButton *sendLightAllBtn;
+
+ // 灯条SN管理区域 - 修改为横向布局
+ QGroupBox *snGroup;
+ QScrollArea *snScrollArea; // 添加滚动区域
+ QWidget *snContainerWidget; // 添加容器widget
+ QHBoxLayout *snHorizontalLayout; // 改为横向布局
+ QLabel *snCountLabel;
+ QPushButton *clearSnBtn;
+ QPushButton *searchLightStripBtn;
+
+ // OTA升级区域 - 添加缺失的成员变量
+ QGroupBox *otaGroup;
+ QLineEdit *currentVersionEdit;
+ QPushButton *getVersionBtn;
+ QPushButton *otaUpgradeBtn;
+ QPushButton *otaDowngradeBtn;
+ QProgressBar *otaProgressBar;
+ QLabel *otaStatusLabel;
+
+ // 消息显示区域 - 添加缺失的成员变量
+ QGroupBox *messageGroup;
+ QTextEdit *messageDisplay;
+
+ // 状态栏 - 添加缺失的成员变量
+ QLabel *statusLabel;
+
+ // MQTT客户端 - 添加缺失的成员变量
+ MqttClient *mqttClient;
+
+ // 灯条SN管理相关成员变量
+ QSet uniqueSnSet;
+ QList lightStripCheckBoxes; // 添加复选框列表
+ QSettings *settings;
+ LightStripManager *lightStripManager; // 新增:灯条管理器实例
+ QPushButton *openManagerBtn; // 新增:打开管理器按钮
+};
+
+#endif // MAINWINDOW_H
\ No newline at end of file
diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp
new file mode 100644
index 0000000..8c28aed
--- /dev/null
+++ b/src/mqttclient.cpp
@@ -0,0 +1,404 @@
+#include "mqttclient.h"
+#include
+#include
+#include
+
+MqttClient::MqttClient(QObject *parent)
+ : QObject(parent)
+ , m_socket(new QTcpSocket(this))
+ , m_isConnected(false)
+ , m_username("") // 添加这一行
+ , m_password("") // 添加这一行
+{
+ connect(m_socket, &QTcpSocket::connected, this, &MqttClient::onConnected);
+ connect(m_socket, &QTcpSocket::disconnected, this, &MqttClient::onDisconnected);
+ connect(m_socket, &QTcpSocket::readyRead, this, &MqttClient::onReadyRead);
+ connect(m_socket, QOverload::of(&QAbstractSocket::errorOccurred),
+ this, &MqttClient::onError);
+
+ // 添加心跳定时器
+ m_heartbeatTimer = new QTimer(this);
+ connect(m_heartbeatTimer, &QTimer::timeout, this, &MqttClient::sendHeartbeat);
+}
+
+MqttClient::~MqttClient()
+{
+ if (m_socket->state() == QAbstractSocket::ConnectedState) {
+ m_socket->disconnectFromHost();
+ }
+}
+
+void MqttClient::connectToHost(const QString &host, quint16 port)
+{
+ if (m_socket->state() == QTcpSocket::ConnectedState) {
+ return;
+ }
+
+ m_host = host;
+ m_port = port;
+
+ qDebug() << "连接到MQTT服务器: " << host << ":" << port;
+
+ // 只使用异步连接,避免竞态条件
+ m_socket->connectToHost(host, port);
+}
+
+void MqttClient::sendConnectPacket()
+{
+ QByteArray packet;
+
+ // 可变头部和载荷
+ QByteArray variableHeader;
+ QByteArray payload;
+
+ // 协议名称 - 修改为MQTT 3.1格式
+ variableHeader.append(char(0x00));
+ variableHeader.append(char(0x06)); // 长度改为6
+ variableHeader.append("MQIsdp"); // 协议名改为MQIsdp
+
+ // 协议版本 (3 for MQTT 3.1)
+ variableHeader.append(char(0x03)); // 版本改为3
+
+ // 连接标志
+ quint8 connectFlags = 0x02; // Clean Session
+ if (!m_username.isEmpty()) {
+ connectFlags |= 0x80; // Username flag
+ }
+ if (!m_password.isEmpty()) {
+ connectFlags |= 0x40; // Password flag
+ }
+ variableHeader.append(connectFlags);
+
+ // Keep Alive (60秒)
+ variableHeader.append(char(0x00));
+ variableHeader.append(char(0x3C));
+
+ // 客户端ID
+ QString clientId = QString("QtClient_%1").arg(QDateTime::currentMSecsSinceEpoch());
+ payload.append(static_cast(clientId.length() >> 8));
+ payload.append(static_cast(clientId.length() & 0xFF));
+ payload.append(clientId.toUtf8());
+
+ // 如果有用户名,添加到载荷
+ if (!m_username.isEmpty()) {
+ payload.append(static_cast(m_username.length() >> 8));
+ payload.append(static_cast(m_username.length() & 0xFF));
+ payload.append(m_username.toUtf8());
+ }
+
+ // 如果有密码,添加到载荷
+ if (!m_password.isEmpty()) {
+ payload.append(static_cast(m_password.length() >> 8));
+ payload.append(static_cast(m_password.length() & 0xFF));
+ payload.append(m_password.toUtf8());
+ }
+
+ // 计算剩余长度
+ int remainingLength = variableHeader.length() + payload.length();
+
+ // MQTT固定头部
+ packet.append(char(0x10)); // CONNECT消息类型
+
+ // 编码剩余长度(变长编码)
+ do {
+ quint8 byte = remainingLength % 128;
+ remainingLength = remainingLength / 128;
+ if (remainingLength > 0) {
+ byte = byte | 128;
+ }
+ packet.append(char(byte));
+ } while (remainingLength > 0);
+
+ // 组装完整包
+ packet.append(variableHeader);
+ packet.append(payload);
+
+ // 调试信息
+ qDebug() << "发送MQTT 3.1 CONNECT包,长度:" << packet.length();
+ qDebug() << "协议: MQIsdp v3.1";
+ qDebug() << "用户名:" << m_username;
+ qDebug() << "客户端ID:" << clientId;
+
+ // 发送CONNECT包
+ m_socket->write(packet);
+ m_socket->flush();
+}
+
+// 在mqttclient.cpp中添加以下方法实现:
+
+void MqttClient::setCredentials(const QString &username, const QString &password)
+{
+ m_username = username;
+ m_password = password;
+}
+
+void MqttClient::setUsername(const QString &username)
+{
+ m_username = username;
+}
+
+void MqttClient::setPassword(const QString &password)
+{
+ m_password = password;
+}
+
+void MqttClient::disconnectFromHost()
+{
+ m_heartbeatTimer->stop();
+ if (m_socket->state() == QAbstractSocket::ConnectedState) {
+ // 发送MQTT DISCONNECT报文
+ QByteArray disconnectPacket;
+ disconnectPacket.append(char(0xE0)); // DISCONNECT报文类型
+ disconnectPacket.append(char(0x00)); // 剩余长度为0
+ m_socket->write(disconnectPacket);
+ m_socket->disconnectFromHost();
+ }
+}
+
+bool MqttClient::isConnected() const
+{
+ return m_isConnected;
+}
+
+bool MqttClient::publish(const QString &topic, const QString &message)
+{
+ if (!m_isConnected) {
+ emit errorOccurred("未连接到MQTT服务器。。。");
+ return false;
+ }
+
+ // 构建MQTT PUBLISH报文
+ QByteArray packet;
+ packet.append(char(0x30)); // PUBLISH报文类型
+
+ QByteArray payload;
+ // 添加主题长度和主题
+ payload.append(char(topic.length() >> 8));
+ payload.append(char(topic.length() & 0xFF));
+ payload.append(topic.toUtf8());
+ // 添加消息内容
+ payload.append(message.toUtf8());
+
+ // 修复:使用MQTT变长编码计算剩余长度
+ int remainingLength = payload.length();
+ QByteArray lengthBytes;
+
+ do {
+ quint8 encodedByte = remainingLength % 128;
+ remainingLength = remainingLength / 128;
+ if (remainingLength > 0) {
+ encodedByte = encodedByte | 128;
+ }
+ lengthBytes.append(char(encodedByte));
+ } while (remainingLength > 0);
+
+ // 添加变长编码的剩余长度
+ packet.append(lengthBytes);
+ packet.append(payload);
+
+ qDebug() << "发送PUBLISH消息,主题:" << topic << ",消息长度:" << message.length() << ",总包长度:" << packet.length();
+
+ qint64 written = m_socket->write(packet);
+ return written > 0;
+}
+
+void MqttClient::subscribe(const QString &topic)
+{
+ if (!m_isConnected) {
+ emit errorOccurred("未连接到MQTT服务器");
+ return;
+ }
+
+ // 构建简单的MQTT SUBSCRIBE报文
+ QByteArray packet;
+ packet.append(char(0x82)); // SUBSCRIBE报文类型
+
+ QByteArray payload;
+ payload.append(char(0x00)); // 报文标识符高字节
+ payload.append(char(0x01)); // 报文标识符低字节
+ // 添加主题长度和主题
+ payload.append(char(topic.length() >> 8));
+ payload.append(char(topic.length() & 0xFF));
+ payload.append(topic.toUtf8());
+ payload.append(char(0x00)); // QoS级别
+
+ packet.append(char(payload.length()));
+ packet.append(payload);
+
+ m_socket->write(packet);
+}
+
+void MqttClient::onConnected()
+{
+ qDebug() << "TCP连接已建立,发送MQTT CONNECT包";
+ // TCP连接建立后发送MQTT CONNECT包
+ sendConnectPacket();
+}
+
+void MqttClient::onDisconnected()
+{
+ m_isConnected = false;
+ m_heartbeatTimer->stop();
+ emit disconnected();
+}
+
+void MqttClient::onReadyRead()
+{
+ // 将新数据追加到缓冲区
+ m_receiveBuffer.append(m_socket->readAll());
+
+ // 处理缓冲区中的完整消息
+ while (m_receiveBuffer.length() >= 2) {
+ quint8 messageType = static_cast(m_receiveBuffer[0]) & 0xF0;
+
+ // 解析消息长度
+ int remainingLength = 0;
+ int multiplier = 1;
+ int pos = 1;
+
+ // MQTT变长编码解析
+ do {
+ if (pos >= m_receiveBuffer.length()) {
+ // 长度字段不完整,等待更多数据
+ return;
+ }
+
+ quint8 byte = static_cast(m_receiveBuffer[pos]);
+ remainingLength += (byte & 0x7F) * multiplier;
+ multiplier *= 128;
+ pos++;
+
+ if ((byte & 0x80) == 0) {
+ break; // 长度解析完成
+ }
+ } while (multiplier <= 128 * 128 * 128);
+
+ // 计算完整消息长度
+ int totalMessageLength = pos + remainingLength;
+
+ // 检查是否有完整消息
+ if (m_receiveBuffer.length() < totalMessageLength) {
+ // 消息不完整,等待更多数据
+ return;
+ }
+
+ // 提取完整消息
+ QByteArray completeMessage = m_receiveBuffer.left(totalMessageLength);
+ m_receiveBuffer.remove(0, totalMessageLength);
+
+ // 处理完整消息
+ processCompleteMessage(completeMessage);
+ }
+}
+
+void MqttClient::processCompleteMessage(const QByteArray &data)
+{
+ if (data.length() >= 2) {
+ quint8 messageType = static_cast(data[0]) & 0xF0;
+
+ switch (messageType) {
+ case 0x20: // CONNACK
+ if (data.length() >= 4) {
+ quint8 returnCode = data[3];
+ if (returnCode == 0x00) {
+ m_isConnected = true;
+ m_heartbeatTimer->start(30000); // 30秒心跳
+ emit connected();
+ } else {
+ QString errorMsg;
+ switch (returnCode) {
+ case 0x01:
+ errorMsg = "连接被拒绝:协议版本不支持";
+ break;
+ case 0x02:
+ errorMsg = "连接被拒绝:客户端ID不合法";
+ break;
+ case 0x03:
+ errorMsg = "连接被拒绝:服务器不可用";
+ break;
+ case 0x04:
+ errorMsg = "连接被拒绝:用户名或密码错误";
+ break;
+ case 0x05:
+ errorMsg = "连接被拒绝:未授权";
+ break;
+ default:
+ errorMsg = QString("连接被拒绝:未知错误码 0x%1").arg(returnCode, 2, 16, QChar('0'));
+ break;
+ }
+ emit errorOccurred(errorMsg);
+ }
+ }
+ break;
+ case 0x30: // PUBLISH
+ // 正确解析PUBLISH消息
+ if (data.length() > 4) {
+ // 解析MQTT PUBLISH报文格式
+ int pos = 2; // 跳过固定头部
+
+ // 读取主题长度(2字节,大端序)
+ if (pos + 2 <= data.length()) {
+ quint16 topicLength = (static_cast(data[pos]) << 8) | static_cast(data[pos + 1]);
+ pos += 2;
+
+ // 读取主题
+ if (pos + topicLength <= data.length()) {
+ QString topic = QString::fromUtf8(data.mid(pos, topicLength));
+ pos += topicLength;
+
+ // 读取消息内容
+ QString message = QString::fromUtf8(data.mid(pos));
+
+ emit messageReceived(topic, message);
+ } else {
+ // 如果解析失败,使用原来的方式
+ emit messageReceived("incoming/topic", QString::fromUtf8(data.mid(4)));
+ }
+ } else {
+ emit messageReceived("incoming/topic", QString::fromUtf8(data.mid(4)));
+ }
+ }
+ break;
+ case 0xD0: // PINGRESP
+ // 心跳响应,无需处理
+ break;
+ }
+ }
+}
+
+void MqttClient::onError(QAbstractSocket::SocketError error)
+{
+ QString errorString;
+ switch (error) {
+ case QAbstractSocket::ConnectionRefusedError:
+ errorString = "连接被拒绝";
+ break;
+ case QAbstractSocket::RemoteHostClosedError:
+ errorString = "远程主机关闭连接";
+ break;
+ case QAbstractSocket::HostNotFoundError:
+ errorString = "主机未找到";
+ break;
+ case QAbstractSocket::SocketTimeoutError:
+ errorString = "连接超时";
+ break;
+ default:
+ errorString = QString("网络错误: %1").arg(m_socket->errorString());
+ break;
+ }
+
+ m_isConnected = false;
+ m_heartbeatTimer->stop();
+ emit errorOccurred(errorString);
+}
+
+void MqttClient::sendHeartbeat()
+{
+ if (m_isConnected && m_socket->state() == QAbstractSocket::ConnectedState) {
+ // 发送MQTT PINGREQ报文
+ QByteArray pingPacket;
+ pingPacket.append(char(0xC0)); // PINGREQ报文类型
+ pingPacket.append(char(0x00)); // 剩余长度为0
+ m_socket->write(pingPacket);
+ }
+}
\ No newline at end of file
diff --git a/src/mqttclient.h b/src/mqttclient.h
new file mode 100644
index 0000000..82e8368
--- /dev/null
+++ b/src/mqttclient.h
@@ -0,0 +1,59 @@
+#ifndef MQTTCLIENT_H
+#define MQTTCLIENT_H
+
+#include
+#include
+#include
+
+class MqttClient : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit MqttClient(QObject *parent = nullptr);
+ ~MqttClient();
+
+ // 添加公共方法
+ bool isConnected() const;
+
+public slots:
+ void connectToHost(const QString &host, quint16 port);
+ void setCredentials(const QString &username, const QString &password);
+ void setUsername(const QString &username);
+ void setPassword(const QString &password);
+ void disconnectFromHost(); // 修正方法名
+ bool publish(const QString &topic, const QString &message); // 修正参数类型
+ void subscribe(const QString &topic);
+
+// 添加缺失的信号声明
+signals:
+ void connected();
+ void disconnected();
+ void errorOccurred(const QString &error);
+ void messageReceived(const QString &topic, const QString &message);
+ void connectionFailed(const QString &error);
+
+private slots:
+ void onConnected();
+ void onDisconnected();
+ void onReadyRead();
+ void onError(QAbstractSocket::SocketError error); // 将 onErrorOccurred 改为 onError
+ void sendHeartbeat();
+
+private:
+ void sendConnectPacket();
+ void processCompleteMessage(const QByteArray &data); // 新增方法
+ QTcpSocket *m_socket;
+ QString m_host;
+ quint16 m_port;
+ QString m_username;
+ QString m_password;
+ QString m_clientId;
+ bool m_isConnected;
+ QTimer *m_heartbeatTimer;
+
+ // 添加消息缓冲区
+ QByteArray m_receiveBuffer;
+};
+
+#endif // MQTTCLIENT_H
\ No newline at end of file
diff --git a/src/resources.qrc b/src/resources.qrc
new file mode 100644
index 0000000..022842d
--- /dev/null
+++ b/src/resources.qrc
@@ -0,0 +1,5 @@
+
+
+ tuxi.png
+
+
diff --git a/src/tuxi.ico b/src/tuxi.ico
new file mode 100644
index 0000000..28fab1e
Binary files /dev/null and b/src/tuxi.ico differ
diff --git a/src/tuxi.png b/src/tuxi.png
new file mode 100644
index 0000000..7471b47
Binary files /dev/null and b/src/tuxi.png differ
diff --git a/windows-toolchain.cmake b/windows-toolchain.cmake
new file mode 100644
index 0000000..5afab03
--- /dev/null
+++ b/windows-toolchain.cmake
@@ -0,0 +1,16 @@
+set(CMAKE_SYSTEM_NAME Windows)
+set(CMAKE_SYSTEM_PROCESSOR x86_64)
+
+# 设置交叉编译工具链
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+
+# 设置Qt6路径(使用aqtinstall下载的版本)
+set(CMAKE_PREFIX_PATH "/home/hyx/work/qt/6.5.0/mingw_64")
+
+# 设置查找路径
+set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32 /home/hyx/work/qt/6.5.0/mingw_64)
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
\ No newline at end of file