今年开春,我跑去坝区处理一件拖了两个月的窝火事:监控室里那台老工控机上装着基康的采集软件,坝基渗压计的测值在里头一跳一跳的,看着一切正常。可分管领导要的是实时上云、大屏展示和手机报警,而G2采集仪的数据就是出不来——不是设备坏了,而是整套链路被厂家私有协议焊死了。这活儿说白了就一句话:把基康G2采集仪的私有MQTT协议摸清楚,再让BGK4500U这种老式巡检设备也“开口”说话,把测值都送进统一的MQTT大网里。
这篇文不是推销文章,是给同行的一次复盘:我会把改造G2的过程拆成协议摸底、串口接入、服务端搭建、现场避坑四个部分,适合正在做大坝安全监测系统集成、或者被厂家数据锁定的运行管理单位信息化人员。你要是正对着基康的设备发愁,这篇应该能帮你少走好几段弯路。
1. 为什么要动这条“老链路”:私有软件、人工巡检与数据孤岛
1.1 传统监测链路里的三道坎
第一道坎是数据“锁在保险柜里”。基康G2这类采集站在坝区扮演的角色,说简单点就是“多通道数据采集员”:定时给振弦式渗压计、测缝计、温度计发激励信号,测频率、测温度,算出工程上要用的物理量,然后存在自己的存储里。问题是它对外说话只认厂家那套私有通信协议,要么靠厂家软件从串口拉数据,要么用U盘去现场导。第三方平台想直接拿数据,基本拿不到,这就叫数据孤岛。
第二道坎是人工巡检跟不上。大坝的测点分布在坝基、坝肩、绕坝渗流区,很多位置车辆根本到不了。很多单位到现在还在用BGK4500U这类便携读数设备,工人扛着设备一个点一个点去读,雨天还要担心路滑。测值倒是能读出来,可回到办公室再录入Excel,时效性全没了。汛期水位暴涨的时候,你不可能让工人冒雨一小时巡一圈。
第三道坎是应急响应靠人盯。传统链路下,测值变化往往是事后才发现。哪天渗压计读数突然蹿升,可能标志着坝体渗流异常,但数据还压在采集仪里,没有实时推送,值班人员也就没法第一时间处理。所以这次改造表面上是“换个协议”,本质上是在给监测系统装上“即时响应”的能力。
1.2 为什么我选MQTT而不是HTTP轮询或Modbus TCP
在动手之前,有几个同事问过我:既然G2能按厂家协议把数据读出来,那我用程序定时去拉不就行了?为什么非要折腾MQTT?我把三个候选方案放在一起对比过:
| 方案 | 连接方式 | 弱网表现 | 一对多分发 | 服务端压力 | 适合场景 |
|---|---|---|---|---|---|
| HTTP轮询 | 短连接请求/响应 | 超时多、重试难设计 | 要做广播逻辑 | 站点多时压力大 | 内网稳定环境 |
| Modbus TCP | 中心主动轮询 | 通道不稳定易丢轮次 | 中心单点调度 | 轮询周期难压缩 | 厂内PLC网络 |
| MQTT | 长连接发布/订阅 | 断线缓存、QoS可调 | 一次发布多方订阅 | broker轻量但很能扛 | 野外弱网、海量站点 |
对比完基本就锁定MQTT了。野外采集站的环境大家都清楚:4G信号说断就断,冬天山上温度零下,设备长时间没人碰。MQTT的长连接设计天生适合这种场景,采集仪作为发布者主动把数据推上来,平台端订阅就行,不需要知道每个野站到底在什么内网IP后面。最实用的一点,MQTT自带QoS等级和遗嘱消息,设备掉线了broker还能通知平台“某某站点失联了”,这在HTTP轮询里得自己写一堆逻辑才能模拟出来。
2. 摸清基康G2采集仪私有MQTT协议的底细
2.1 G2在改造中的设备定位
先明确一下G2干活的位置:它挂在坝区的测点旁边,机箱里是一块采集板加通信模块,外面接十几路传感器,上面可能还装了一块太阳能板。改造之后,它的任务从“只对厂家软件说话”变成“把测值作为消息发布到MQTT服务器”。注意,这里我不替厂家背书,不同固件版本的G2开放程度不一样,有的出厂就带了MQTT客户端功能,有的要靠升级或扩展模块才支持。
我在现场碰到过一种情况:设备管理界面里能填“MQTT服务器地址、端口、用户名”,看起来像支持MQTT,可固件文档里却一个字都没提,代码层级上也可能只是预留了接口。这种时候你千万别急着写业务代码,先验证它到底能不能把消息发出来。
2.2 从“黑盒”到“白盒”:三个手段快速摸底
想要摸清私有协议,我的做法是三步走,缺一不可。
第一步,在办公室里装一个MQTT客户端工具,我推荐MQTT Explorer。它最大的好处是支持通配符订阅,你输入一个#就能把某个broker上所有主题都看一遍,主题树一目了然。这个工具在Windows、Linux、macOS上都有安装包,下载很方便,后面我还会细讲使用步骤。
第二步,抓包。如果G2支持配置MQTT参数,你把它指向自己临时搭的broker,然后在网关上用tcpdump或者Wireshark抓网络包。抓包能看到的内容比客户端工具更细:连接的cleanSession标志、心跳间隔、发布消息的主题和payload原始字节、有没有遗嘱消息,这些信息对后续对接非常有价值。
第三步,要协议文档,而且要带版本号。厂家给的协议文档往往只写了“推荐用法”,到了现场一测,字段对不上、字节序不一样的情况太多了。所以拿到文档后,我的经验是先把文档放一边,用抓到的真实报文去验证文档里的每一个字段,再按实际报文重新整理一份自己的字段对照表。
2.3 主题与Payload的典型套路
在基康这类工业设备的私有MQTT实现里,主题命名通常有一定规律,常见的是按站点和设备拆层级。举个例子,我接触过的设备里,测量数据会发布到这样的主题:
bgk/{station_id}/{device_id}/measurement设备在线状态则发到:
bgk/{station_id}/{device_id}/online至于Payload,我见过JSON,也见过二进制帧。JSON的一帧长这样(这是我在项目里实际整理出来的示例格式,字段名以固件为准):
{ "dev": "G2-20240510-001", "ts": 1719990000, "ch": [ {"n": 1, "freq": 2456.3, "temp": 18.5}, {"n": 2, "freq": 2301.8, "temp": 18.6} ] }二进制帧则紧凑得多,常见结构是:帧头、数据长度、设备ID、数据段、CRC校验。数据段里频率值可能是4字节float,温度是2字节整数。这里最容易踩的坑是字节序,有些固件高字节在前,有些低字节在前,不实测盲猜的话,解析出来的数据会面目全非。
我的建议是:无论文档怎么讲,先把真实数据抓下来存成样本文件,写解析器时用样本一个个对。手里有个几十帧真实报文,比啥文档都管用。
2.4 用MQTT Explorer做一次真实观测
这里说下具体操作,方便第一次干这活的兄弟快速上手。先下载安装MQTT Explorer,打开后点“Add connection”,填一个连接配置:填写broker地址、端口(默认1883)、用户名密码,Base topic可以留空。连接成功后,在顶部搜索框输入#订阅全部主题,左侧就会刷新出一棵主题树。
看到主题树之后,点击任意一个主题,右侧会显示实时消息内容。JSON会自动格式化,二进制内容会显示成十六进制,两种都方便复制。我的习惯是:在确认协议之前,先订阅#连续跑半小时,把期间产生的所有消息保存下来,存成一份格式统一的样本库。样本够了再停,别手欠中途断掉。
有一点必须提醒:如果现场有多个采集站同时在线,订阅#会把所有站点的消息都拉下来,初期看起来比较乱。这时可以把通配符换成具体设备ID去过滤,比如bgk/station_01/#,先单站单点验证。
3. BGK4500U的接入:把巡检老设备也拉进MQTT大网
3.1 BGK4500U到底是什么角色
很多运行管理单位手里其实有两套数据:一套是G2自动采集的在线数据,另一套是工人用BGK4500U这类便携读数仪,在现场对关键测点做定期巡检得到的数据。BGK4500U在基康的产品体系里属于老一代读数终端,能接在振弦式传感器上读出频率和温度,也能在屏幕上显示结果,有些型号带存储,串口还可以输出数据。
这次改造里,它的角色比较特殊:我不打算让它联网,那太折腾了,而是让它在巡检时把数据“说”出来,由旁边的边缘网关或者G2的扩展串口收下,再转成MQTT消息上报。这样,自动测值和人工巡检值终于能在同一个平台上并排展示、互相验证。
3.2 串口参数与数据帧:先让设备开口
第一步永远是确认串口参数。BGK4500U这类设备常见的串口设置是波特率9600或19200,数据位8位,停止位1位,无校验。先把USB转串口线接上,用一个串口调试助手(我用的是开源的Serial Port Assistant)发一个查询指令,或者直接看它主动输出的数据流。
输出的数据帧多半是两种风格:一种是ACSII明文,类似:
$BGK4500,SN=12345,CH=1,F=2456.3,T=18.5*3F另一种是Modbus RTU的十六进制帧。ASCII明文比较简单,直接按分隔符拆字段;Modbus RTU则要按寄存器地址去解析数据。无论哪一种,我都建议先把采集到的原始帧存进日志文件,便于后续重复验证。
3.3 边缘封装:串口数据变成MQTT消息
拿到原始帧之后,需要一个“翻译官”把串口数据转成MQTT消息。我这边选择独立边缘网关的方案,原因很简单:不依赖G2固件是否开放扩展串口能力,网关自己说了算,后期换设备也不影响。网关我用过树莓派,也用过工业级的嵌入式工控机,逻辑都一样。
下面是一段可运行的Python示例,思路是:打开串口读取一行,解析出频率和温度,然后以JSON形式发布到broker。用到的库是pyserial和paho-mqtt,pip install pyserial paho-mqtt就可以装上。
import time import serial import json import paho.mqtt.client as mqtt SERIAL_PORT = "/dev/ttyUSB0" BAUD_RATE = 9600 BROKER = "192.168.1.100" TOPIC = "bgk/station_01/bgk4500u/measurement" # 连接MQTT client = mqtt.Client(client_id="bgk4500u-gateway") client.username_pw_set("gw_user", "gw_pass") client.connect(BROKER, 1883, keepalive=60) client.loop_start() ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=3) while True: line = ser.readline().decode("ascii", errors="ignore").strip() if not line.startswith("$BGK4500"): continue payload = {} for item in line.split("*")[0].split(",")[1:]: if "=" in item: key, value = item.split("=") payload[key] = value # 组上报消息 msg = { "source": "bgk4500u", "sn": payload.get("SN"), "channel": payload.get("CH"), "freq": float(payload.get("F", 0)), "temp": float(payload.get("T", 0)), "gw_ts": int(time.time()) } client.publish(TOPIC, json.dumps(msg), qos=1) print("published:", msg)运行之前记得把串口设备号、broker地址、账号密码换成自己的。我在现场还加了一个很关键的细节:发布消息时带了gw_ts字段,记录网关接收的时间,这个时间戳在后面数据校核和追历史时非常有用。
4. 服务器端搭建:MQTT服务、账号权限与数据落库
4.1 服务端选型:EMQX还是Mosquitto
用户量不大、站点数也就几十个的场景,用Mosquitto完全够,配置简单,消耗小。但如果平台将来要接入上千个测点、还要做规则引擎、数据库持久化、设备管理,我建议直接用EMQX,它的内置能力能省不少开发量。
举个具体例子:我这次给单位搭的平台用的是EMQX的Docker镜像,一条命令就能跑起来:
docker run -d --name emqx -p 1883:1883 -p 18083:18083 emqx/emqx:5.4.01883是MQTT端口,18083是管理控制台网页端口。启动后访问http://服务器IP:18083,默认用户名admin、密码public,进控制台后第一件事就是改密码、创建业务账号。如果是Windows Server,不想装Docker,装Mosquitto的Windows安装包也行,配置过程不难,官方文档写得很清楚,我就不展开了。
4.2 账号权限与TLS:别开匿名大门
这片段我特意要强调:千万不要开匿名访问。野外设备接入后,broker会暴露在公网,没有账号限制等于把采集数据裸奔在路上,出一次数据泄露就够喝一壶。
我的做法是分两类账号:设备侧账号只能发布,平台侧账号只能订阅。在EMQX里,可以为客户端分配不同的发布/订阅权限,比如设备账号允许bgk/#发布,平台账号允许bgk/#订阅。这样即使设备被外人控制,他也只能往这个主题塞垃圾数据,拿不走别人的数据。
通信加密也一样要做。如果条件允许,直接上TLS证书,把broker的CA证书提前配置到网关设备里。很多现场设备是老固件,不一定支持TLS,那就先跑明文内网,但要严格限制端口只对内网开放。这个权衡没法一刀切,但原则不变:能加密就别裸奔。
4.3 数据订阅与入库:从消息到报表
broker搭好、设备消息进来之后,最核心的业务是数据落库。我这里的做法是用Python订阅采集主题,解析JSON后写入时序数据库。时序库我用了TDengine,当然MySQL也能行,只是查询连续的仪表数据时时序库更顺手。
一个简单的订阅入库脚本逻辑如下:
import json import paho.mqtt.client as mqtt from datetime import datetime def on_message(client, userdata, msg): payload = json.loads(msg.payload.decode("utf-8")) print(f"topic={msg.topic}, ts={payload['ts']}, freq={payload['ch'][0]['freq']}") # 在这里把解析后的数据写入数据库 client = mqtt.Client() client.username_pw_set("plat_user", "plat_pass") client.connect("127.0.0.1", 1883) client.subscribe("bgk/#", qos=1) client.on_message = on_message client.loop_forever()注意一个细节:订阅的主题用bgk/#,这会把G2和BGK4500U的消息都收进来。如果你想分开处理,就分别订阅bgk/+/+/measurement这类更精确的主题,再在on_message回调里按topic判断数据来源。有人喜欢在平台端统一做清洗,我更喜欢在边缘网关就把数据标准化好,平台只负责存和显示,少折腾。
5. 现场改造绕不开的坑:信号、时间戳、固件与接线
5.1 弱网区断线重连与数据缓存
河谷地带4G信号不稳定,这是所有野外监测项目的老大难。G2或者边缘网关一掉线,消息发不出去,平台端就发现“数据断层”。MQTT本身有断线重连机制,但光靠它不够,发布端必须把发不出去的数据先在本地缓存,等网络恢复再补发。
我遇到过最坑的一件事:某测站掉线三天,重连之后因为没做补发,三天数据直接消失,跑过去一看SD卡里明明存着原始测值。后来我在网关程序里加了一个简单的本地队列,用SQLite存待发消息,断线重连后按顺序补发。实现逻辑不复杂,但数据完整性好了不止一个档次。
补发的时候要有序,别一股脑全塞给broker,适当加sleep限速。另外QoS级别选1就够,QoS2在弱网下一次重传卡死的情况我也见过,千万别盲目追求高级别。
5.2 时间不同步:平台端永远对不上
大坝监测数据最讲究“时间对齐”,可现场设备的时间来源五花八门:有带GPS授时的,有只靠板载RTC的,还有干脆没电池一停电就归出厂时间的。G2如果没接NTP,停电重启后时间可能回到出厂值;BGK4500U这类便携设备的时钟更不靠谱。
我的对策是双时间戳:设备自己上报一个ts,网关或服务器再记一个gw_ts。入库时两个都存,查询时优先用校准后的时间。这个习惯救过我两次,一次是报警提前,一次是曲线错位,最后都是靠双时间戳查出来的。
5.3 固件版本差异带来的字段漂移
私下说一句:基康不同批次、不同固件版本的G2,MQTT报文里的字段名和单位都可能不一样。有的版本频率字段叫freq,有的叫F,还有的干脆用frequency。我这次还遇到过单位显示Hz,某些版本则直接给模数,需要乘一个系数才算出来。
解决方法不复杂但很笨:每次对接到不同版本设备,先把真实报文备份到本地,再写一个字段映射层。不要把解析代码写死在业务逻辑里,而是做成一份配置文件,设备固件不同就切换不同解析配置。这样以后升级固件,平台端的改动会小很多。
5.4 防雷供电与接线:最容易返工的地方
大坝监测站大多在户外,雷雨季一来,信号线上的感应雷想躲都躲不掉。串口线和网线裸露在机箱外的话,务必加信号防雷器,机箱也要做好接地。现场接地不规范,设备频繁死机是常事,查半天查不到原因。
供电方面,别让MQTT网关和传感器共用一路开关电源,传感器的激励信号很容易干扰通信模块,导致消息丢帧。用独立的工业级DC-DC电源给网关供电,输入电压留出20%以上的余量,稳定的电源能省掉一多半稀奇古怪的毛病。
接线最容易被新手忽略:串口线引脚顺序不同厂家定义不一样,接错线开机没反应甚至烧串口芯片都不稀奇。干活前先翻设备手册,用万用表量好引脚电平,宁可多花十分钟确认,也别等烧了再后悔。
写到这里,其实这次改造里最让我感慨的,不是代码写得有多顺,而是头一回用MQTT Explorer打开主题树、看到G2的消息一条条跳出来那一刻——数据终于不再是厂家软件的私藏品了。如果你现在也在捣鼓类似的接入,我给三条实在建议:先在办公室拿真实设备跑通全链路再上现场;每一个解析字段都要有真实报文背书,别信文档一家之言;正式切换前一定并行走一段旧系统,两边数据都对上了才动手拆老链路。大坝安全监测这行,稳字当头,慢慢来反而最快。