基于 Qt + Paho MQTT C++ 的通用 MQTT 设备监控客户端。
MqttMonitor 是一个运行在 Windows 桌面端的 MQTT 客户端上位机,面向需要监控多台 IoT 设备的调试/运维人员。
它不绑定任何特定设备类型或 Topic 格式,用户可以自由配置订阅规则,程序负责收集消息、展示状态、下发指令。
- 填写 Broker 地址、端口、用户名/密码、ClientID、KeepAlive 等连接参数
- 配置全局订阅 Topic(支持通配符,如
devices/#) - 配置持久化,支持多套环境切换(如测试环境 / 生产环境)
设备有两种来源:
| 来源 | 说明 |
|---|---|
| 手动添加 | 用户填写设备名称及其关注的 Topic 列表 |
| 自动发现 | 程序监听全局订阅 Topic,有新消息来源时自动创建设备卡片 |
自动发现的设备以消息来源 Topic 作为初始名称,用户可后续重命名。
右侧主区域以卡片网格展示所有设备,每张卡片显示:
- 设备名称
- 在线状态(在线 🟢 / 离线 🔴 / 未知 🟡)
- 最后一条消息的首个字段预览
- 最后消息时间戳
点击工具栏"卡片规则"按钮可自定义 JSON 字段名映射及在线/离线状态值,配置本地持久化。
- 点击单个设备卡片 → 查看该设备的消息详情(原始 Payload + JSON 自动解析展开)
- 多选设备 → 向所选设备批量下发指令(群发)
- 指令下发:选择目标 Topic、填写 Payload、选择 QoS,发送
- 原始层:始终保留原始 Payload,便于调试
- 解析层:若 Payload 为合法 JSON,自动展开为键值对
- 非 JSON 格式(纯文本、十六进制)同样支持展示,不强制约定格式
┌──────────┬──────────────────────────────────────────┐
│ │ │
│ 配置 │ [设备A 🟢] [设备B 🔴] [设备C 🟢] │
│ │ │
│ │ [设备D 🟢] [设备E 🟡] [未知设备 🟢] │
│ 设备 │ │
│ │ [设备G 🔴] [设备H 🟢] [...] │
│ │ │
└──────────┴──────────────────────────────────────────┘
左侧导航:配置 / 设备两个页面入口
右侧内容区:当前页面的主体内容
| 层 | 技术 |
|---|---|
| UI 框架 | Qt 5(Widgets,Fusion 风格 + 自定义 QSS 暗黑主题) |
| MQTT 通信 | Eclipse Paho MQTT C++ |
| 构建系统 | CMake + vcpkg |
| 平台 | Windows 11 |
UI 层
MainWindow / ConfigPanel / DeviceGridView / DeviceDetailView
↕ Qt 信号槽(QueuedConnection)
Bridge 层
MqttBridge(QObject,负责线程安全的消息转发)
↕ 回调注册
Core 层
MqttClient(封装 Paho,管理连接/订阅/发布)
MessageBuffer(线程安全消息队列,批量推送给 UI)
Paho 的消息回调运行在子线程,Bridge 层通过 Qt::QueuedConnection 将消息安全投递至主线程,避免直接操作 UI。
设备被标记为"断联"的条件:
| 条件 | 来源 | 说明 |
|---|---|---|
| 时间超时 | 本地计时 | 设备超过 30 秒未收到消息 |
| MQTT 连接断开 | MqttBridge.connectionLost 信号 | Broker 连接丢失时,所有设备标记断联 |
| status 字段 | 消息 JSON | 设备主动上报 status: "offline" 时 |
判定逻辑: 只要满足上述任意一个条件,设备即刻被标记为断联(无需多条件同时满足)。
断联状态在 DeviceCard 上以样式标记显示(不覆盖原有 status 显示):
- 保留原 status 字段显示:卡片继续展示消息中的 status 值
- 应用断联视觉标记:边框/背景改为灰化或红色提示样式(如
border: 2px solid #ff6b6b) - QSS 实现:使用选择器
DeviceCard[disconnected="true"]精确定位样式
为支持大量设备的高效检测,采用懒惰轮询策略:
- 轮询定时器:QTimer,每 1 秒触发一次检查
- 待检查队列:维护
QSet<QString> checkQueue_,仅包含"距上次消息已 > 20 秒"的设备- 新消息到达:若该设备距离上次消息时间已 > 20 秒,自动加入待检查队列
- 轮询时:仅检查队列中的设备,计算
当前时间 - lastMessageTime,若 > 30 秒则标记断联
- 性能特性:即使有 1000+ 设备,也仅检查活跃设备的小规模子集,性能开销恒定
- 立即恢复:设备一旦收到新消息、或 MQTT 重连、或 status ≠ offline,立即取消断联标记
- 无需延迟确认:消息本身已代表设备活着,无抖动风险
DeviceView 维护每个设备的状态:
struct DeviceState {
QString deviceId;
qint64 lastMessageTime; // 最后收消息的时间戳(毫秒)
bool isDisconnected; // 当前是否已标记为断联
};- CMakeLists.txt 集成 Paho MQTT C++
- MqttClient 封装:连接、订阅、发布
- MessageBuffer 线程安全消息队列
- CMakePresets.json(VS Code CMake Tools 集成)
- ConfigPanel:Broker 参数填写,内置 QSplitter(左配置表单 + 右消息列表)
- MqttBridge:Paho 回调 → Qt 信号跨线程安全传递
- MainWindow:navBar(配置/设备)+ QStackedWidget 双页结构
- DeviceView 占位类(待 Phase 3 实现)
- 设备消息 JSON 结构确定(device_id / name / status / timestamp / data)
- DeviceView 实现(QScrollArea + QGridLayout 卡片网格)
- DeviceCard 类(展示 name / status / data KV)
- JSON 解析(Qt5 QJsonDocument)
- 手动添加/删除设备(对话框填写 ID + 名称;右键菜单删除)
- 自动发现:收到新 device_id 消息时自动生成卡片(配合通配符订阅如
devices/#) - 多选设备 + 指令群发(左键点击卡片选中/取消;底部面板填写 Topic / Payload / QoS 批量发送;Topic 支持
{device_id}占位符)
- Catppuccin Mocha 暗黑主题(全局 QSS)
- Win11 原生标题栏暗色(DWM API)
- 导航按钮互斥高亮
- 多套 Broker 配置切换:命名配置存档,启动自动恢复,JSON 持久化至
%APPDATA%\MqttMonitor\profiles.json - 卡片规则配置:JSON 字段名(device_id / name / status / data)及状态值(online / offline)均可自定义,规则持久化至
%APPDATA%\MqttMonitor\card_rules.json,修改仅对新消息生效 - 指令预设:群发面板支持保存命名预设(以 topic 命名,重名加
-2/-3后缀),持久化至%APPDATA%\MqttMonitor\cmd_presets.json;上次使用的 topic/payload/QoS 跨重启自动恢复 - 设备断联检测(设计阶段):自动检测设备离线状态,基于组合判断(见下方详述)
- 日志模块:消息历史记录到本地文件
- 数据库模块:SQLite 持久化 + 历史消息查询
MqttMonitor/
├── CMakeLists.txt
├── CMakePresets.json
├── README.md
├── PROGRESS.md
├── main.cpp
├── mainwindow.h / .cpp / .ui
└── src/
├── core/
│ ├── MqttClient.h / .cpp # Paho 封装
│ ├── MessageBuffer.h / .cpp # 线程安全消息队列
│ ├── ConfigStore.h / .cpp # MQTT 配置持久化
│ ├── CardRuleConfig.h # 卡片规则数据结构
│ ├── CardRuleStore.h / .cpp # 卡片规则持久化
│ ├── CmdPreset.h # 指令预设数据结构
│ └── CmdPresetStore.h / .cpp # 指令预设持久化
├── bridge/
│ └── MqttBridge.h / .cpp # Paho 回调 → Qt 信号
└── ui/
├── ConfigPanel.h / .cpp / .ui # 配置页(含消息列表)
├── DeviceView.h / .cpp # 设备页(卡片网格 + 指令面板 + 预设)
├── DeviceCard.h / .cpp # 设备卡片(状态展示 + 多选)
├── AddDeviceDialog.h / .cpp # 手动添加设备对话框
└── CardRuleDialog.h / .cpp # 卡片规则配置对话框
devices/<device_id>/status
device_id 同时冗余在 payload JSON 中,便于解析。
{
"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 |
- 设备离线时主动发送
status: offline消息,不依赖心跳超时 data字段结构自由,程序以 KV 列表形式展示在卡片下方
在 MQTTX 的定时发送功能中使用以下脚本,可模拟设备持续上报温湿度数据:
function handlePayload(value) {
let msg = typeof value === 'string' ? JSON.parse(value) : value;
msg.timestamp = Date.now();
msg.data = {
temperature: +(20 + Math.random() * 15).toFixed(1), // 模拟 20-35 度
humidity: Math.floor(40 + Math.random() * 30) // 模拟 40-70% 湿度
};
return JSON.stringify(msg, null, 2);
}
execute(handlePayload);使用方式:
- 在 MQTTX 新建连接,Topic 填写
<device_id>/status - Payload 填写包含完整字段的初始 JSON(
device_id、name、status等固定字段在此填写) - 开启定时发送,选择上方脚本,脚本会自动覆盖
timestamp和data字段