CODESYS集成MQTT客户端库:PLC设备上云与双向通信实战指南

发布时间:2026/10/22 18:23:57
CODESYS集成MQTT客户端库:PLC设备上云与双向通信实战指南 简介这套基于CODESYS平台的MQTT客户端库面向工业自动化与物联网开发者解决PLC设备与MQTT代理间高效可靠双向通信的需求同时包含Zigbee2MQTT集成方案支持多代理连接与JSON数据交互适用于智能产线、远程运维等场景。压缩包共38个文件大小仅6.6MB主体为13个project示例工程与10个library库文件MQTT 1.1和1.2多个版本另有PNG架构/操作示意图、TXT说明、Markdown集成文档和PDF教程从库添加、消息订阅发布到TLS加密连接均有清晰演示。示例覆盖Windows与Raspberry平台区分有无TLS环境方便读者对照不同软硬件环境调试结合文档中关于消息主题、负载格式和动态内存处理的讲解可直接迁移到自有PLC项目中复用。目前已有89人浏览学习适合正在评估或实施CODESYS联网方案的工程师快速入门并少走弯路。1. 基于CODESYS的MQTT客户端库给PLC配一条双向消息通道一条装配线既有工位温湿度盒子又有AGV呼叫按钮和能源表老板说把这些数据都接进PLC做完还能远程下发参数。用Modbus去一台台轮询点位表能改到崩溃这时候再回头看基于CODESYS平台的MQTT客户端库就明白它为什么成了设备上云方向上的主流做法。它是把PLC当成一个普通MQTT客户端向代理服务器发布数据、订阅指令实现双向传输还支持同时挂多个代理消息载荷统一走JSON调试和扩展都比点位表舒服得多。这套方案适合两类人一类是懂ST语言但没摸过MQTT的自动化工程师另一类是被各种网关盒子来回倒腾通信格式、想收拢技术栈的人。读完你应该能判断它值不值得用并且能直接照着搭一个最小闭环。2. MQTT进PLC的选型逻辑通信模型与CODESYS库的功能块划分2.1 从轮询到订阅MQTT在控制现场能带来什么传统PLC上位通信最常见的主从轮询模型PLC当主站一个周期里把仪表、网关、触摸屏轮一遍谁的点位多了轮询周期就拉长断线重连后要把历史数据重新捞一遍通信逻辑经常比控制逻辑还难维护。MQTT换了个思路PLC不再是主站而是代理服务器broker下的一个普通客户端所有设备都往broker上发布、订阅PLC和上位机、传感器盒子之间不需要直接认识。这个模型带来的直接好处是解耦。新增一台设备只要它往同一个代理上发主题PLC订阅对应主题就能收到不用改其他节点的通信配置。掉线重连也简单客户端带着clientId重新连上broker会按cleanSession配置决定是否补发离线消息自动化场景里常用的做法是采集类数据允许丢命令类用QoS1保证投递并做去重。对PLC工程师来说要把这个概念落到CODESYS里关键理解三点一是QoS等级的含义QoS0最多一次、QoS1至少一次、QoS2恰好一次现场最常用的是QoS0做高频采集、QoS1做命令下发二是keepalive心跳它决定broker多长时间认为你掉线三是Last Will遗嘱消息PLC异常断线时broker会替它发一条消息让上位机立刻知道这台设备失联了。2.2 CODESYS侧MQTT库的常见构成连接、发布、订阅三组功能块CODESYS里MQTT能力不是内嵌在标准库里的而是以库的形式装进工程常见做法是厂商或社区提供一组功能块按职责拆成三组连接管理、发布、订阅。我一般在库管理器里先确认这三组都在再决定工程怎么分层。// 连接功能块的典型实例化功能块名以你所装库的文档为准 VAR fbMqttConn : MQTTClient_Connect; hClient : DWORD; // 连接句柄多代理时用来区分 sBrokerUrl : STRING(120) : tcp://192.168.1.20:1883; sClientId : STRING(40) : PLC_LineA; bConnReq : BOOL : FALSE; bConnected : BOOL : FALSE; END_VAR // 需要建立连接时给 bConnReq 一个上升沿 fbMqttConn( bExecute : bConnReq, sBrokerUrl : sBrokerUrl, sClientID : sClientId, hClient hClient, bConnected bConnected);关键参数有三个sBrokerUrl的写法要带协议头tcp://表示明文1883ssl://表示加密443sClientID在同一代理下必须唯一重复会导致旧连接被踢掉线hClient输出句柄是后续发布和订阅都要带的身份标识多代理连接时尤其靠它区分是哪个链路收到的数据。还需要说明一点CODESYS的MQTT库调用多为触发式功能块bExecute给上升沿执行一次连接建立后靠keepalive维持。很多初用者会把这个功能块放在周期任务里每次执行反而导致重复建连正确做法是只在需要时给一次触发连接状态用bConnected去判断。2.3 选型判断什么场景适合这套什么场景还是走Modbus更省事不是所有PLC通信都该换成MQTT。我一般按三个问题做判断数据实时性要求是不是毫秒级是不是同一网段内少数设备直连控制回路本身是不是需要硬实时伺服轴控、运动插补这类链路老老实实走EtherCAT或PROFINET别拿MQTT凑热闹它本质是消息投递不保证周期抖动。该用MQTT的场景是数据采集、远程监控、跨网段传输、设备上云。PLC内做库的优势是省掉一个网关硬件接线最少、成本最低代价是通信占用了PLC自己的任务周期和内存。另一种常见做法是外置网关盒子PLC只管Modbus给网关网关做MQTT转发好处是PLC负载小坏处是多一个设备多一层排障。我的建议很简单改造老设备、PLC算力紧张加网关新项目、点位多且后续要加设备直接在CODESYS里做MQTT客户端库省一个中间环节。两者各是各的合理边界别指望一个方案通吃。3. 跑通最小闭环连接、发布订阅与JSON载荷的落地配置3.1 连接与断线重连最小ST代码与连接参数说明先从连接开始。上一章的代码能建连但生产环境不能只建连还要处理掉线重连。常见做法是把连接状态机化未连接时等待、连接中避免重复触发、已连接时周期检查心跳状态。CASE byConnState OF 0: // 空闲等待条件满足后请求连接 IF bNeedConnect THEN bConnReq : TRUE; byConnState : 1; END_IF 1: // 连接中等待功能块反馈 bConnReq : FALSE; IF fbMqttConn.bConnected THEN byConnState : 2; ELSIF fbMqttConn.bError THEN byConnState : 0; // 间隔一段时间再重试 END_IF 2: // 已连接周期检查异常则回到空闲 IF NOT fbMqttConn.bConnected THEN byConnState : 0; END_IF END_CASE这段状态机的意义在于避免周期性重复触发连接功能块。很多库的bExecute是边沿触发如果每个扫描周期都给TRUE代理端会看到同一个clientId反复连接表现为掉线、重连、再掉线。状态机里只有状态0才给bConnReq上升沿其他状态保持FALSE。连接参数里还有一个容易被忽略的项keepalive。常见做法设30到60秒比PLC扫描周期大一到两个数量级目的是让broker在真正掉线时能通过心跳超时感知而不是等TCP超时等上几分钟。如果PLC任务周期本身是10ms却把keepalive设成5秒代理侧稍微抖动就可能误判掉线。3.2 上报方向把PLC变量封装成JSON发布到主题CODESYS的ST没有原生的JSON序列化常见做法是用字符串拼接把PLC变量转成字符串后拼出JSON文本。温度值如果原来是整数除以10的精度先放大成整数再转字符串避免浮点格式化出现意外的科学计数法。// 上报当前设备状态到主题 plc/lineA/status sPayload : CONCAT( {temp:, DINT_TO_STRING(nTempInt), ,run:, BOOL_TO_STRING(bRunning), ,ts:, DINT_TO_STRING(udiTimestamp), }); fbMqttPub( bExecute : bPubReq, hClient : hClient, // 绑定到已建立的连接 sTopic : plc/lineA/status, sPayload : sPayload, uiQoS : 0, bRetain : FALSE);发布功能块的参数逻辑hClient填连接功能块输出的句柄多代理时这个参数决定消息发给哪个brokeruiQoS为0时最快但可能丢包适合周期性状态上报bRetain为TRUE时broker会把最后一条消息存下来新订阅者一上来就能拿到当前值适合温湿度这种慢变量不适合会误导控制逻辑的瞬时命令。这里要提醒一个坑CONCAT拼接的字符串超过CODESYS字符串变量最大长度时会静默截断长JSON会莫名缺字段。我一般把sPayload声明成STRING(255)或更大并且发送前用LEN检查长度超过阈值就拆包或者改用轮询读取缓冲区。3.3 下发方向订阅主题并解析JSON回写PLC变量订阅是MQTT客户端库里最体现差异的部分。CODESYS库的常见实现是订阅功能块注册一个主题过滤器之后收到的消息放进内部缓冲区或回调队列用户在周期任务里轮询读取。回调里直接操作PLC变量不是主流做法因为中断上下文里访问全局变量容易和数据一致性打架。// 订阅下发命令主题支持通配符 fbMqttSub( bExecute : TRUE, hClient : hClient, sTopicFilter : cmd/lineA/#, // # 匹配该层级所有下级主题 uiQoS : 0); // 在每个扫描周期轮询是否有新消息 bGotMsg : fbMqttSub.bMessageAvailable; IF bGotMsg THEN sRcvTopic : fbMqttSub.sMessageTopic; sRcvPayload : fbMqttSub.sMessagePayload; // 接下来做JSON解析把目标值写入对应PLC变量 ... END_IF订阅后解析JSON的策略有两种一种是用CODESYS的JSON解析库用路径表达式取键值另一种是字符串查找截取。前者工程规范后者临时排查快。我自己的习惯是正式工程用解析库因为它能处理键顺序变化和转义字符调试阶段直接在监视窗口里用查找函数看字段几秒钟能定位问题。JSON解析库有一个常见约定键名写带点号的路径temp.zone1会先找temp对象再找zone1键。可Zigbee2MQTT这类系统生成的键名常带连字符或点号解析库的路径分隔符会踩坑后面专门讲。3.4 本地闭环验证的检查清单跑通闭环后别急着接真实设备先在CODESYS仿真或者一台小PLC上做验证我的检查顺序如下。第一broker连通性用PC上的MQTT客户端工具订阅#看PLC发布的消息能不能收到这是最快的一层验证。第二连接稳定性让PLC反复断电重启观察broker上是否出现clientId冲突旧连接有没有被正常释放。第三JSON完整度订阅端收到的JSON逐字段和PLC原始变量对比重点检查字符串截断、大小写、时间戳类型。第四命令下发回环从订阅端发一条JSON命令PLC收到后改变输出再反过来发布一条状态确认双向链路都通。这个回环验证做完最小闭环就稳了然后再去接多代理和Zigbee2MQTT。4. 多代理连接同机同时对接多个Broker的路由与状态管理4.1 多代理场景辨析与连接资源分配单代理方案覆盖不了所有现场。我遇到最典型的是本地边缘代理负责采集产线实时状态给MES看云端代理负责远程运维和报表给总部看。同一个PLC必须同时和两个代理维持连接数据还不能发串。多代理不是把连接功能块复制一份那么简单连接句柄、clientId、心跳、遗嘱、重连状态都必须按代理维度分开管理。常见做法是用结构体数组描述代理连接每个代理一个元素索引号就是路由ID。TYPE ST_MqttProxy : STRUCT sBrokerUrl : STRING(120); sClientID : STRING(40); hClient : DWORD; // 连接句柄建连成功后由库回填 byState : BYTE; // 0空闲 1连接中 2已连接 bError : BOOL; byErrCode : BYTE; END_STRUCT END_TYPE VAR arrProxy : ARRAY [1..2] OF ST_MqttProxy; END_VAR用数组管理的好处是重连逻辑可以统一写成FOR循环新增代理只是加一个数组元素和连接参数。这里要特别注意clientId在同一个broker内唯一但不同broker之间可以相同所以同一个PLC在两个代理上用同一个ID没问题只要不是连到同一个broker就行。4.2 clientId、Last Will与重连状态机怎么处理多代理下最容易翻车的是重连逻辑。两个代理一个断线重连代码如果不区分句柄很可能把还活着的那个连接也触发一遍重复建连。所以重连状态机必须和数组下标绑定。FOR i : 1 TO 2 DO CASE arrProxy[i].byState OF 0: IF bNeedReconnect[i] THEN fbMqttConn[i](bExecute : TRUE, sBrokerUrl : arrProxy[i].sBrokerUrl, sClientID : arrProxy[i].sClientID, hClient arrProxy[i].hClient, bConnected arrProxy[i].bConnected); arrProxy[i].byState : 1; END_IF 1: fbMqttConn[i](bExecute : FALSE); IF arrProxy[i].bConnected THEN arrProxy[i].byState : 2; ELSIF fbMqttConn[i].bError THEN arrProxy[i].byState : 0; // 稍后重试 END_IF 2: IF NOT arrProxy[i].bConnected THEN arrProxy[i].byState : 0; END_IF END_CASE END_FOR每个代理独立走一遍状态机互不干扰。另外还要给每个代理单独配Last Will遗嘱内容至少包含代理代号和PLC状态这样broker能替PLC上报这台设备从这里掉了上位机和云端都能第一时间感知是哪个区域失联。4.3 消息路由与话题规划避免跨代理串扰多代理最头疼的不是连不上而是消息发错地方。比如本地MES不该看到云端的远程升级指令云端又不该采集本地的高频过程数据。话题规划要按方向切干净。我一般按主题前缀划分本地上报全部走local/plc/...云端上报走cloud/plc/...下发命令本地走cmd/local/...云端走cmd/cloud/...。主题过滤器和发布调用各绑定各的句柄订阅时更要注意不要图省事用#把两个代理的消息全收到一个缓冲区里。如果要跨代理转发比如本地MES需要把一条数据同时发到云端常见做法不是让PLC发两次而是PLC发布到本地broker由broker侧的路由规则镜像到云端broker。PLC端只保证自己发布到正确主题跨代理转发尽量交给broker或网关做别让PLC任务承担太多消息搬运。4.4 多代理ST代码框架这里给一个发布方向的路由选择框架体现同一份PLC变量按代理分别构建载荷。CASE iTargetProxy OF 1: // 本地代理发布高频状态 sPayload : BuildJson_Local(); // 自建函数拼接本地点位JSON fbMqttPub(bExecute : TRUE, hClient : arrProxy[1].hClient, sTopic : local/plc/lineA/status, sPayload : sPayload, uiQoS : 0); 2: // 云端代理发布低频汇总 sPayload : BuildJson_Cloud(); // 自建函数拼接云端汇总JSON fbMqttPub(bExecute : TRUE, hClient : arrProxy[2].hClient, sTopic : cloud/plc/lineA/summary, sPayload : sPayload, uiQoS : 1); END_CASE这个框架把发什么数据发到哪个代理用什么QoS三件事分开改路由只改CASE分支不动数据拼装逻辑。实际项目里我还会加一个上电自检两个代理的连接状态都健康后才开放命令下发防止PLC带着半连接状态执行来自云端的误指令。5. Zigbee2MQTT集成联调与常见问题排查从主题映射到数据落地5.1 Zigbee2MQTT的通信链路设备状态怎么变成JSON推给PLCZigbee2MQTT做的事情是把Zigbee无线设备桥接到MQTT。它连接Zigbee协调器订阅协调器上报的无线数据再转换成JSON发布到MQTT代理。对PLC来说Zigbee设备完全透明PLC只面对主题和JSON。链路是Zigbee传感器 → Zigbee2MQTT → MQTT代理 → PLC的MQTT订阅 → 解析JSON → 回写变量。典型主题是zigbee2mqtt/设备友好名比如一个叫temp_sensor_01的设备它上报的消息长这样{ temperature: 25.3, battery: 89, linkquality: 55, last_seen: 2025-01-15T10:24:00 }PLC要拿到温度就订阅zigbee2mqtt/temp_sensor_01解析出temperature字段。另外Zigbee2MQTT还维护一个zigbee2mqtt/bridge/devices主题用于查询设备列表和状态联调时很有用可以确认设备到底有没有正常上报。5.2 排查1CODESYS侧收不到Zigbee2MQTT消息现象Zigbee2MQTT运行正常用PC端MQTT工具能收到温度可PLC程序里bMessageAvailable始终为FALSE。原因多半是订阅的clientId或连接状态没对上或者PLC订阅的broker地址不是Zigbee2MQTT写入的那个代理。Zigbee2MQTT配置里写的是mqtt://某IP:1883PLC连的却是另一个代理两边的broker不是同一个自然收不到。解决先用PC工具连PLC所连的那个代理订阅#确认Zigbee2MQTT有没有把消息发到这个代理上。确认后把PLC的sTopicFilter从zigbee2mqtt/#开始逐级收窄看哪一级断了。还有一个常见细节订阅功能块要在连接成功后执行很多库在未连接时调用订阅会静默失败要在状态机的已连接状态下发订阅请求。5.3 排查2JSON键名不兼容导致解析失败现象消息明明收到了但JSON解析库取不到temperature的值或者取出来是空字符串。原因Zigbee2MQTT的JSON键名本身没有特殊字符问题常出在嵌套对象上比如有些设备上报{illuminance: {lux: 320}}用路径表达式取illuminance.lux时解析库把点号当分隔符可设备上报的键里如果真带点号就会被误拆。解决先看原始载荷别盲猜键路径。用PC订阅工具抓一条完整JSON再逐级用解析库的取键方法验证。CODESYS字符串查找法在这里反而快直接FIND(sRcvPayload, temperature)定位后截取数值段联调期间临时用稳定后再固化到正式封装里。5.4 排查3无线设备掉线但PLC不知道现象Zigbee温湿度传感器电池耗尽或被挪走PLC里温度还停留在最后值控制系统拿着一个假温度在跑。原因Zigbee设备是无线低功耗休眠或离网后不会主动通知MQTTPLC没有收到新消息但也不可能凭空知道设备已经失联。解决把Zigbee2MQTT的availability机制用起来。Zigbee2MQTT默认会发布zigbee2mqtt/设备名/availability值为online或offline。PLC订阅这个主题在解析逻辑里把offline直接映射成设备故障变量并在HMI上亮黄色提醒。再配合last_seen字段做超时判断比如超过10分钟没有温度上报就置位传感器超时标志比只信last value安全得多。5.5 排查4MQTT任务拖慢扫描周期现象接上Zigbee2MQTT后PLC任务周期从10ms变成50ms甚至出现任务超时报警。原因常见的错误做法是把JSON解析和字符串拼接直接放在高频任务里每个扫描周期都执行大量字符串查找操作而ST的字符串函数本身就比较重消息多的时候直接把任务周期拉爆。解决把MQTT多收多解析的活放到独立任务或低优先级任务里高频控制任务里只放一个变量翻转标志等收到完整消息后再联动。如果CPU实在紧张把JSON解析库换成字符串查找法解析耗时能降不少。另一个常见做法是消息节流Zigbee2MQTT里对温度这种慢变量设置上报最小间隔比如5分钟一条避免设备频繁上报压垮PLC。6. 收尾验证抓包、离线缓存与重连参数的进阶调试6.1 用抓包确认协议层问题应用层排查找不到原因时别在代码里瞎试先抓包看协议。用Wireshark抓broker所在网卡过滤条件mqtt || tcp.port1883重点看三件事连接报文里的clientId对不对、CONNACK返回码是什么、掉线前有没有发DISCONNECT。有一次设备不断闪断代码层怎么查都正常抓包发现PLC在反复CONNECT原来broker配置了最大连接数限制新连接踢掉了旧连接。这种问题看代码永远看不出来只有抓包能看到完整的连接时序。抓包习惯了之后MQTT调试效率比纯看日志高很多。6.2 离线缓存与重连参数调优如果担心PLC短暂掉线导致关键数据丢失有两招。第一招是broker侧用RETAIN消息PLC发布时bRetain : TRUEbroker会保留最新值重连后新订阅者立刻拿到当前值适合状态类数据。第二招是PLC侧缓存把断电或断网期间要补发的数据放进环形缓冲区重连后按顺序补发适合能源计量这类需要连续性的数据。重连参数上注意三个数keepalive设30到60秒重连间隔从5秒开始按指数退避最大不超过60秒避免broker被打爆cleanSession通常设FALSE让broker保留订阅关系和队列但一定要搭配遗嘱消息否则broker堆积大量僵尸会话。把这三组数按现场网络情况调一遍比盲目改代码管用得多。我最早做这套的时候把所有消息都设成QoS1结果累积计数在broker重投时翻倍后来给自己立了规矩采集用QoS0命令用QoS1并在载荷里带消息序号做去重。这个习惯一直保留到现在。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询