最后更新:2026-04-21(断联检测 UI 控制 + 配置持久化)
- CMakeLists 集成 Paho MQTT C++
- MqttClient 封装(连接、订阅、发布)
- MessageBuffer 线程安全消息队列
- CMakePresets.json 替代 configure.bat / build.bat(VS Code CMake Tools 集成)
- 编译验证通过(Ninja + MSVC + Qt MOC/UIC 正常运行)
说明
- 构建工具链:CMake → Ninja → Qt MOC/UIC + MSVC cl.exe
- vcpkg 管理依赖:Qt5 Widgets、PahoMqttCpp(x64-windows triplet)
- 已加
PAHO_MQTTPP_IMPORTS解决 MSVC dllimport 链接问题
- MqttBridge(Paho 回调 → Qt 信号跨线程转发)
- ConfigPanel(连接配置面板,含输入校验,基于 .ui 文件)
- ConfigPanel 内部布局:QSplitter(左 300px 配置表单 + 右 QPlainTextEdit 消息列表)
- ConfigPanel 新增
appendMessage(topic, payload)public slot - 消息格式:
[hh:mm:ss.zzz] topic+ 换行 + payload(simplified 压缩为单行) - MainWindow 主框架:navBar(配置/设备按钮,72px)+ QStackedWidget
- MqttBridge 信号直连 ConfigPanel::appendMessage(移除 MainWindow 中转)
- DeviceView 占位类(QStackedWidget index 1)
- MSVC
/utf-8 /wd4819编译选项
涉及文件
src/bridge/MqttBridge.h / .cpp ✅
src/ui/ConfigPanel.h / .cpp / .ui ✅ QSplitter 重构
src/ui/DeviceView.h / .cpp ✅ 占位
mainwindow.h / .cpp ✅ navBar + QStackedWidget
CMakeLists.txt ✅
{
"device_id": "device001",
"name": "温控器-1号",
"status": "online",
"timestamp": 1712750000,
"data": {
"temperature": 25.3,
"humidity": 60
}
}| 字段 | 类型 | 说明 |
|---|---|---|
device_id |
string | 设备唯一标识 |
name |
string | 可读名称(卡片标题) |
status |
string | online / offline / error |
timestamp |
number | Unix 时间戳(秒) |
data |
object | 业务 KV,value 为 number 或 string |
- Topic 格式:
devices/<device_id>/status - 离线方式:设备主动发
status: offline,不依赖心跳超时
-
Step 1 新建
DeviceCard(src/ui/DeviceCard.h / .cpp)- QFrame 固定尺寸
- 顶部:设备名(粗体)+ 状态色块 Label(在线绿 / 离线红 / 未知黄)
- 下方:
dataKV 用 QFormLayout 逐行展示 - 公开方法:
void update(const QJsonObject& obj)
-
Step 2 改造
DeviceView(src/ui/DeviceView.h / .cpp)- 替换占位 Label,改为 QScrollArea + QGridLayout(每行 3 列)
- 新增
updateDevice(const QString& topic, const QString& payload)public slot- 解析 JSON(QJsonDocument)
- 按
device_id查找已有 DeviceCard → 调用 update(),或新建并插入网格
-
Step 3
mainwindow.cpp加一条 connectonConnectRequested中,mqttBridge_ 信号额外连接到deviceView_->updateDevice
-
Step 4
CMakeLists.txt添加DeviceCard.h / .cpp -
Step 5 手动添加/删除设备
- 新建
AddDeviceDialog(src/ui/AddDeviceDialog.h / .cpp):填写设备 ID + 名称 - DeviceView 顶部工具栏:
+ 添加设备按钮 - DeviceCard 右键菜单:
删除设备,触发removeRequested信号
- 新建
-
Step 6 自动发现
- 收到新
device_id的消息时自动生成卡片(配合通配符订阅如devices/#)
- 收到新
-
Step 7 多选设备 + 批量指令群发
- DeviceCard 左键点击切换选中状态(蓝色边框高亮),发出
selectionChanged信号 - DeviceView 底部指令面板(选中后显示):Topic / Payload / QoS 输入 + 发送按钮
- Topic 支持
{device_id}占位符,批量发送时自动替换为各设备 ID - MainWindow 新增
onPublishRequested槽,调用MqttClient::publish()
- DeviceCard 左键点击切换选中状态(蓝色边框高亮),发出
涉及文件
src/ui/DeviceCard.h / .cpp ✅ 含选中状态、右键菜单
src/ui/DeviceView.h / .cpp ✅ 卡片网格 + 底部指令面板
src/ui/AddDeviceDialog.h / .cpp ✅ 新建
mainwindow.h / .cpp ✅ onPublishRequested 槽
CMakeLists.txt ✅ 新增 AddDeviceDialog 源文件
- 新建
src/ui/dark.qss:Catppuccin Mocha 暗黑主题,覆盖所有控件(Button / LineEdit / PlainTextEdit / ComboBox / GroupBox / ScrollBar / StatusBar 等) - 新建
resources.qrc,将 QSS 嵌入可执行文件 -
main.cpp:设置 Fusion 风格基底 + 从资源加载全局样式表;窗口初始尺寸改为 1100×680 -
mainwindow.cpp:navBar 设objectName("navBar")供 QSS 精确定位;导航按钮改为setCheckable+QButtonGroup互斥,切换页面时自动高亮当前项;调用DwmSetWindowAttribute(DWMWA_USE_IMMERSIVE_DARK_MODE)使 Win11 原生标题栏同步变黑 -
DeviceCard.cpp:设objectName("DeviceCard"),选中边框改为#89b4fa(蓝),非选中状态回退到全局 QSS;状态 badge 颜色与暗色背景对比度调整(在线绿 / 离线红 / 未知黄) -
ConfigPanel.ui:错误提示 Label 颜色改为#f38ba8(玫瑰红,适配暗色背景) -
CMakeLists.txt:添加resources.qrc;MSVC 分支链接dwmapi
涉及文件
src/ui/dark.qss ✅ 新建
resources.qrc ✅ 新建
main.cpp ✅ Fusion + QSS 加载
mainwindow.cpp ✅ navBar / 互斥按钮 / DWM 暗色标题栏
src/ui/DeviceCard.cpp ✅ objectName + 选中/状态样式
src/ui/ConfigPanel.ui ✅ 错误标签颜色
CMakeLists.txt ✅ resources.qrc + dwmapi
- 新建
ConfigStore(src/core/ConfigStore.h / .cpp):JSON 读写,Profile 增删改查,存储路径%APPDATA%\MqttMonitor\profiles.json -
ConfigPanel.ui:GroupBox 顶部新增配置选择下拉框(profileCombo)+ 保存/删除按钮 -
ConfigPanel.h / .cpp:集成 ConfigStore,实现配置切换/保存/删除逻辑;连接时锁定下拉框防误操作;启动时自动恢复上次使用的配置 -
CMakeLists.txt:添加 ConfigStore.h / .cpp
涉及文件
src/core/ConfigStore.h / .cpp ✅ 新建
src/ui/ConfigPanel.ui ✅ 顶部加配置选择行
src/ui/ConfigPanel.h / .cpp ✅ 集成 ConfigStore
CMakeLists.txt ✅ 新增源文件
- 新建
CardRuleConfig(src/core/CardRuleConfig.h):纯数据结构体,配置device_id/name/status/data字段名及在线/离线状态值,默认值与原硬编码一致 - 新建
CardRuleStore(src/core/CardRuleStore.h / .cpp):仿 ConfigStore 模式,JSON 持久化至%APPDATA%\MqttMonitor\card_rules.json,temp-file 原子写入,load 容错 - 新建
CardRuleDialog(src/ui/CardRuleDialog.h / .cpp):模态 QDialog,QFormLayout 6 行(4 个字段名 + 在线值 + 离线值),底部提示修改仅对新消息生效 -
DeviceView:工具栏新增"卡片规则"按钮;构造时 load 配置;updateDevice()加翻译层,用配置字段名读 JSON 并将状态值归一化为内部固定 key 再传给 DeviceCard;DeviceCard 本身不改动 -
CMakeLists.txt:添加 5 个新文件
涉及文件
src/core/CardRuleConfig.h ✅ 新建
src/core/CardRuleStore.h / .cpp ✅ 新建
src/ui/CardRuleDialog.h / .cpp ✅ 新建
src/ui/DeviceView.h / .cpp ✅ 翻译层 + 按钮
CMakeLists.txt ✅ 新增源文件
- 新建
CmdPreset(src/core/CmdPreset.h):纯数据结构体,存储预设的 name/topic/payload/qos - 新建
CmdPresetStore(src/core/CmdPresetStore.h / .cpp):JSON 持久化至%APPDATA%\MqttMonitor\cmd_presets.json;addPreset()以 topic 为基础命名,重名自动加-2/-3后缀;额外存储 lastTopic/lastPayload/lastQos 用于跨重启恢复 -
DeviceView:指令面板顶部新增预设行(下拉框 + 保存 + 删除);构造时恢复 lastUsed 到输入框;选择预设自动填入 topic/payload/qos;字段变化实时写入 lastUsed
涉及文件
src/core/CmdPreset.h ✅ 新建
src/core/CmdPresetStore.h / .cpp ✅ 新建
src/ui/DeviceView.h / .cpp ✅ 预设行 + lastUsed 恢复
CMakeLists.txt ✅ 新增源文件
设计要点
- 断联判断:三重条件(30秒超时 / MQTT断连 / status=offline),任意一个触发即断联
- UI 样式:卡片红色边框 + 状态 badge 显示"断联",恢复后还原断联前的状态(online/offline)
- 检测实现:懒惰轮询(1秒检查一次,仅检查 checkQueue_ 中的设备),支持大量设备
- 恢复逻辑:新消息到达立即恢复,无延迟确认
- MQTT 断连:杀掉 Broker 进程触发
connection_lost回调,所有设备立即断联,状态栏提示
实测验证(2026-04-21)
- ✅ 30 秒无消息后设备自动显示断联(红边框 + badge "断联")
- ✅ Kill Mosquitto 进程后所有设备立即断联,状态栏显示 "MQTT Connection Lost"
- ✅ 重新发送消息后卡片恢复断联前状态
实施任务列表
-
Task #1 定义断联状态数据结构和 DeviceView 基础改造
- 定义 DeviceState 结构体(deviceId / lastMessageTime / isDisconnected)
- 在 DeviceView 添加
QMap<QString, DeviceState> deviceStates_ - 在 DeviceView 添加
QSet<QString> checkQueue_(待检查队列) - 更新 addCard() 方法初始化 DeviceState
-
Task #2 实现懒惰轮询定时器和检测逻辑
- 添加
QTimer disconnectCheckTimer_ - 实现
checkDisconnectedDevices()槽(检查 checkQueue 中超时的设备) - 实现
markDeviceDisconnected()和markDeviceConnected()方法
- 添加
-
Task #3 更新消息处理流程,集成超时检测
- 在
updateDevice()中更新 lastMessageTime - 在
updateDevice()中处理设备恢复逻辑 - 在
updateDevice()中决定是否加入 checkQueue
- 在
-
Task #4 处理 MQTT 连接断开事件
- 连接 MqttBridge::connectionLost 信号
- 在
onMqttConnectionLost()中标记所有设备为断联
-
Task #5 DeviceCard 添加断联状态支持和样式
- 添加 Q_PROPERTY(bool disconnected)
- 实现
setDisconnected(bool)方法 - 实现
isDisconnected()查询方法
-
Task #6 更新 dark.qss 样式表支持断联指示
- 添加
DeviceCard[disconnected="true"]选择器 - 定义红色边框或灰化背景样式
- 添加
-
Task #7 启动/停止轮询定时器的生命周期管理
- 在 MainWindow 中管理定时器启动/停止
- 在连接时启动,断开时停止
-
Task #8 处理设备删除时的状态清理
- 在
removeDevice()中删除 deviceStates_[deviceId] - 从 checkQueue_ 中移除 deviceId
- 在
-
Task #9 编译验证和集成测试
- 编译验证无错误
- 测试超时检测(30 秒无消息后显示断联)
- 测试 MQTT 断连(Kill Mosquitto 进程,所有设备立即断联)
- 测试恢复逻辑(新消息到达立即恢复)
涉及文件预期
src/ui/DeviceView.h / .cpp ✅ 数据结构 + 轮询逻辑
src/ui/DeviceCard.h / .cpp ✅ 断联状态支持
src/ui/dark.qss ✅ 断联样式
src/bridge/MqttBridge.h / .cpp ✅ connectionLost 信号
mainwindow.h / .cpp ✅ 定时器生命周期
CMakeLists.txt ✅ 验证编译
设计要点
- 启用/禁用开关(QCheckBox):默认关闭,用户手动勾选启用
- 超时时间设置(QSpinBox):范围 1-300 秒,默认 30 秒
- 配置持久化:保存到
%APPDATA%\MqttMonitor\disconnect_detection.json - 启动行为:读取上次保存的配置状态,自动恢复
- 断开连接时:自动停止检测定时器,但保留配置状态
- 重新连接时:用户根据复选框状态手动启动(如果上次启用了)
实施要点
- 新建
DisconnectDetectionConfig类:管理 enabled 状态和 timeoutSeconds,JSON 持久化 - 在 DeviceView 工具栏添加两个控件:
- QCheckBox "启用断联检测"
- QSpinBox 超时秒数(1-300,默认 30)
- 修改
checkDisconnectedDevices()方法:使用timeoutMs_成员变量替代硬编码的 30000 - 移除 mainwindow.cpp 中的自动启动逻辑:
- 移除连接时自动调用
startDisconnectDetection() - 保留断开时调用
stopDisconnectDetection()(断连后停止检测)
- 移除连接时自动调用
- 构造函数自动启动:如果上次保存的配置为启用状态,自动启动检测
操作流程
- 启动应用 → 读取配置 → 根据配置决定是否显示"启用断联检测"为勾选状态
- 用户勾选 → 启动检测定时器 → 配置立即保存
- 用户取消勾选 → 停止检测定时器 → 配置立即保存
- 修改超时秒数 → 新值立即生效并保存
涉及文件
src/core/DisconnectDetectionConfig.h / .cpp ✅ 新建
src/ui/DeviceView.h / .cpp ✅ UI 控件 + 槽函数
mainwindow.h / .cpp ✅ 移除自动启动逻辑
CMakeLists.txt ✅ 新增 DisconnectDetectionConfig
- 日志模块
- 数据库模块