Initial commit: Qt MQTT Light Strip Manager
This commit is contained in:
commit
e84cef6c95
57
.gitignore
vendored
Normal file
57
.gitignore
vendored
Normal 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
54
CMakeLists.txt
Normal 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)
|
||||
|
||||
# 启用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()
|
||||
64
README.md
Normal file
64
README.md
Normal 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消息和系统日志显示在消息区域
|
||||
- 包含时间戳和详细信息
|
||||
|
||||
## 项目结构
|
||||
5
resources.qrc
Normal file
5
resources.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/image">
|
||||
<file>src/tuxi.ico</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
939
src/lightstripmanager.cpp
Normal file
939
src/lightstripmanager.cpp
Normal 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
134
src/lightstripmanager.h
Normal 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
12
src/main.cpp
Normal 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
1166
src/mainwindow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
149
src/mainwindow.h
Normal file
149
src/mainwindow.h
Normal 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
404
src/mqttclient.cpp
Normal 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
59
src/mqttclient.h
Normal 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
5
src/resources.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/image">
|
||||
<file>tuxi.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
BIN
src/tuxi.ico
Normal file
BIN
src/tuxi.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
BIN
src/tuxi.png
Normal file
BIN
src/tuxi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
16
windows-toolchain.cmake
Normal file
16
windows-toolchain.cmake
Normal 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)
|
||||
Loading…
Reference in New Issue
Block a user