
1. 项目背景与需求拆解1.1 为什么SCADA系统要引入MQTT做工业自动化项目做到一定阶段你会发现一个问题SCADA系统本身的数据采集和监控能力已经很成熟了但在数据往外走的环节经常卡壳。传统的OPC DA受限于Windows平台和DCOM配置OPC UA虽然跨平台能力强但在物联网场景下还是偏重至于把数据直接写数据库、写文件那种方案项目多了之后维护成本会堆得很高。我最早接触中控SCADA的MQTT需求是一个食品行业的MES对接项目。现场用的是中控InPlant SCADA采集多条产线的PLC数据MES厂商希望以消息订阅的方式实时拿到产线的产量、设备状态、工艺参数。如果走OPCMES那边得部署OPC客户端并且要能访问到SCADA的网段安全策略很难谈后来我们决定用MQTT把SCADA数据推送到中间消息服务器MES、看板系统、手机App都去订阅同一个Broker一个月时间就把数据链路打通了。这里说的MQTT驱动采集是指SCADA通过MQTT协议作为客户端去连接消息服务器既能订阅外部主题获取数据相当于把MQTT Broker当作一个数据源采集其他设备或平台发布的消息也能在后面提到的转发场景中作为一个方向的数据出口。中控SCADA从V6.1之后的版本开始支持MQTT相关组件早期版本则往往靠外部网关或定制开发来弥补。MQTT本身的发布/订阅模型特别适合SCADA数据往上层平台流动的场景。SCADA天然是一个实时数据库数据量不大但粒度细、实时性要求高MQTT的消息头非常小又是基于TCP的长连接比轮询数据库或定时推送HTTP接口在实时性和资源占用上都有优势。而且Broker可以多端订阅一个数据源同时喂给MES、可视化大屏、云平台SCADA侧只需要发布一份数据。1.2 中控SCADA的MQTT接入架构从整体架构上看中控SCADA做MQTT接入一般分为三种形态第一种是IO驱动接入形态。把MQTT Broker当作一个采集源通过编写或者配置MQTT驱动订阅指定的Topic对收到的JSON报文做解析后映射到SCADA的变量上这样MQTT消息中的数据就和其他PLC、仪表的数据一样进入实时数据库参与报警、历史存储和画面展示。第二种是转发服务形态。SCADA把工程里的实时变量按规则收集起来定期或者按变化推送给Broker供上层平台消费。这种形态下SCADA本身不主动去读别人的消息而是作为数据出口。中控InPlant SCADA里通常是通过运行环境中的转发组件或者类似功能来配置选择变量、设置上报方式、填写Broker地址和Topic。第三种是混合形态既订阅又转发。现场仪表通过窄带物联网比如4G DTU把数据推送到BrokerSCADA通过MQTT驱动订阅下来用于本地监控同时SCADA又把处理后的聚合数据转发到另一个平台的Broker。这种形态在分布式水处理、光伏电站、燃气站这种网关多的场景里很常见。实际选型的时候最重要的一个判断标准是数据流的方向和是否有点位映射需求。如果你只是想让SCADA的数据单向送到IoT平台那就走转发服务如果MQTT链路另一头有大量遥测数据要进入SCADA做分析展示那就要把MQTT当作驱动来采集。少数项目需要两个同时做。2. MQTT驱动采集侧的配置要点2.1 创建通道、设备与点表映射中控SCADA里做MQTT驱动采集第一步是在IO设备管理或者叫通讯配置、驱动管理不同版本入口名称有差异中创建一个新的通道选择MQTT协议驱动。这一步比较关键的是通道的通讯参数一般会包括Broker地址、端口号、ClientID、用户名密码、KeepAlive时间以及是否启用TLS/SSL。这些参数和你在MQTT客户端工具比如MQTTX、MQTT Explorer里填的东西是同一套逻辑。很多新手在这里会踩一个坑ClientID。Broker通常要求一个ClientID在同一时刻只能存在一个连接如果你开了多个项目或者多个客户端抢占同一个ClientID后连接的那个会把前面的踢下线表现就是连接总是刚建立就断开。我习惯把ClientID设置为“SCADA站点编号功能编号”这种唯一结构例如InPlant_PlantA_Collect1避免冲突。创建设备时要配置订阅的Topic。这个地方需要注意Topic的层级和通配符。MQTT的Topic用斜杠分层支持加号通配单层、井号#通配多层。实际项目中我建议订阅一个独立的业务Topic区分数据类型比如设备状态和设备数据分开订阅而不是一股脑用井号通配把整个主题空间都拉下来否则报文解析逻辑会非常混乱。点表映射部分中控SCADA的MQTT驱动通常支持通过关键词或JSONPath表达式来关联变量。举个例子Broker收到的JSON消息是这样{ device_id: PM001, ts: 2025-06-18 10:30:00, metrics: { temperature: 65.2, pressure: 1.32 } }那么定义变量时温度的映射表达式通常可以写成类似$.metrics.temperature的形式表示从JSON根节点出发逐级定位到temperature字段。如果驱动支持用字段名直接匹配也可以简化配置但那种方式对报文结构变化的容错性会比较差我建议尽量用层级明确的路径表达式。还有一点不要忽略变量数据类型。JSON消息中的数值可能是整数、小数、字符串、布尔值SCADA变量在建立时必须和实际数据类型匹配。尤其是开关量在MQTT消息中往往以0/1整数或ON/OFF字符串出现如果变量类型定义错了数据要么不更新要么一直显示通讯异常。中控SCADA的变量类型中I/O整数、I/O实数、I/O离散都需要在配置时选对对应的数据类型解析规则。2.2 报文调试与链路验证MQTT驱动配置完之后不要急着在画面里看数据先做链路验证。我常用的方法是先用MQTTX或者Mosquitto自带的mosquitto_sub订阅SCADA要发布的Topic同时在SCADA侧用调试工具或者驱动自带的报文监视窗口看收发的原始报文。如果SCADA收到消息但变量不刷新按下面顺序排查第一先看Topic是否匹配。消息发布到了device/PM001/data但SCADA订阅的是device//data理论上加号是可以匹配的如果订阅写的是device/PM001/#也能匹配到同一消息但要注意井号通配会把这个前缀下的所有层级都接收进来如果Broker上没人往这个前缀发消息自然就收不到。第二看Payload格式是否合法。JSON报文里多个了一对括号、或者单引号代替了双引号驱动解析失败变量不会更新。你可以用SCADA的报文调试窗口复制原始报文放到在线的JSON格式化工具里验证一遍这个操作非常简单但很实用。第三看QoS级别是否满足要求。SCADA订阅的Topic如果QoS0Broker和客户端之间的消息传输就是尽力而为在客户端短暂断线或者网络抖动时消息会丢。对于数据采集场景建议订阅时把QoS设为1至少保证消息到达Broker后能被正确投递一次。链路验证稳定后再让现场设备侧配合发送真实报文确认数据刷新周期和时标。我遇到过一种情况设备每小时才上报一次数据SCADA变量看起来“不刷新”实际上是正常现象。所以做调试前一定要和业务方确认数据的真实上报频率不要盲目判断通讯故障。3. MQTT转发服务配置与主题规划3.1 转发规则与JSON格式设计相比MQTT采集更多项目中SCADA的角色是“数据源”也就是把PLC、仪表采上来的数据通过MQTT转发给上层平台。中控SCADA的MQTT转发服务核心是定义转发组和点表。转发组的含义是一批变量、一种上报策略、一个目标Topic。同一个工程里可以建多个转发组比如把产量数据放到产量组把设备状态放到状态组把能耗数据放到能耗组。这样做的目的不只是分类清晰还方便上层平台按需订阅——它只想要产量数据就订阅产量Topic不会收到一坨无关JSON。配置转发变量时有几个参数需要重点关注上报方式周期上报还是变化上报。周期上报适合连续量、累计量比如温度、流量、产量变化上报适合开关量、状态量比如设备启停、故障信号。项目可以同时使用两种方式分组配置。变化死区变化上报时不是变量一变就上报而是在变化幅度超过设定值时才报。这样可以过滤掉采集信号的微小抖动避免无效消息刷屏。比如温度死区设0.5℃那一分钟内波动还在0.5℃以内就不上报一旦超出就立即上报当前值。这个参数对Broker压力和流量计费影响很大尤其现场是用4G网络连云端Broker的场景合理设置死区能省不少流量。周期值周期上报的间隔一般在1到60秒之间可选。对于大多数IoT平台5到10秒足够了没必要追求1秒级转发因为SCADA内部采集周期可能本身就比1秒长。JSON格式的设计是另一个值得提前规划的点。我建议的转发格式尽量保持扁平化避免嵌套太深方便上层平台解析。一个典型的点位转发JSON可以这样{ device_code: PLANT_LINE1, collect_time: 2025-06-18 10:31:02, points: [ {id: TEMP_REACTOR, value: 65.2, quality: 0}, {id: PRESS_REACTOR, value: 1.32, quality: 0}, {id: PUMP_STATUS, value: 1, quality: 0} ] }这里quality用0表示正常非0表示质量戳异常比如通讯故障、手动置数这个字段对上层平台很重要在后续数据分析时可以过滤坏质量数据。如果你的SCADA变量本身带有质量戳转发时一定要把这个字段映射出来不要只传值。关于collect_time不同项目有两种做法一种用SCADA收到数据的服务器时间作为采集时间另一种用设备上报的时间戳。我建议在字段名上区分比如collect_time是设备源时间report_time是SCADA转发时的时间这样遇到网络延迟产生的数据漂移可以通过对比两个时间戳来判断。3.2 QoS、遗嘱与断线缓存策略配置转发服务时有些MQTT参数属于“不踩坑不知其重要”的类型。第一个就是发布消息的QoS。SCADA本地采集几乎不出问题但数据经过网络发到远程Broker链路质量就不一定了。MQTT Qos 0的消息在网络抖动时可能直接丢失QOS 1则保证至少收到一次QOS 2保证恰好一次。IoT平台侧的监控告警场景我一般设QoS1如果对接的是计量计费这种敏感数据可以考虑QoS2但代价是消息吞吐量下降。第二个是遗嘱消息LWT。MQTT这个机制很适合SCADA做设备在线状态上报。你可以在建立连接时设置一个遗嘱Topic和遗嘱Payload例如在Topicinplant/plant1/online发布遗嘱消息{online:0}。正常情况下SCADA转发服务会周期性发心跳表示在线如果SCADA异常断电、断网Broker会代替关闭的连接发布遗嘱消息上层平台立即就能感知到“采集服务掉线了”。这个功能在一个工厂有多个SCADA站点的场景里非常实用相当于给SCADA加了一个远程心跳监控。第三个是断线缓存。MQTT本身协议上不提供离线消息缓存除非Broker开启了持久会话并且客户端用持久会话连接。SCADA转发服务如果和Broker的会话是持久的SCADA短暂断线重连后离线期间Broker缓存的消息会被重新投递给SCADA但这条链路通常用于SCADA发布数据所以更贴切的场景是反过来——Broker把上游过来的消息缓存给离线的订阅终端。对于SCADA转发这个方向如果上层平台断线了消息就丢了除非平台侧用QoS1或2订阅。所以SCADA转发服务侧要注意设置合理的发布QoS并且在和平台联调时明确双方的消息保障等级。这里还想提一个和SCADA工程运行相关的问题某些转发组件不会缓存本地数据SCADA运行服务一旦重启转发也会中断重启瞬间的数据点就丢了。如果项目要求高连续性比如产量累计不能断务必在方案阶段明确Broker侧是否启用了持久会话、平台侧是否做了离线补传接口而不是指望SCADA转发端自己补历史。4. 与S7-200CN等PLC联调中的细节4.1 S7-200CN接入的两种常用方案虽然中控SCADA自身支持的驱动类型很多但很多老项目里用到S7-200CN恰恰是最容易卡壳的一环。S7-200CN是西门子S7-200系列在国内的版本早期产品只自带PPI串口不支持以太网口的S7协议。SCADA要采集它的数据最常用的方案有两种。第一种是SCADA直接通过串口走PPI协议采集。中控SCADA支持PPI驱动但前提是工控机上要有可用的串口而且PPI通讯速率通常设置成9.6kbps或19.2kbps现场有多个200CN时一个一个轮询速度会非常慢。这种方案适合站点距离近、点数少、实时性要求不高的场景。第二种方案是加一个以太网模块或者协议转换器比如当时常用的CP243-1模块或国产的串口转以太网模块。模块一端接到S7-200CN的PPI口另一端连以太网SCADA通过S7协议iso-on-tcp端口102去访问。这种方式通讯速度快很多而且多个PLC可以组网SCADA只需要在网络里配置不同IP地址即可。我做的多数项目都选了这种方案毕竟一张网线比拉一串RS485线省心太多了。加了协议转换器之后中控SCADA里创建IO设备时驱动选S7-TCP或者MicroWin取决于SCADA版本地址里填模块的IP和机架号。S7-200没有机架号的概念通常默认机架0、插槽0即可。注意转换器有的带数据区映射有的直接把PPI变量区透传给S7协议联调前一定要看转换器的手册确认DB块、M区、V区的访问方式是否一致。这里要特别提一句S7-200CN的V区变量存储器是最常用的数据区很多程序都把工艺参数放在VB或VW里。在SCADA中建立变量时地址格式一般是类似VW100、VD200这种写法但要和你转换器那边的地址映射一致。曾经有个项目PLC程序里VW100存的是液位但转换器默认把V区的字节偏移做了调整SCADA读到的数字明显不合理查了半天手册才发现是地址偏移的问题。4.2 变量映射与通讯优化S7-200CN接入SCADA后变量映射的规划直接影响后期维护效率。我见过不少工程点表命名用中文拼音缩写比如wd、yl、pump1虽然也能用但半年后想改就全靠回忆了。我建议命名规则采用“工艺段_设备_变量类型_序号”的形式比如LINE1_REACTOR_TEMP_01这样和MQTT转发时的点ID也能保持一致后面设计JSON格式时省很多事。通讯参数方面S7-TCP协议的轮询周期建议设置在100到300毫秒之间。设得太快PLC的通讯负载会明显上升严重时会影响PLC本身的扫描周期设得太慢SCADA画面上的实时性又跟不上。对于S7-200CN这种老PLC我一般先设200毫秒如果监控点数少于50个再把周期降到100毫秒。单个IO设备下的变量数量不要贪多一个PLC的监控点超过500个时建议拆分为多个设备逻辑通过不同的轮询周期来优化效率。另外一个容易被忽视的点是通讯超时和重试次数。S7协议在SCADA侧如果写的是默认值有时候在PLC重启、网络短断时会连续报错然后SCADA会反复发送连接请求。适当的超时设置是连接超时3000毫秒请求超时1000到1500毫秒重试次数2次。这样PLC重启期间SCADA不会疯狂重连而且恢复之后能自动把连接建立起来。SCADA这一侧采集到的数据最终要进入MQTT转发这里有一个细节在转发变量时要检查变量是否勾选了“初始值允许转发”或者类似的标记。有些SCADA变量在通讯故障时保持最后值如果转发端不做质量判断上层平台会误以为数据仍在正常刷新。所以转发变量最好带上质量戳字段或者利用变化死区功能过滤故障时的异常波动。5. 常见问题与排障经验5.1 连接反复断开MQTT转发服务配置完成运行几分钟后Broker后台看到客户端断开重连循环这种问题我遇到过很多次原因基本逃不出这几类一是ClientID冲突。前面说过多个客户端用了同一个ClientID后者把前者踢下线。排查方法很简单在Broker的日志里搜ClientID看看是不是来自不同的IP如果是说明有别的客户端顶号了。二是KeepAlive设置太短。SCADA侧的MQTT连接保活时间如果设置成5秒甚至更短而网络偶尔有超过5秒的延迟Broker就会判定客户端失联从而断开连接。一般建议KeepAlive设置为30到60秒同时依赖MQTT底层的TCP超时来兜底而不是把保活时间压得很短。三是Broker的会话过期时间设置。如果Broker上设置了Session Expiry Interval为0并且SCADA使用非持久会话那SCADA每次重连后旧会话立即销毁如果在重连瞬间还有订阅关系就会反复重建会话来接收消息也表现为连接不稳定。第四类可能很多人没想到防火墙对长连接的空闲超时。办公网和云服务器之间常见的NAT超时时间可能是300秒左右如果MQTT连接在超过这个时间没有任何数据包中间设备会把这条TCP连接静默回收。SCADA侧如果消息频率低比如设备状态量变化上报可能几分钟都不发数据这时候对端Broker以为连接还在但中间的防火墙已经把连接断了。解决方案是启用MQTT层面的KeepAlive或者PINGREQ机制确保连接空闲时也有心跳包在走。5.2 数据上云延迟高SCADA本地画面数据刷新正常但上层平台收到的数据总是慢半拍。这种情况通常不是MQTT本身慢而是链路中的某个环节出现了排队。先检查SCADA转发服务的上报周期。如果设的是60秒周期那数据从采集到转发最多有60秒的延迟这是正常现象。如果业务要求5秒级的实时性调整周期即可。再检查MQTT Broker所在服务器的带宽和负载。有一天现场上报了上万台设备的数据转发Broker的消息吞吐到了瓶颈消息堆积严重平台收到数据的延迟自然会越来越大。这种情况优化思路有两个一是把不同数据类型拆分到不同Topic错峰发送二是调整上报策略把周期上报改成变化上报减少无效消息。第三个检查点是平台侧的消费速度。无论是MES的数据库写入还是IoT平台的消息处理如果订阅端的消费速率跟不上消息产生速率消息就会在Broker上积压从监控上看就是SCADA发送的消息时间戳和平台接收到的时间戳差越来越大。这种问题在MQTT本身没有直接表现Broker的监控面板里能看到队列积压数。5.3 平台收到数据后不闭环数据源和平台都正常但平台在业务上始终收不到完整的数据闭环这种情况多数是数据质量问题而不是传输问题。我遇到过某项目平台统计产量时发现数据断断续续追查后发现SCADA转发端把质量戳异常的数据也发布出来了平台没有过滤坏质量入库后算出来的产量自然不对。解决方式是转发端在消息中增加quality字段平台侧入库时对quality进行判断非0一律不入库至少不打入正常统计。还有一个常见问题是时间字段。平台侧默认用消息到达Broker的时间作为数据时间但SCADA发布的collect_time是PLC的本地时钟。如果PLC断电过时钟不准平台看到的时序就是乱的。这时候在SCADA转发配置里要注意时间格式是否精确到毫秒如果只有秒级遇到同一秒内多条消息的排序需求时会出现顺序不稳。我通常转发时把时间字段的输出格式配成毫秒级时间戳或者ISO8601带毫秒的字符串。最后是老生常谈但必须提一句的备份和版本管理。中控SCADA工程里MQTT驱动和转发配置的点表修改完一定要导出备份。每次跟现场联调改Topic、改地址搞完就把配置文件归档。我吃过一次亏现场调试完忘了备份后来工程被误覆盖重新配置花了一整天。SCADA这种长生命周期项目配置管理就是后续维护的安全网。