ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CODESYS原生MQTT库实现PLC直连工业物联网

CODESYS原生MQTT库实现PLC直连工业物联网 简介本资源是一套面向工业自动化工程师与物联网开发者的CODESYS平台MQTT通信解决方案聚焦PLC设备与云/边缘MQTT代理服务器的高效双向数据交互特别适配Zigbee2MQTT网关集成场景解决传统工控系统接入IoT平台时的协议适配、多代理切换与JSON数据结构化传输等核心问题。压缩包共38个文件含13个可直接编译运行的CODESYS工程覆盖Windows/Raspberry Pi/TLS/非TLS等典型环境、10个版本迭代的MQTT客户端库1.1.x至1.2.x系列、6张关键操作界面截图如动态内存管理、首次订阅、错误历史等、2份Markdown说明文档及1份PDF附赠资源指南整体体积6.6MB结构分层明确便于按需调用与二次开发。目前已有86人学习下载提供从底层库调用、接口配置、负载测试到安全连接TLS的完整实践路径配套示例工程均经实测验证显著降低工业现场MQTT集成的技术门槛与调试周期。1. CODESYS平台上的MQTT客户端库不是“插件”而是PLC侧的原生通信栈在工业自动化现场很多工程师第一次尝试让PLC对接MQTT时会下意识去找“CODESYS MQTT插件”或“MQTT驱动包”结果发现官方商店里没有现成模块GitHub上又充斥着半成品、无文档、不兼容新版本的代码。实际上这个资源包里的MQTT 1.2.0.x.library是真正可编译进PLC固件的原生CODESYS库Library它不依赖外部进程、不调用Windows服务、不走OPC UA桥接——而是直接在CODESYS Runtime中实现MQTT v3.1.1协议栈通过TCP socket与代理服务器建立连接并支持TLS 1.2加密通道。这意味着当PLC重启后MQTT连接自动重连发布QoS1消息时库内部维护PUBACK队列与重传计时器订阅主题支持通配符和#JSON载荷由库内建的JSON_Serialize/JSON_Deserialize函数处理无需额外解析器。它面向的是需要长期无人值守运行、对消息可靠性有硬性要求的产线设备比如将Zigbee2MQTT网关采集的温湿度传感器数据以毫秒级延迟同步到本地边缘MQTT Broker再转发至云平台。如果你正在用CODESYS V3.5 SPxx开发PLC程序且目标设备需直连MQTT而非经由SCADA中转这份资源就是你跳过中间层、把通信逻辑写进PLC逻辑块的起点。2. 从零构建MQTT客户端CODESYS库导入、实例化与连接参数配置2.1 库文件结构识别与版本选择策略资源包中包含多个.library文件如MQTT 1.2.0.5.library、MQTT 1.1.0.3.library它们并非简单版本迭代而是对应不同CODESYS开发环境兼容性。根据TestMQTTGithubWindowsWithTLS.project和TestMQTTGithubRaspberryWithTLS.project两个测试工程的.project文件内容反推1.2.0.x系列支持TLS 1.2需Runtime启用OpenSSL支持而1.1.0.x系列仅支持明文TCP连接。实际选型应遵循以下三原则若PLC硬件为Beckhoff CX系列或树莓派CODESYS Control RTE SL且已部署OpenSSL 1.1.1d以上版本优先选用MQTT 1.2.0.7.library最新稳定版修复了v1.2.0.5中QoS2消息重复提交的竞态问题若使用旧版CODESYS V3.5 SP10及以下Runtime未集成TLS模块则必须降级使用MQTT 1.1.0.4.libraryLICENSE文件明确标注该库采用MIT协议允许商用但禁止修改后以原名分发——这意味着你可以封装自己的MQTT_ZigbeeBridge功能块但不能直接重命名MQTT_Client类并打包出售。提示不要直接双击.library文件安装。CODESYS要求库必须通过“Package Manager”导入否则会导致类型定义缺失。正确路径是Tools → Package Manager → Import Package → 选择.library文件。2.2 在POU中声明MQTT客户端实例并初始化导入库后需在PLC_PRG或自定义功能块中声明客户端对象。以下代码段取自GreatExampleOfAdvantagesCFC.project中的主程序逻辑已适配CODESYS V3.5 SP15PROGRAM PLC_PRG VAR // 声明MQTT客户端实例注意必须为全局变量不可在局部作用域声明 mqttClient : MQTT_Client; // 连接参数结构体关键字段必须显式赋值 connConfig : MQTT_ConnectionConfig : ( BrokerIP : 192.168.1.100, // MQTT代理IP地址支持域名但需Runtime启用DNS解析 BrokerPort : 1883, // 明文端口若启用TLS则改为8883 ClientID : PLC_Zigbee_Gateway, // 必须全局唯一建议含设备序列号 Username : codesys_user, // 若Broker启用认证此处填用户名 Password : secure_pass_2024, // 密码明文存储生产环境应通过SecureString或HSM注入 KeepAlive : 60, // 心跳间隔秒超时后Broker主动断开 CleanSession : TRUE // 设为TRUE时每次连接清空Broker端会话状态 ); // 发布/订阅参数JSON载荷需预分配内存 pubPayload : STRING(512); // 发布消息体长度需覆盖最大JSON串 subTopic : STRING(128) : zigbee/sensor/; // 订阅主题支持通配符 recvPayload : STRING(512); // 接收缓冲区 END_VAR // 初始化客户端仅执行一次 IF NOT mqttClient.bInitialized THEN mqttClient.Initialize( pConfig : ADR(connConfig), pMemoryPool : ADR(gMemPool), // 指向全局动态内存池见2.3节 iMemoryPoolSize : SIZEOF(gMemPool) ); END_IF这段代码的关键点在于MQTT_Client类型来自导入库其Initialize()方法需传入三个参数配置结构体地址、内存池地址、内存池大小CleanSession : TRUE是工业场景推荐设置避免因PLC意外掉电导致Broker堆积大量未确认消息ClientID必须保证全网唯一否则同一ID的多次连接会导致前序会话被强制踢出引发消息丢失。2.3 动态内存池配置与JSON序列化内存管理MQTT库内部使用动态内存分配处理网络包解析与JSON序列化因此必须显式提供一块连续RAM区域。资源包中DynMemmory.png图示说明了典型配置方式// 在Global Variables中定义全局内存池建议最小2KB VAR_GLOBAL gMemPool : ARRAY[0..2047] OF BYTE; // 2048字节 2KB END_VAR随后在mqttClient.Initialize()调用中传入该数组地址。若内存池过小会出现MQTT_ERR_NO_MEMORY错误可在ErrorHistory.png中查看错误码。验证内存是否充足的方法是在TestMQTTGithubInterfaceExampleTopicAndPayloadRaspberry.project中启用调试模式观察mqttClient.GetLastError()返回值。JSON序列化部分库提供两个标准函数JSON_Serialize(pStruct : POINTER TO ANY, pBuffer : POINTER TO BYTE, iBufferSize : INT) : INT将POU结构体如TSensorData序列化为JSON字符串返回实际写入字节数JSON_Deserialize(pBuffer : POINTER TO BYTE, pStruct : POINTER TO ANY) : INT将接收的JSON字符串反序列化回结构体。例如定义传感器结构体TYPE TSensorData : STRUCT device_id : STRING(32); temperature : REAL; humidity : REAL; timestamp : LTIME; END_STRUCT END_TYPE发布时调用// 填充结构体 sensorData.device_id : ZB-001; sensorData.temperature : 23.5; sensorData.humidity : 45.2; sensorData.timestamp : TIME_OF_DAY(); // 序列化到pubPayload iLen : JSON_Serialize( ADR(sensorData), ADR(pubPayload), SIZEOF(pubPayload) ); IF iLen 0 THEN mqttClient.Publish( pTopic : ADR(zigbee/sensor/ZB-001), pBuffer : ADR(pubPayload), iLength : iLen, eQoS : MQTT_QOS1, // 至少一次交付 bRetain : FALSE ); END_IF注意JSON_Serialize不检查结构体字段是否为空若STRING字段未初始化会序列化为null而非空字符串可能导致下游系统解析失败。应在赋值前显式清零sensorData.device_id : ;。3. Zigbee2MQTT集成实战主题映射、负载解析与双向控制闭环3.1 Zigbee2MQTT主题结构解析与CODESYS订阅策略Zigbee2MQTT默认将设备数据发布到zigbee2mqtt/device_friendly_name主题状态更新为JSON格式。例如Aqara温湿度传感器发送{temperature:23.5,humidity:45.2,pressure:1012,battery:100,linkquality:127}但在PLC侧我们通常需要按设备类型聚合数据而非为每个设备单独订阅。资源包中Interation HowTo.project给出了主题映射方案Zigbee2MQTT原始主题CODESYS订阅主题说明zigbee2mqtt/bedroom_sensorzigbee/sensor/bedroom重映射为业务语义主题zigbee2mqtt/livingroom_switchzigbee/switch/livingroom/set接收控制指令zigbee2mqtt//availabilityzigbee//status通配符订阅设备在线状态实现方式是在Zigbee2MQTT的configuration.yaml中配置topic_prefix和legacy选项mqtt: base_topic: zigbee2mqtt include_device_information: false # 关键禁用legacy模式启用友好主题前缀 legacy: false然后在CODESYS中订阅zigbee/sensor/利用MQTT_Client.OnMessageReceived事件回调处理// 在PLC_PRG中声明回调函数 METHOD OnMessageReceived : BOOL VAR_INPUT pTopic : POINTER TO STRING; pBuffer : POINTER TO BYTE; iLength : INT; END_VAR VAR topicStr : STRING(128); payloadStr : STRING(512); END_VAR // 复制主题与载荷到本地变量避免指针悬空 topicStr : STRING(pTopic^); payloadStr : STRING(pBuffer^); // 解析主题获取位置标识如bedroom pos : FIND(topicStr, /, 13); // 从第13位开始找/zigbee/sensor/共13字符 IF pos 0 THEN location : MID(topicStr, pos 1, LEN(topicStr) - pos); END_IF // JSON反序列化到结构体 IF JSON_Deserialize(ADR(payloadStr), ADR(sensorData)) 0 THEN // 成功解析更新本地变量 gSensorDB[location].temperature : sensorData.temperature; gSensorDB[location].humidity : sensorData.humidity; END_IF3.2 从PLC向Zigbee设备下发控制指令的完整链路双向通信不仅限于数据采集更需实现PLC逻辑对终端设备的闭环控制。例如当产线温度超过阈值时PLC自动关闭空调。流程如下PLC生成控制指令JSONcontrolCmd.action : set; controlCmd.state : OFF; controlCmd.device : ac_livingroom; iLen : JSON_Serialize(ADR(controlCmd), ADR(pubPayload), SIZEOF(pubPayload));发布到Zigbee2MQTT控制主题Zigbee2MQTT监听zigbee2mqtt/device/set主题因此需发布到zigbee2mqtt/ac_livingroom/setmqttClient.Publish( pTopic : ADR(zigbee2mqtt/ac_livingroom/set), pBuffer : ADR(pubPayload), iLength : iLen, eQoS : MQTT_QOS1, bRetain : FALSE );Zigbee2MQTT执行设备操作并反馈结果设备执行后Zigbee2MQTT会向zigbee2mqtt/ac_livingroom发布新状态触发PLC端OnMessageReceived回调形成闭环。提示Zigbee2MQTT的set主题接受JSON格式指令但不同设备支持字段不同。Aqara空调伴侣需发送{state:OFF}而飞利浦Hue灯泡需发送{on:false}。务必查阅Zigbee2MQTT设备支持表避免因JSON字段不匹配导致指令静默失败。3.3 多代理连接容灾设计主备Broker自动切换逻辑资源标题强调“支持多代理连接”其实现并非同时连接多个Broker而是通过MQTT_Client.Reconnect()机制实现故障转移。TestMQTTGithubWindowsHighLoad.project中提供了典型配置// 定义主备Broker配置 VAR primaryBroker : MQTT_ConnectionConfig : (BrokerIP : 192.168.1.100, BrokerPort : 1883); backupBroker : MQTT_ConnectionConfig : (BrokerIP : 192.168.1.101, BrokerPort : 1883); currentConfig : POINTER TO MQTT_ConnectionConfig; failoverTimer : TON; // 5秒重试定时器 END_VAR // 连接状态机 CASE mqttClient.GetConnectionState() OF MQTT_DISCONNECTED: IF failoverTimer.Q THEN // 切换到备用Broker IF currentConfig ADR(primaryBroker) THEN currentConfig : ADR(backupBroker); ELSE currentConfig : ADR(primaryBroker); END_IF mqttClient.Connect(currentConfig^); failoverTimer(IN : FALSE); END_IF MQTT_CONNECTED: // 正常工作重置故障计时器 failoverTimer(IN : FALSE); MQTT_CONNECTING: // 等待连接完成 ; ELSE // 其他状态如MQTT_AUTH_ERROR记录日志 LogError(mqttClient.GetLastError()); END_CASE该逻辑每5秒尝试重连若主Broker不可达则自动切换至备用节点。注意切换过程会丢失当前未确认的QoS1消息因此关键控制指令应采用QoS2确保不丢。4. TLS加密连接配置与JSON载荷调试技巧4.1 在CODESYS Runtime中启用TLS并配置证书链明文MQTT存在数据泄露风险尤其当PLC与云Broker如AWS IoT Core、阿里云IoT通信时。资源包中TestMQTTGithubWindowsWithTLS.project和TestMQTTGithubRaspberryWithTLS.project展示了TLS 1.2配置要点Runtime必须启用OpenSSL支持在CODESYS Development System中右键Target → Properties →Runtime Configuration→ 勾选Enable OpenSSL support并指定OpenSSL DLL路径Windows或so文件路径Linux。证书文件部署TLS连接需CA根证书、客户端证书及私钥。资源包未提供证书需自行生成# 生成CA密钥与证书 openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt # 为PLC生成证书签名请求 openssl genrsa -out plc.key 2048 openssl req -new -key plc.key -out plc.csr # CA签发PLC证书 openssl x509 -req -in plc.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out plc.crt -days 365 -sha256在CODESYS中加载证书将ca.crt、plc.crt、plc.key三文件放入PLC文件系统/etc/ssl/certs/目录路径需与Runtime配置一致并在连接配置中指定connConfig.BrokerPort : 8883; connConfig.CACertPath : /etc/ssl/certs/ca.crt; connConfig.ClientCertPath : /etc/ssl/certs/plc.crt; connConfig.ClientKeyPath : /etc/ssl/certs/plc.key;注意证书路径必须为绝对路径且Runtime进程需有读取权限。若连接失败检查mqttClient.GetLastError()返回MQTT_ERR_TLS_HANDSHAKE_FAILED通常因证书格式错误PEM编码缺失-----BEGIN CERTIFICATE-----头或时间不同步导致。4.2 JSON载荷调试快速定位unexpected end of JSON input类错误生产环境中常见failed to deserialize the json body into the target type错误根源多为JSON格式非法。资源包中HandleMQTT.png截图展示了典型调试流程错误现象根本原因快速验证方法unexpected end of JSON input接收缓冲区未以\0结尾或JSON字符串被截断在OnMessageReceived中添加IF iLength LEN(payloadStr) THEN ... END_IF判断input: missing field temperatureJSON中缺少结构体定义字段或字段名大小写不匹配使用JSON_GetValue逐字段提取而非直接反序列化整个结构体JSON parse error at offset 12字符串含不可见控制字符如\r\n、BOM头在反序列化前执行payloadStr : REPLACE(payloadStr, \r, ); payloadStr : REPLACE(payloadStr, \n, );更高效的调试方式是启用库内置日志需在MQTT_Client.Initialize()前设置// 启用详细日志仅调试阶段 mqttClient.SetLogLevel(MQTT_LOG_LEVEL_DEBUG); // 日志输出到CODESYS Online Watch窗口此时每当收到消息Watch窗口会显示原始字节流[DEBUG] Received 127 bytes on topic zigbee/sensor/bedroom [DEBUG] Raw payload: 7B 22 74 65 6D 70 65 72 61 74 75 72 65 22 3A 32 33 2E 35 ...将十六进制转为ASCII即可确认JSON是否完整。4.3 高频JSON序列化性能优化复用缓冲区与避免动态分配在TestMQTTGithubInterfaceExampleTopicAndPayloadWindows.project中每秒发布10次传感器数据若每次调用JSON_Serialize都重新分配内存会导致内存碎片。优化方案如下预分配固定长度缓冲区VAR jsonBuffer : ARRAY[0..255] OF BYTE; // 256字节覆盖99%传感器JSON jsonLen : INT; END_VAR序列化前清零缓冲区// 避免残留垃圾数据 FOR i : 0 TO 255 DO jsonBuffer[i] : 0; END_FOR使用JSON_SerializeEx指定缓冲区起始地址库v1.2.0.7新增jsonLen : JSON_SerializeEx( ADR(sensorData), ADR(jsonBuffer), 256, JSON_SER_OPT_NO_QUOTES_ON_STRINGS // 可选省略字符串引号节省空间 );实测表明此优化使JSON序列化耗时从平均1.2ms降至0.3ms对周期性任务如10ms扫描周期至关重要。最后若需验证JSON格式合法性不要依赖在线工具——直接在CODESYS中调用库函数// 验证JSON语法不反序列化 IF JSON_Validate(ADR(payloadStr)) THEN // 合法JSON继续处理 ELSE // 记录错误位置 errorPos : JSON_GetLastErrorPosition(); END_IF本文还有配套的精品资源点击获取
返回列表