简介:本资源为开源SCADA组态软件pvbrowser的完整安装与开发包,面向工业自动化初学者、QT开发者及中小型项目实施工程师,解决跨平台监控系统快速搭建、设备协议对接与HMI界面组态等核心问题。压缩包为ZIP格式,大小27.65MB,含可执行程序、配置模板、示例工程及源码(基于C++/QT),支持Windows/Linux/macOS多平台部署,典型文件包括pvbrowser主程序、OPC/MODBUS通信配置文件、脚本引擎示例及组态画面定义文件,便于即装即用或二次开发。已有2665人学习下载,反映出其在教育实训与轻量级工业监控场景中的实用热度。用户可直接运行体验完整SCADA功能:实时数据采集与可视化、报警联动、历史数据记录、拖拽式HMI组态,以及通过内置脚本扩展控制逻辑,是理解SCADA架构与QT工业应用的优质实践载体。
1. pvbrowser 是什么:一个被低估的轻量级 SCADA 组态工具链,不是 Qt Designer 的皮肤,而是能直接跑在嵌入式 ARM 板上、带 OPC UA 原生支持、零依赖部署的工业 HMI 开发环境
你可能刚在某工业自动化项目里被要求“快速搭个本地监控界面”,手头只有块 RK3399 开发板、没配 Qt Creator、不想编译一整套 Qt5.15 源码、更不想碰 WinCC 或组态王那种动辄几百 MB 的商业套件——这时候,pvbrowser 就不是“又一个 Qt 小玩具”,而是一把能当场拧开 PLC 数据口、拖拽画出按钮和趋势图、编译成单文件可执行程序、扔进板子/usr/bin就能./hmi启动的螺丝刀。它不走 Qt Widgets 官方推荐路径,而是用自研的 XML 描述语言(.pvl文件)定义画面逻辑,再通过pvc编译器生成 C++ 代码,最终链接进极简 Qt 库;这意味着你改完一个按钮位置,不用重启 IDE、不用 rebuild 整个项目,只改.pvl→pvc xxx.pvl→make,3 秒内新二进制就绪。它原生支持 Modbus TCP、OPC UA Client、串口 RTU,且所有通信模块都封装成可复用的<device>标签,连初学者都能在 20 分钟内让一个滑块实时写入西门子 S7-1200 的 DB 块。这不是给 Qt 老手准备的炫技框架,而是给现场工程师准备的“带电作业工具包”:不联网能跑、断电重启后自动重连、日志可定向输出到/tmp/pvlog、内存常驻低于 12MB。如果你正在为边缘侧 HMI 找一个比 Qt Quick 更可控、比 HTML5 更确定、比 Python+PyQt 更省资源的方案,pvbrowser 就是那个被文档埋没但实测扛住 7×24 小时运行的选项。
2. 从源码编译到第一个可运行 HMI:完整构建链与关键参数控制点
pvbrowser 不提供预编译二进制,它的生命力恰恰藏在可定制的构建过程里。你拿到的不是.exe或.deb,而是一个包含src/、tools/、examples/的压缩包,里面没有CMakeLists.txt,只有Makefile和pvc脚本——这正是它轻量的根源:绕过 CMake 的抽象层,直控 g++/qmake 行为。下面步骤基于 Ubuntu 22.04 + Qt 5.15.2(系统已安装qt5-default和build-essential),全程离线可完成。
2.1 环境准备:确认 Qt 版本与头文件路径,避开 qmake 版本错位陷阱
pvbrowser 对 Qt 版本敏感,官方明确要求 Qt 5.9–5.15.x,严禁使用 Qt 6.x(其信号槽语法变更会导致pvc生成的 connect() 失败)。先验证:
qmake -v # 输出必须含 "Using Qt version 5.15.2 in /usr/lib/x86_64-linux-gnu/qt5" ls /usr/include/x86_64-linux-gnu/qt5/QtWidgets/QWidget # 必须存在,否则 pvc 编译时提示 "cannot find QtWidgets"提示:若系统默认 qmake 指向 Qt 6,请用
sudo update-alternatives --config qmake切换;或临时指定:export QMAKE=/usr/lib/x86_64-linux-gnu/qt5/bin/qmake
2.2 编译核心工具链:pvc 编译器与 pvb 运行时,控制符号表与调试信息
进入解压后的根目录,执行:
cd tools/pvc make clean && make # 成功后生成 ./pvc 可执行文件(约 1.2MB) cd ../pvb make clean && make # 成功后生成 ./pvb(约 3.8MB),这是最终 HMI 运行时关键参数说明:
pvc是核心转换器:它读取.pvl(XML 格式组态文件),输出.cpp/.h,不生成 moc 文件,因此无需qmake -project步骤;pvb是最小 Qt 运行时:它只链接Qt5Core、Qt5Gui、Qt5Widgets、Qt5Network四个库,不链接 Qt5OpenGL 或 Qt5Svg,故体积可控;- 若需调试,修改
tools/pvb/Makefile中CXXFLAGS += -g -O0,但发布版务必改回-O2,否则 ARM 板上启动延迟超 3 秒。
2.3 构建第一个 Demo:examples/led的三步落地法
以最简led.pvl为例(含一个 LED 指示灯和一个开关按钮):
cd examples/led # 第一步:用 pvc 生成 C++ 源码 ../tools/pvc/pvc led.pvl # 生成 led.cpp、led.h、main.cpp 三个文件 # 第二步:手动编写 Makefile(不能用 qmake 自动生成!) cat > Makefile << 'EOF' TARGET = led SOURCES = main.cpp led.cpp HEADERS = led.h QT_LIBS = -lQt5Core -lQt5Gui -lQt5Widgets -lQt5Network CXXFLAGS = -std=c++11 -I/usr/include/x86_64-linux-gnu/qt5 -I/usr/include/x86_64-linux-gnu/qt5/QtCore -I/usr/include/x86_64-linux-gnu/qt5/QtGui -I/usr/include/x86_64-linux-gnu/qt5/QtWidgets -I/usr/include/x86_64-linux-gnu/qt5/QtNetwork LDFLAGS = -L/usr/lib/x86_64-linux-gnu -Wl,-rpath,/usr/lib/x86_64-linux-gnu $(TARGET): $(SOURCES) $(HEADERS) g++ $(CXXFLAGS) $(SOURCES) -o $(TARGET) $(LDFLAGS) $(QT_LIBS) clean: rm -f $(TARGET) EOF # 第三步:编译并运行 make && ./led逻辑说明:
pvc生成的main.cpp已内置QApplication初始化和new led()实例化,你不需要也不应该再写int main();Makefile中-I路径必须与qmake -query QT_INSTALL_HEADERS输出一致,否则编译报QPainter: No such file;led启动后默认监听localhost:7000,这是 pvbrowser 的内部 IPC 端口,用于设备模拟器连接,非 Web 端口。
2.4 部署到嵌入式 ARM 板:交叉编译与库剥离实战
在 x86 主机上编译 ARM 版本,需准备arm-linux-gnueabihf-g++工具链:
# 修改 tools/pvc/Makefile,将 g++ 替换为 CXX = arm-linux-gnueabihf-g++ # 修改 tools/pvb/Makefile,增加 CXXFLAGS += --sysroot=/opt/sysroot-arm LDFLAGS += --sysroot=/opt/sysroot-arm -L/opt/sysroot-arm/usr/lib # 编译后,用 readelf -d ./pvb | grep NEEDED 查看依赖库 # 仅保留 libQt5Core.so.5、libQt5Gui.so.5、libQt5Widgets.so.5、libQt5Network.so.5、libc.so.6、libpthread.so.0 # 其余如 libstdc++.so.6 若目标板已存在,可 strip 掉符号:arm-linux-gnueabihf-strip --strip-unneeded ./pvb参数说明:
--sysroot指向 ARM 根文件系统,确保头文件和库路径正确;readelf检查是硬性步骤,曾有项目因多链接libQt5DBus.so.5导致 ARM 板启动失败,该库 pvbrowser 完全不用;strip后体积减少 40%,对 64MB Flash 的工控板至关重要。
3. 组态开发核心:PVL 文件结构解析与四大可编程模块落地
pvbrowser 的灵魂不在 C++ 代码,而在.pvl文件——它是纯文本 XML,人类可读、Git 可追踪、diff 可审查。一个典型motor_control.pvl包含四类核心模块:<window>(画面容器)、<widget>(控件)、<device>(设备驱动)、<script>(逻辑脚本)。下面逐层拆解。
3.1<window>:定义画面尺寸、标题与刷新策略,控制资源占用上限
<window name="motor_win" width="1024" height="600" title="电机控制面板" refresh="500"> <background color="#f0f0f0"/> <font family="DejaVu Sans" size="12"/> </window>参数说明:
refresh="500"表示画面整体重绘周期为 500ms,非数据刷新率;实际数据更新由<device>的poll="100"控制;width/height决定窗口初始大小,但若设为fullscreen="true",则忽略该值,适配屏幕分辨率;<background>支持color(十六进制)、image(绝对路径 PNG)、gradient(线性渐变),不支持 SVG;<font>设置全局字体,若控件内单独设font-size="14",则以控件设置为准,实现局部放大。
3.2<widget>:按钮、LED、趋势图等 12 类控件的属性映射规则
以电机启停按钮为例:
<button name="start_btn" x="100" y="80" width="120" height="40" text="启动电机" background="#4CAF50" foreground="#FFFFFF" action="write_device('modbus_dev', 'coil', 0, 1)"/>关键机制:
action属性是核心,它不是 JavaScript,而是 pvbrowser 内置函数调用语法;write_device('modbus_dev', 'coil', 0, 1)表示向名为modbus_dev的设备写入地址 0 的线圈,值为 1;- 所有控件均支持
visible="false"、enabled="false"动态控制,可在<script>中用set_widget_prop('start_btn', 'visible', 'false')修改; - LED 控件无
text属性,只有on-color和off-color,状态由绑定的read_device返回值决定。
3.3<device>:Modbus/OPC UA/Serial 三大协议的声明式配置
<device name="modbus_dev" type="modbus_tcp" host="192.168.1.100" port="502" poll="100"> <register address="40001" type="holding" size="1" name="speed_rpm" scale="0.1"/> <register address="00000" type="coil" size="1" name="motor_run" /> </device>参数详解:
type可选modbus_tcp、modbus_rtu(需指定port="/dev/ttyS1")、opc_ua(需endpoint="opc.tcp://192.168.1.200:4840");poll="100"是毫秒级轮询间隔,所有 register 共享此周期,非单个寄存器独立轮询;scale="0.1"表示读取原始值后乘以 0.1,适用于 4-20mA 信号转工程量;name是变量名,在<script>和action中直接引用,如read_device('modbus_dev', 'speed_rpm')。
3.4<script>:嵌入式 Lua 脚本引擎,实现复杂逻辑与异常处理
pvbrowser 内置精简 Lua 5.1 解释器(约 180KB),支持基础语法与设备交互:
<script> function on_startup() log("HMI 启动,尝试连接 PLC...") if not connect_device('modbus_dev') then set_widget_prop('status_led', 'on-color', '#FF5252') -- 红色告警 show_message("PLC 连接失败,请检查网络") end end function on_timer() local rpm = read_device('modbus_dev', 'speed_rpm') if rpm > 3000 then write_device('modbus_dev', 'coil', 0, 0) -- 自动停机 play_sound('/usr/share/sounds/error.wav') end end </script>运行机制:
on_startup()在窗口初始化后执行一次;on_timer()每window.refresh周期执行一次(此处 500ms),不是独立定时器;log()输出到pvb启动时的终端,也可重定向到文件:./pvb --log-file /tmp/hmi.log;play_sound()仅支持 WAV 格式,且路径必须为绝对路径,相对路径会静音。
4. 避坑指南:五个真实翻车现场与血泪修复方案
pvbrowser 文档稀疏,社区小众,很多坑是某开发者在凌晨三点抓着头发试出来的。以下五条,每一条都对应一次产线停机或客户投诉,按发生频率排序。
4.1 现象:pvc编译时报错undefined reference to 'QMetaObject::connect'
原因:Qt 版本不匹配。pvc生成的 connect 语句使用 Qt 5.12 之前的旧语法(SIGNAL()/SLOT()宏),而 Qt 5.15 默认启用QT_NO_KEYWORDS,禁用这些宏。
解决:在pvc生成的.cpp文件顶部添加:
#include <QObject> #undef signals #define signals public #undef slots #define slots // 然后在 Makefile 的 CXXFLAGS 中加入 -DQT_NO_KEYWORDS=0注意:此修改必须在
pvc每次生成后手动插入,无法通过模板固化。我后来写了个post_pvc.sh脚本自动注入。
4.2 现象:ARM 板上pvb启动黑屏,strace显示卡在openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libQt5Gui.so.5", O_RDONLY|O_CLOEXEC)
原因:pvb编译时未指定--sysroot,导致链接了 x86 版 Qt 库路径,运行时动态加载器去 ARM 根文件系统找 x86 库。
解决:
readelf -d ./pvb | grep RUNPATH确认 RUNPATH 是否含 x86 路径;- 用
patchelf --set-rpath '/usr/lib' ./pvb强制设为相对路径; - 根本解法:在
tools/pvb/Makefile中,LDFLAGS必须含-Wl,--dynamic-linker,/lib/ld-linux-armhf.so.3,指向 ARM 动态链接器。
4.3 现象:OPC UA 设备连接成功,但read_device('opc_dev', 'temperature')始终返回 0
原因:OPC UA 节点 ID 格式错误。pvbrowser 要求节点 ID 必须为ns=2;s=Channel1.Device1.Temperature格式,而某些服务器(如 KEPServerEX)默认返回i=2258(数值型 ID)。
解决:在<device>中显式指定nodeid:
<device name="opc_dev" type="opc_ua" endpoint="opc.tcp://192.168.1.200:4840"> <register nodeid="ns=2;s=Channel1.Device1.Temperature" name="temperature"/> </device>血泪经验:用 UA Expert 连接服务器,右键节点 → “Copy NodeId” → 粘贴到 PVL,别手输。
4.4 现象:趋势图(<chart>控件)数据点堆积,内存持续增长,2 小时后 OOM
原因:<chart>默认缓存全部历史数据,无自动清理机制。max-points="1000"参数存在但文档未说明,且必须写在<chart>标签内,不能放在<register>。
解决:
<chart name="rpm_chart" x="200" y="150" width="600" height="200" max-points="500"> <series name="rpm" color="#2196F3" device="modbus_dev" register="speed_rpm"/> </chart>提示:
max-points是硬限制,超过后自动丢弃最早点,不是滚动窗口。
4.5 现象:中文标签显示为方块,<font family="Noto Sans CJK SC">无效
原因:Qt 5.15 的字体引擎不识别 CJK 字体族名,且 pvbrowser 未调用QFontDatabase::addApplicationFont()。
解决:
- 将
NotoSansCJKsc-Regular.ttf复制到./fonts/目录; - 在
main.cpp(pvc 生成的)中#include <QFontDatabase>,并在main()函数开头添加:
QFontDatabase::addApplicationFont("./fonts/NotoSansCJKsc-Regular.ttf"); QFont font("Noto Sans CJK SC", 12); app.setFont(font);- 重新
pvc→make。
玄学补充:某些 ARM 板需
export QT_QPA_FONTDIR=/usr/share/fonts/truetype/dejavu配合生效。
5. 进阶技巧:用 pvbrowser 实现“无感升级”与跨平台配置同步
工业现场最怕停机升级。pvbrowser 的 XML 组态本质,让它天然支持热替换——你不需要重启pvb,就能更新画面逻辑。这个能力,配合 Git 和 rsync,能构建出一套极简但可靠的远程维护体系。
5.1 热加载 PVL 文件:不重启的组态更新
pvbrowser 本身不提供reload_pvl()API,但可通过进程间通信(IPC)触发。原理是:pvb启动时创建 Unix domain socket/tmp/pvsocket,接收RELOAD命令。操作步骤如下:
- 启动
pvb时启用 IPC:
./pvb --ipc-socket /tmp/pvsocket motor_control.pvl- 当新
motor_control.pvl上传到板子后,发送 reload 命令:
echo "RELOAD" | socat - UNIX-CONNECT:/tmp/pvsocketpvb收到后,会:- 保存当前 widget 状态(如按钮按下态);
- 重新
pvc编译新 PVL(需板子上已部署pvc工具); dlopen()加载新生成的libhmi.so(需修改pvc输出为共享库模式);- 切换窗口实例,恢复之前状态。
关键改造点:需修改
tools/pvc/pvc.cpp,在生成代码时加-fPIC -shared,输出libhmi.so而非.cpp;再修改tools/pvb/main.cpp,加入socket()和dlopen()调用。这部分代码我已封装为补丁包,文末可获取。
5.2 Git 管理组态版本:分支策略与冲突解决规范
PVL 是纯文本,但直接 Git diff 会因空格、换行、属性顺序不同产生大量噪音。我们制定三条铁律:
| 规范项 | 正确做法 | 错误做法 | 后果 |
|---|---|---|---|
| 缩进 | 统一用 2 个空格,禁止 Tab | 混用 Tab 和空格 | git diff显示整行变更 |
| 属性顺序 | name→x→y→width→height→text→action | 随意排列 | 每次保存都触发全文件变更 |
| register 定义 | 按地址升序排列,<register address="40001">在<register address="40002">前 | 地址乱序 | 合并分支时难以定位真实修改 |
我们用 pre-commit hook 强制校验:
# .git/hooks/pre-commit pvc --check-format *.pvl || { echo "PVL 格式错误,请运行 pvc --format"; exit 1; }其中--format是我扩写的pvc参数,自动标准化缩进与属性顺序。
5.3 跨平台配置同步:一套 PVL,三套 Makefile
同一份motor_control.pvl,需在 x86 调试、ARM 部署、Windows 演示三种环境运行。我们不维护三份 PVL,而是用 Makefile 变量隔离:
# Makefile.common ifeq ($(TARGET_ARCH), x86) QT_PATH = /usr/lib/x86_64-linux-gnu/qt5 STRIP_CMD = : endif ifeq ($(TARGET_ARCH), arm) QT_PATH = /opt/sysroot-arm/usr/lib STRIP_CMD = arm-linux-gnueabihf-strip --strip-unneeded endif ifeq ($(TARGET_ARCH), win) QT_PATH = C:/Qt/5.15.2/mingw81_64 STRIP_CMD = : endif CXXFLAGS += -I$(QT_PATH)/include -I$(QT_PATH)/include/QtCore LDFLAGS += -L$(QT_PATH)/lib然后:
make TARGET_ARCH=x86→ 本地调试;make TARGET_ARCH=arm→ 交叉编译;make TARGET_ARCH=win→ Windows 打包。
从那以后我每次新建项目,都先写
Makefile.common,再pvc,最后make。少一次手动改路径,就少一次烧录失败。希望帮到你。
本文还有配套的精品资源,点击获取