ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用 Qt 构建工业泵站监控 HMI:从 Modbus TCP 数据采集到实时趋势曲线与报警系统

2026/9/25 12:01:22 拓冰建站 浏览量
用 Qt 构建工业泵站监控 HMI:从 Modbus TCP 数据采集到实时趋势曲线与报警系统 教程文档【免费下载链接】easy-vibe从 0 到 1 学会 vibe coding项目制学习项目地址https://gitcode.com/datawhalechina/easy-vibe点击查看免费下载本指南以 docs/fr-fr/stage-3/cross-platform/qt-industrial-hmi/index.md 为核心带你从零搭建一套「工业级」泵站监控上位机HMI通过 Qt 官方模块QModbusTcpClient从 Modbus 从站读取传感器数据、用 Qt Charts 绘制压力实时趋势、在压力越限时弹出报警并写入 SQLite 故障日志最后用 Qt 官方部署工具打成独立可执行程序。全程使用免费的 Modbus Slave 模拟器充当虚拟水泵无需任何真实工业硬件。读完本文你将掌握上位机/下位机通信模型、Modbus 读写原理、Qt Widgets 上位机的最小可运行架构以及真实工厂里数据采集 → 趋势展示 → 报警记录 → 控制下发的完整链路。1. 先搞清楚什么是上位机、下位机以及 Modbus 协议1.1 上位机SCADA 层与下位机控制器层在工业自动化里两个概念必须先分清下位机是直接接触现场设备的手脚——通常是 PLC、MCU、运动控制器或传感器负责按固定周期读取温度、压力、流量等输入执行预定义逻辑如压力超限自动停泵并驱动电机、阀门、继电器等输出上位机则是运行在 PC 或工控机上的监控软件即我们要构建的 HMI负责把下位机传来的原始数据变成操作员看得懂的状态、趋势、报警并允许操作员远程下发启动水泵切换配方等请求——但最终能否执行仍由下位机检查现场条件后决定。两者通过工业通信协议交换数据最常见的便是Modbus一个诞生于 1979 年的老牌协议因简单、可靠、几乎所有工业设备都支持而沿用至今。控制室 工厂现场 ------------ Modbus 协议 ------------ | 上位机 | -------------- | 下位机 | | (Qt HMI) | 告诉我压力值 | (PLC/传感器)| | 显示/记录/ | 压力是 1.20MPa | 读取/控制/ | | 报警 | | 保护 | ------------ ------------这套传感器 → 下位机 → 协议 → 上位机的链路在仓库中文版教程中以架构图形式给出上下位机架构图。中文版教程还在 docs/zh-cn/stage-3/cross-platform/qt-industrial-hmi/index.md 中给出了一个关键结论下位机负责把设备控制正确上位机负责让人看得懂、管得住、查得到急停、安全联锁、电机保护必须留在 PLC/MCU 里上位机界面绝不能成为设备安全的唯一保障。1.2 Modbus 的两个关键概念与两种变体Modbus 只需理解两个概念寄存器Register下位机里的数据单元格每个都有地址0、1、2……存放一个数值。例如地址0存压力、地址1存温度。读写操作上位机读寄存器获取数据写寄存器下发控制命令。Modbus 有两大常见变体变体传输层典型场景Modbus RTU串口RS-485/RS-232短距离、设备直连Modbus TCP以太网TCP/IP长距离、网络通信本教程选用Modbus TCP。因为基于网络上位机程序和从站模拟器可以跑在同一台电脑上无需任何物理线缆——这正是零硬件、零成本跑通全流程的关键。1.3 为什么工业软件选 Qt优势说明跨平台一套代码编译到 Windows、Linux 及嵌入式设备工业协议内置支持Qt Serial Bus 原生支持 Modbus无需第三方库图表能力强大Qt Charts 提供专业实时图表高性能C 底座适合实时数据刷新成熟稳定30 年历史工业领域久经考验中文版教程进一步解释了企业的选型逻辑工业软件不追求最新界面技术更在意能否稳定运行多年、断网后是否仍能工作、换一台 Windows/Linux 工控机时是否需要全部重写、能否持续接收设备数据而不卡界面——这些恰好是 Qt 的强项。同时它也提醒若只是做一个浏览器访问的内部表单或数据看板普通 Web 技术通常更省事需要毫秒级控制、电机联锁或急停保护时任务应留在 PLC/MCU 层不能交给桌面界面。1.4 我们要构建什么一个模拟真实工厂水泵压力监控的泵站监控 HMI 系统功能描述实时数据读取每秒从下位机读取压力数据压力趋势曲线显示最近 60 秒压力变化的折线图越限报警压力超过阈值时弹窗提醒 界面变红故障日志所有报警事件写入数据库可历史查询手动控制一键启停水泵写下位机寄存器2. 环境准备Qt 6.5 与 Modbus Slave虚拟水泵2.1 安装 Qt 6.5Qt 提供免费的开源版足以完成本教程访问 Qt 官网下载 Qt Online Installer执行安装登录或注册一个免费的 Qt 账号在组件选择界面勾选Qt 6.5.x或更新版本Qt Serial Bus位于 Additional Libraries提供 Modbus 支持Qt Charts位于 Additional Libraries用于图表渲染Qt CreatorIDE通常默认勾选点击安装并等待完成。提示如果 Qt 已安装但缺少 Serial Bus 或 Charts可重新运行 Qt Maintenance Tool 增量添加组件。2.2 安装 Modbus Slave你的24 小时虚拟水泵Modbus Slave 是免费的 Modbus 从站模拟器可在电脑上模拟一个 PLC/传感器设备让上位机有对象可通信从 modbustools.com 下载并安装 Modbus Slave打开配置连接菜单Connection → Connect选择Modbus TCP/IPIP 填127.0.0.1localhost端口填502Modbus TCP 默认端口点击OK开始监听设置模拟数据寄存器表格中每一行是一个寄存器地址地址0改为120表示压力 1.20 MPa应用内除以 100 还原地址1改为350表示温度 35.0℃地址2改为1水泵状态1运行、0停止。之后保持窗口打开它会持续响应上位机的读写请求。动态模拟技巧右键寄存器值选择 Auto increment 或 Random可模拟传感器数据的真实波动便于验证趋势曲线效果。3. 创建 Qt 项目并打通 Modbus TCP 连接3.1 新建项目并声明模块在 Qt Creator 中File → New Project → Application (Qt) → Qt Widgets Application项目命名为PumpHMI选择已安装的 Qt 6.5 kit。打开PumpHMI.pro若用 CMake 则是CMakeLists.txt加入四个关键模块QT core gui widgets serialbus charts sql模块用途serialbus提供QModbusTcpClient实现 Modbus TCP 通信charts提供QChart、QLineSeries实现实时趋势图sql提供QSqlDatabase实现 SQLite 故障日志CMake 等价写法find_package(Qt6 REQUIRED COMPONENTS Widgets SerialBus Charts Sql) target_link_libraries(PumpHMI PRIVATE Qt6::Widgets Qt6::SerialBus Qt6::Charts Qt6::Sql)3.2 声明核心成员mainwindow.h主窗口头文件声明通信、图表、数据库与界面元素。核心成员包括// mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include QMainWindow #include QModbusTcpClient #include QModbusDataUnit #include QTimer #include QtCharts #include QSqlDatabase #include QLabel #include QPushButton #include QTableWidget class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent nullptr); ~MainWindow(); private slots: void connectModbus(); // 连接下位机 void readPressure(); // 定时读取压力 void onReadReady(); // 读操作回调 void triggerAlarm(float v); // 触发报警 void togglePump(); // 启停水泵 private: // Modbus 通信 QModbusTcpClient *m_modbusClient nullptr; QTimer *m_pollTimer nullptr; // 实时图表 QChart *m_chart nullptr; QLineSeries *m_series nullptr; QDateTimeAxis *m_axisX nullptr; QValueAxis *m_axisY nullptr; // 数据库 QSqlDatabase m_db; // 界面元素 QLabel *m_pressureLabel nullptr; // 压力显示 QLabel *m_statusLight nullptr; // 状态指示灯 QPushButton *m_pumpButton nullptr; // 启停按钮 QTableWidget *m_logTable nullptr; // 日志表格 // 报警阈值 float m_alarmThreshold 1.50f; // 超过 1.50 MPa 报警 bool m_pumpRunning false; void setupUI(); void setupDatabase(); void logAlarm(float pressure, const QString message); }; #endif // MAINWINDOW_H3.3 建立 Modbus TCP 连接mainwindow.cpp// mainwindow.cpp - 连接部分 void MainWindow::connectModbus() { m_modbusClient new QModbusTcpClient(this); // 连接 Modbus Slave 模拟器 m_modbusClient-setConnectionParameter( QModbusDevice::NetworkPortParameter, 502); m_modbusClient-setConnectionParameter( QModbusDevice::NetworkAddressParameter, 127.0.0.1); m_modbusClient-setTimeout(1000); // 超时 1 秒 m_modbusClient-setNumberOfRetries(3); // 重试 3 次 if (!m_modbusClient-connectDevice()) { statusBar()-showMessage(连接下位机失败, 3000); return; } statusBar()-showMessage(已连接下位机 (127.0.0.1:502), 3000); // 启动定时器每秒读取一次 m_pollTimer new QTimer(this); connect(m_pollTimer, QTimer::timeout, this, MainWindow::readPressure); m_pollTimer-start(1000); // 1000ms 1s }代码要点代码含义QModbusTcpClientQt 内置 Modbus TCP 客户端与下位机通信NetworkPortParameter, 502连接 502 端口与 Modbus Slave 配置一致NetworkAddressParameter, 127.0.0.1连接本机回环地址模拟器在本机运行m_pollTimer-start(1000)每秒触发一次readPressure()3.4 读取压力数据异步请求与回调解析// mainwindow.cpp - 读取部分 void MainWindow::readPressure() { if (!m_modbusClient || m_modbusClient-state() ! QModbusDevice::ConnectedState) return; // 构造读请求从地址 0 开始读取 3 个保持寄存器 QModbusDataUnit readUnit( QModbusDataUnit::HoldingRegisters, // 寄存器类型 0, // 起始地址 3 // 数量 ); // 发送异步读请求 if (auto *reply m_modbusClient-sendReadRequest(readUnit, 1)) { if (!reply-isFinished()) { connect(reply, QModbusReply::finished, this, MainWindow::onReadReady); } else { delete reply; // 广播请求直接释放 } } } void MainWindow::onReadReady() { auto *reply qobject_castQModbusReply *(sender()); if (!reply) return; if (reply-error() QModbusDevice::NoError) { const QModbusDataUnit unit reply-result(); // 解析数值寄存器值按量程换算成实际单位 float pressure unit.value(0) / 100.0f; // 地址 0压力 (MPa) float temperature unit.value(1) / 10.0f; // 地址 1温度 (°C) int pumpStatus unit.value(2); // 地址 2泵状态 // 更新界面 m_pressureLabel-setText( QString(%1 MPa).arg(pressure, 0, f, 2)); // 检查报警 if (pressure m_alarmThreshold) { triggerAlarm(pressure); } // 更新趋势图下一章实现 // updateChart(pressure); } else { statusBar()-showMessage( QString(读取失败%1).arg(reply-errorString()), 2000); } reply-deleteLater(); }Modbus 读取流程定时器触发 readPressure() - 构造 QModbusDataUnit读取地址 0-2 - sendReadRequest() 异步发送不阻塞 UI - 下位机返回数据 - 触发 onReadReady() - 解析寄存器值并更新界面值得注意的设计sendReadRequest是异步的界面线程不会被网络等待卡住——这与中文版教程强调的设备通信和耗时任务不能堵住界面线程每一次请求都要有超时、错误提示和恢复办法一脉相承。4. 用 Qt Charts 绘制实时压力趋势4.1 初始化图表在构造函数中初始化图表X 轴用QDateTimeAxis显示最近 60 秒Y 轴用QValueAxis范围 0–3.0 MPa折线为蓝色 2px// mainwindow.cpp - 图表初始化 void MainWindow::setupChart() { m_series new QLineSeries(); m_series-setName(压力 (MPa)); m_series-setPen(QPen(QColor(#2196F3), 2)); m_chart new QChart(); m_chart-addSeries(m_series); m_chart-setTitle(实时压力趋势); m_chart-setAnimationOptions(QChart::NoAnimation); // 实时数据关闭动画 // X 轴时间 m_axisX new QDateTimeAxis(); m_axisX-setFormat(HH:mm:ss); m_axisX-setTitleText(时间); m_chart-addAxis(m_axisX, Qt::AlignBottom); m_series-attachAxis(m_axisX); // Y 轴压力 m_axisY new QValueAxis(); m_axisY-setRange(0, 3.0); m_axisY-setTitleText(压力 (MPa)); m_axisY-setLabelFormat(%.1f); m_chart-addAxis(m_axisY, Qt::AlignLeft); m_series-attachAxis(m_axisY); // 创建图表视图 QChartView *chartView new QChartView(m_chart); chartView-setRenderHint(QPainter::Antialiasing); // 加入布局假设已有 centralLayout centralLayout-addWidget(chartView); }4.2 实时更新追加新点、只保留 60 秒// mainwindow.cpp - 图表更新 void MainWindow::updateChart(float pressure) { QDateTime now QDateTime::currentDateTime(); // 追加新点 m_series-append(now.toMSecsSinceEpoch(), pressure); // 只保留最近 60 秒数据 QDateTime cutoff now.addSecs(-60); while (m_series-count() 0 m_series-at(0).x() cutoff.toMSecsSinceEpoch()) { m_series-remove(0); } // 更新 X 轴范围始终显示最近 60 秒 m_axisX-setRange(cutoff, now); }然后在onReadReady()中取消注释、调用它// 在 onReadReady() 解析压力之后添加 updateChart(pressure);运行程序即可看到一条每秒新增一个点的蓝色折线始终显示最近 60 秒手动修改 Modbus Slave 中的寄存器值曲线会立即响应。性能提示QChart::NoAnimation很关键。实时数据每秒刷新动画会造成界面卡顿这是工业 HMI 的常见实践。5. 越限报警与 SQLite 故障日志5.1 越限报警界面变红 弹窗提醒压力超过阈值时需要界面变红提示 弹窗报警 记录日志// mainwindow.cpp - 报警逻辑 void MainWindow::triggerAlarm(float pressure) { // 界面变红 m_pressureLabel-setStyleSheet( color: white; background-color: #F44336; font-size: 32px; padding: 10px; border-radius: 8px;); // 状态指示灯变红 m_statusLight-setStyleSheet( background-color: #F44336; border-radius: 12px; min-width: 24px; min-height: 24px;); // 弹窗报警仅在首次越限时弹出避免重复弹窗 static bool alarmActive false; if (!alarmActive) { alarmActive true; QMessageBox::warning(this, 压力报警, QString(当前压力 %1 MPa 超过阈值 %2 MPa\n请立即检查水泵状态。) .arg(pressure, 0, f, 2) .arg(m_alarmThreshold, 0, f, 2)); } // 写入数据库 logAlarm(pressure, QString(压力超限%1 MPa %2 MPa) .arg(pressure, 0, f, 2) .arg(m_alarmThreshold, 0, f, 2)); // 压力恢复后复位 if (pressure m_alarmThreshold) { alarmActive false; m_pressureLabel-setStyleSheet( color: #2196F3; font-size: 32px; padding: 10px;); m_statusLight-setStyleSheet( background-color: #4CAF50; border-radius: 12px; min-width: 24px; min-height: 24px;); } }仓库中文版教程中有一张 PySide6 版本实际运行时的报警截图红色报警卡片 趋势尖峰 待确认记录Qt 客户端报警界面。它印证了同一个原则报警至少要写清楚时间、内容和状态——只显示一句温度过高以后很难追查何时发生、是否处理。5.2 初始化 SQLite 数据库工业系统必须记录所有报警事件以便追溯这里使用 SQLite// mainwindow.cpp - 数据库初始化 void MainWindow::setupDatabase() { m_db QSqlDatabase::addDatabase(QSQLITE); m_db.setDatabaseName(pump_alarm_log.db); if (!m_db.open()) { qWarning() 无法打开数据库 m_db.lastError().text(); return; } // 创建报警表 QSqlQuery query; query.exec( CREATE TABLE IF NOT EXISTS alarm_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, pressure REAL, message TEXT ) ); }5.3 记录日志并显示在界面表格// mainwindow.cpp - 写日志 void MainWindow::logAlarm(float pressure, const QString message) { // 写入数据库 QSqlQuery query; query.prepare( INSERT INTO alarm_log (pressure, message) VALUES (?, ?)); query.addBindValue(pressure); query.addBindValue(message); query.exec(); // 更新界面表格 int row m_logTable-rowCount(); m_logTable-insertRow(row); m_logTable-setItem(row, 0, new QTableWidgetItem( QDateTime::currentDateTime().toString(yyyy-MM-dd HH:mm:ss))); m_logTable-setItem(row, 1, new QTableWidgetItem(QString::number(pressure, f, 2))); m_logTable-setItem(row, 2, new QTableWidgetItem(message)); // 自动滚动到底部 m_logTable-scrollToBottom(); }日志表格有三列时间、压力值、报警信息。每次报警新增一行同时持久化到 SQLite。中文版教程中验证过同样的闭环报警写入 SQLite 后完全退出应用再重启历史报警依然存在见 重启后记录仍在证明数据不是只存在于页面内存里的假数据。5.4 手动启停水泵写下位机寄存器上位机不只是读数据还要能控制下位机——通过写寄存器实现// mainwindow.cpp - 水泵控制 void MainWindow::togglePump() { if (!m_modbusClient || m_modbusClient-state() ! QModbusDevice::ConnectedState) return; m_pumpRunning !m_pumpRunning; // 构造写请求向地址 2 写入 1启动或 0停止 QModbusDataUnit writeUnit( QModbusDataUnit::HoldingRegisters, 2, 1); writeUnit.setValue(0, m_pumpRunning ? 1 : 0); if (auto *reply m_modbusClient-sendWriteRequest(writeUnit, 1)) { connect(reply, QModbusReply::finished, this, [this, reply]() { if (reply-error() QModbusDevice::NoError) { m_pumpButton-setText(m_pumpRunning ? 停止水泵 : 启动水泵); m_pumpButton-setStyleSheet(m_pumpRunning ? background-color: #F44336; color: white; padding: 12px; : background-color: #4CAF50; color: white; padding: 12px;); statusBar()-showMessage( m_pumpRunning ? 水泵已启动 : 水泵已停止, 2000); } reply-deleteLater(); }); } }在 Modbus Slave 中可以看到地址2的值随按钮点击在0和1之间切换——这就是上位机控制下位机的实际过程。需要注意的是正如中文版教程所述界面上的按钮只是发出请求真正控制电机和阀门的逻辑仍留在下位机PLC 会先检查急停是否复位、阀门是否打开、液位是否正常条件全部满足才真正执行。6. 打包与部署从开发环境到独立可执行程序6.1 使用 windeployqt / macdeployqtQt 官方部署工具会自动收集运行所需的动态库。Windows# 先以 Release 模式构建然后在构建目录中执行 windeployqt PumpHMI.exewindeployqt会把 Qt 的 DLL、插件、翻译文件等复制到可执行文件旁边这个打包好的文件夹可直接分发。macOSmacdeployqt PumpHMI.app -dmg这会生成一个.dmg安装映像。6.2 用 Qt Installer Framework 构建专业安装向导若需要下一步 → 下一步 → 完成式的专业安装向导可使用 Qt Installer Framework创建安装包目录结构config、packages配置config.xml安装器名称、版本、目标目录将windeployqt的输出文件放入packages/com.example.pumphmi/data/运行binarycreator生成安装器。中文版教程从实践角度补充了发布前的验证清单可直接用于本教程的 C 项目固定 Qt 和依赖版本、生成可安装包后在无开发环境的干净电脑上连续运行检查重启电脑后软件能否恢复、断网/断电/设备离线后数据是否损坏、日志是否无限占满磁盘、新版本失败能否回退、Windows 安装包和 macOS 应用是否完成签名以及开源版 Qt 的许可证合规。7. 总结与进阶方向恭喜你已从零构建了一个工业级泵站监控 HMI。回顾整条链路理解了上位机、下位机与 Modbus 协议的核心概念用 Modbus Slave 模拟虚拟水泵全程无需真实硬件用 QtQModbusTcpClient建立上下位机通信用 Qt Charts 绘制实时滚动压力趋势曲线实现越限弹窗报警与 SQLite 故障日志实现水泵远程启停控制。整个过程未使用任何真实工业硬件但架构与功能与真实工厂 HMI 系统一致。把 Modbus Slave 换成真实 PLC这套应用可直接用于生产场景。本教程对应的中文版 PySide6 实践docs/zh-cn/stage-3/cross-platform/qt-industrial-hmi/index.md展示了同一链路的另一种实现设备模拟器 → TCP 通信 → Qt 页面 → 断线提示 → 报警 → SQLite → 独立应用并验证了数据不只会变化、报警不只在屏幕上、项目不只在开发环境运行三件关键事实。进阶方向多设备监控连接多个下位机用标签页/分屏视图展示不同设备数据历史回放从 SQLite 读取历史数据配合时间轴控件回放趋势曲线OPC UAModbus 适合简单场景复杂工业系统常用 OPC UAQt 也有 Qt OPC UA 模块支持Web 远程监控用 Qt WebSocket 把实时数据推送到浏览器支持移动端查看AI 预测性维护把历史压力数据喂给 ML 模型提前预测设备故障。用代码守护工业现场的每一台设备。参考资料本教程基于 docs/fr-fr/stage-3/cross-platform/qt-industrial-hmi/index.md 撰写仓库内可进一步参考中文版Qt 工业设备客户端PySide6 实战上下位机架构图Qt 客户端报警界面运行截图Qt 客户端重启后 SQLite 报警记录仍在赞分享教程文档【免费下载链接】easy-vibe从 0 到 1 学会 vibe coding项目制学习项目地址https://gitcode.com/datawhalechina/easy-vibe点击查看免费下载相关推荐Easy-Vibe 实战用 Qt 从零构建工业级泵站监控 HMI——Modbus 通信、实时趋势图与故障报警全流程Easy Vibe 实战用 Qt 从零构建工业级泵站监控 HMI——Modbus 通信、实时趋势图与故障报警全流程 本文是 Easy Vibe 教程第三阶段教程文档人工智能Vibe Coding从零构建工业级 Qt 泵站监控 HMIModbus TCP、实时趋势图与报警日志实战从零构建工业级 Qt 泵站监控 HMIModbus TCP、实时趋势图与报警日志实战 本篇指南基于 easy vibe 仓库 docs/en/stage 3/教程文档CSS Zen Garden设计评审分析最优秀的10个设计作品CSS Zen Garden设计评审分析最优秀的10个设计作品 CSS Zen Garden是网页设计领域的经典项目它展示了如何通过纯CSS技术将相同的HT教程文档上一篇ArcObjects SDK 10.8从零开始掌握GIS开发核心技术的完整指南下一篇Windows 7 SP2终极更新包让经典系统焕发新生的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考