Initial commit: Qt MQTT Light Strip Manager

This commit is contained in:
zzh 2025-09-11 09:35:03 +08:00
commit e84cef6c95
17 changed files with 3065 additions and 0 deletions

57
.gitignore vendored Normal file
View File

@ -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/

54
CMakeLists.txt Normal file
View File

@ -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)
# QtMOCUICRCC
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()

64
README.md Normal file
View File

@ -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消息和系统日志显示在消息区域
- 包含时间戳和详细信息
## 项目结构

BIN
logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

1
logo.rc Normal file
View File

@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "logo.ico"

5
resources.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/image">
<file>src/tuxi.ico</file>
</qresource>
</RCC>

939
src/lightstripmanager.cpp Normal file
View File

@ -0,0 +1,939 @@
#include "lightstripmanager.h"
#include <QThread>
// 如果不再需要可以删除: #include <QRandomGenerator>
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<QWidget*>(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<QLabel*>();
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<QLabel*>();
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<QLabel*>();
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);
}
}

134
src/lightstripmanager.h Normal file
View File

@ -0,0 +1,134 @@
#ifndef LIGHTSTRIPMANAGER_H
#define LIGHTSTRIPMANAGER_H
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QScrollArea>
#include <QCheckBox>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QSpinBox>
#include <QComboBox>
#include <QSettings>
#include <QSet>
#include <QList>
#include <QMessageBox>
#include <QScreen>
#include <QGuiApplication>
#include <QTimer>
#include <QGridLayout>
#include <QSplitter>
#include <QResizeEvent>
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<QString> snSet;
// 新增:主窗口引用
MainWindow *m_mainWindow;
// 新增:缺失的成员变量
QTimer *resizeTimer; // 调整大小定时器
QSet<QString> uniqueSnSet; // 唯一SN集合
QList<QWidget*> lightStripWidgets; // 灯条控件列表
QList<QCheckBox*> lightStripCheckBoxes; // 灯条复选框列表
int columnsPerRow; // 每行列数
};
#endif // LIGHTSTRIPMANAGER_H

12
src/main.cpp Normal file
View File

@ -0,0 +1,12 @@
#include <QApplication>
#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();
}

1166
src/mainwindow.cpp Normal file

File diff suppressed because it is too large Load Diff

149
src/mainwindow.h Normal file
View File

@ -0,0 +1,149 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
#include <QGroupBox>
#include <QStatusBar>
#include <QSpinBox>
#include <QComboBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QProgressBar>
#include <QJsonObject>
#include <QJsonDocument>
#include <QTimer>
#include <QListWidget>
#include <QJsonArray>
#include <QSettings>
#include <QSet>
#include <QDateTime>
#include <QScrollArea> // 添加滚动区域头文件
#include <QCheckBox> // 添加复选框头文件
#include <QScreen>
#include <QGuiApplication>
#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<QString> uniqueSnSet;
QList<QCheckBox*> lightStripCheckBoxes; // 添加复选框列表
QSettings *settings;
LightStripManager *lightStripManager; // 新增:灯条管理器实例
QPushButton *openManagerBtn; // 新增:打开管理器按钮
};
#endif // MAINWINDOW_H

404
src/mqttclient.cpp Normal file
View File

@ -0,0 +1,404 @@
#include "mqttclient.h"
#include <QDebug>
#include <QDateTime>
#include <QTimer>
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<QAbstractSocket::SocketError>::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<char>(clientId.length() >> 8));
payload.append(static_cast<char>(clientId.length() & 0xFF));
payload.append(clientId.toUtf8());
// 如果有用户名,添加到载荷
if (!m_username.isEmpty()) {
payload.append(static_cast<char>(m_username.length() >> 8));
payload.append(static_cast<char>(m_username.length() & 0xFF));
payload.append(m_username.toUtf8());
}
// 如果有密码,添加到载荷
if (!m_password.isEmpty()) {
payload.append(static_cast<char>(m_password.length() >> 8));
payload.append(static_cast<char>(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<quint8>(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<quint8>(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<quint8>(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<quint8>(data[pos]) << 8) | static_cast<quint8>(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);
}
}

59
src/mqttclient.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef MQTTCLIENT_H
#define MQTTCLIENT_H
#include <QObject>
#include <QTcpSocket>
#include <QTimer>
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

5
src/resources.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/image">
<file>tuxi.png</file>
</qresource>
</RCC>

BIN
src/tuxi.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
src/tuxi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

16
windows-toolchain.cmake Normal file
View File

@ -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)