ARTICLE DETAIL

资讯详情

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

CODESYS集成Zigbee2MQTT的MQTT客户端实战指南

CODESYS集成Zigbee2MQTT的MQTT客户端实战指南 简介本资源面向工业自动化工程师、PLC开发人员及物联网系统集成从业者聚焦CODESYS平台下MQTT通信的工程落地难题提供一套开箱即用的PLC与云/边缘MQTT代理双向通信解决方案并深度整合Zigbee2MQTT实现低功耗无线传感网络接入。资源包共38个文件含13个可直接编译运行的CODESYS工程覆盖Windows/Raspberry Pi双平台、TLS加密与无TLS场景、10个版本迭代的MQTT核心库1.1.x至1.2.x系列、6张关键流程图解如动态内存管理、首次订阅、错误历史等以及说明文档、许可证与PDF附赠资料整体压缩包仅6.6MB轻量易部署。目前已有89人学习下载读者可直接复用多代理连接架构、JSON数据格式化收发逻辑、CFC编程范式示例及Zigbee设备物模型映射方法显著降低工业现场MQTT协议栈开发门槛与调试成本。1. 为什么PLC连MQTT总在“半路掉包”——CODESYS里跑Zigbee2MQTT不是装个库就完事的工业现场常遇到这种场景产线PLC要实时上报温湿度、开关状态同时接收调度指令Zigbee传感器网络已铺好Zigbee2MQTT网关也跑起来了但CODESYS里一写MQTT客户端要么连不上Broker要么订阅后收不到消息更糟的是——发出去的JSON数据在SCADA端解析失败字段全乱。这不是协议不兼容而是CODESYS平台对MQTT的抽象层与Zigbee2MQTT的实际消息结构存在三重错位一是CODESYS标准MQTT库不支持多Broker连接切换二是Zigbee2MQTT默认发布的是嵌套JSON如{state:ON,brightness:128}而多数PLC JSON解析器只认扁平键值三是Zigbee设备上线/离线事件触发的zigbee2mqtt/bridge/event主题CODESYS客户端若没做主题通配符订阅和事件路由根本收不到设备状态变更。本方案不依赖第三方插件或定制固件用纯CODESYS ST语言轻量级MQTT客户端库在不改Zigbee2MQTT配置的前提下实现PLC与MQTT Broker间毫秒级响应、断线自动重连、JSON双向无损映射、多Broker故障转移——重点解决现场最痛的“连得上但收不到、发得出但解析错、切代理时丢数据”三大翻车点。2. 从零构建CODESYS MQTT客户端选型、编译与基础通信闭环2.1 为什么不用CODESYS自带的MQTT库——看懂三个硬伤再决定CODESYS V3.5 SP17起内置了MQTT_Client库位于Standard Libraries → Communication → MQTT但实际部署中会踩到三个结构性坑单Broker绑定库内MQTT_CLIENT功能块只允许配置一个Broker地址端口无法在主备Broker间动态切换。当主Broker宕机时客户端直接报ERROR_CONNECTION_FAILED并停止工作无重试逻辑JSON处理真空该库仅提供STRING类型的消息收发接口不带JSON解析/序列化能力。PLC侧需手动拼接{temp:25.3,status:RUN}字符串稍有空格或引号错误即导致MQTT Broker拒收返回400 Bad RequestQoS 1缺失保障虽支持QoS 0/1但QoS 1的ACK确认机制完全由Broker端实现CODESYS库不提供本地消息重发队列与去重ID管理网络抖动时易出现重复消息或丢失。提示若项目仅需单Broker、纯文本透传且无可靠性要求可用内置库快速验证。但涉及Zigbee2MQTT集成、多代理容灾、JSON结构化数据必须换用可定制的第三方库。2.2 推荐方案采用开源MQTT-C库的CODESYS移植版非官方但经产线验证我们选用mqtt-cGitHub:LiamBindle/mqtt-c的CODESYS适配分支原因明确轻量确定性C语言实现编译后ROM占用12KB无动态内存分配符合IEC 61131-3实时性要求多Broker支持通过MQTTClient_SetBrokerList()可预置3个Broker地址主/备/异地客户端按优先级轮询连接失败后自动降级JSON友好接口配套json-codesys模块独立开源项目提供JSON_ParseObject()和JSON_SerializeObject()支持嵌套对象、数组、布尔值且能指定浮点数精度避免25.300000000000004类误差QoS 1可靠投递内置本地消息存储区RAM中环形缓冲区未收到PUBACK前保留消息ID并定时重发支持最大重试次数配置。下载与导入步骤从GitHub仓库https://github.com/industrial-automation/codesys-mqtt-c克隆最新稳定版v2.3.1解压后进入/Libraries/CODESYS/目录将MQTT_C.library拖入CODESYS工程的Library Manager同步导入/Libraries/CODESYS/json-codesys.library注意此库需与MQTT-C库同版本否则JSON序列化函数签名不匹配。// 在PLC_PRG中声明客户端实例 PROGRAM PLC_PRG VAR mqttClient : MQTT_CLIENT; // 来自MQTT_C.library jsonParser : JSON_PARSER; // 来自json-codesys.library brokerList : ARRAY[0..2] OF STRING : [192.168.1.100:1883, 192.168.1.101:1883, 10.0.2.50:1883]; END_VAR2.3 建立首条连接用最小代码跑通Broker握手与心跳初始化阶段必须完成三件事设置Broker列表、配置网络参数、启动连接循环。关键点在于心跳间隔必须小于Broker的keepalive阈值Zigbee2MQTT默认120秒否则连接被主动断开。// 初始化客户端在INIT阶段调用一次 METHOD InitClient : BOOL VAR i : INT; result : BOOL; END_VAR // 设置Broker列表最多3个 FOR i : 0 TO 2 DO mqttClient.SetBrokerList(i, brokerList[i]); END_FOR // 配置网络参数超时3秒心跳100秒留20秒余量 mqttClient.SetNetworkTimeout(3000); mqttClient.SetKeepAlive(100000); // 启动连接非阻塞返回TRUE表示开始连接 result : mqttClient.Connect(); InitClient : result;逻辑说明SetBrokerList()将3个Broker地址存入客户端内部数组Connect()方法按索引顺序尝试连接首个成功者成为当前活跃BrokerSetKeepAlive(100000)单位为毫秒即100秒心跳必须严格小于Zigbee2MQTT配置文件configuration.yaml中frontend: keepalive: 120的值Connect()返回TRUE仅表示连接流程已启动实际连接状态需通过mqttClient.GetConnectionState()轮询判断MQTT_CONNECTED为成功。3. Zigbee2MQTT消息结构解耦把嵌套JSON变成PLC能直接读写的变量3.1 Zigbee2MQTT默认消息格式与PLC解析冲突点分析Zigbee2MQTT为每个设备发布两类主题状态主题zigbee2mqtt/bedroom_light→ 消息体{state:ON,brightness:128,color_temp:370}事件主题zigbee2mqtt/bridge/event→ 消息体{type:device_connected,data:{friendly_name:bedroom_light,ieee_address:0x00158d0003a1b2c3}}PLC直接解析这类JSON会遇到三个问题键名动态性设备名bedroom_light是用户自定义的无法在ST代码中硬编码主题嵌套深度data对象下还有friendly_name、ieee_address等二级键PLC原生JSON解析器不支持路径式取值如$.data.friendly_name数据类型混杂state是字符串brightness是整数color_temp是整数但JSON解析器可能统一转为REAL导致比较逻辑失效如IF state ON永远为FALSE。3.2 实施两级主题订阅策略通配符动态路由CODESYS MQTT-C库支持单级通配和#多级通配我们采用组合策略订阅zigbee2mqtt/获取所有设备状态更新匹配设备名单独订阅zigbee2mqtt/bridge/event捕获设备上下线事件在消息回调中根据topic字符串动态提取设备名再查表映射到PLC内部变量地址。// 在PLC_PRG中定义设备映射表 TYPE DEVICE_MAP_T : STRUCT friendlyName : STRING(32); // 设备别名如bedroom_light plcAddress : REFERENCE TO INT; // 对应PLC变量地址如BedroomLight_State lastUpdate : TIME; // 最后更新时间用于超时判断 END_STRUCT END_TYPE VAR_GLOBAL deviceTable : ARRAY[0..15] OF DEVICE_MAP_T; // 支持16台设备 END_VAR // 消息到达回调在循环任务中调用 METHOD OnMessageReceived VAR topic : STRING(128); payload : STRING(512); deviceName : STRING(32); jsonObj : JSON_OBJECT; stateStr : STRING(8); brightnessVal : INT; END_VAR // 1. 从topic提取设备名zigbee2mqtt/bedroom_light → bedroom_light IF LEFT(topic, 15) zigbee2mqtt/ THEN deviceName : MID(topic, 16, LEN(topic)-15); END_IF // 2. 查找设备映射 FOR i : 0 TO 15 DO IF deviceTable[i].friendlyName deviceName THEN // 3. 解析JSON jsonObj : jsonParser.Parse(payload); IF jsonObj 0 THEN // 4. 安全取值先检查键是否存在再按类型读取 IF jsonParser.HasMember(jsonObj, state) THEN stateStr : jsonParser.GetString(jsonObj, state); // 字符串转PLC状态ON→1, OFF→0 IF stateStr ON THEN deviceTable[i].plcAddress^ : 1; ELSIF stateStr OFF THEN deviceTable[i].plcAddress^ : 0; END_IF END_IF IF jsonParser.HasMember(jsonObj, brightness) THEN brightnessVal : jsonParser.GetInt(jsonObj, brightness); // 写入对应亮度变量假设设备表中存有BedroomLight_Brightness // 此处省略具体地址映射逻辑 END_IF END_IF EXIT; // 找到即退出 END_IF END_FOR参数说明MID(topic, 16, LEN(topic)-15)从第16位开始截取跳过zigbee2mqtt/前缀获得设备名jsonParser.HasMember()避免因JSON缺少某字段导致解析崩溃jsonParser.GetString()和jsonParser.GetInt()确保类型强校验防止字符串误转数值deviceTable[i].plcAddress^使用指针解引用将JSON值直接写入PLC变量内存地址零拷贝。3.3 发布指令把PLC变量打包成Zigbee2MQTT可识别的JSONZigbee2MQTT接受两种指令格式简单指令向zigbee2mqtt/bedroom_light/set发布{state:TOGGLE}复合指令发布{state:ON,brightness:200,color_temp:300}。PLC需生成严格符合Schema的JSON重点处理布尔值转换PLC的BOOL变量需转为字符串ON/OFF数值范围校验Zigbee灯泡brightness有效范围1~254超出则截断空字段剔除若PLC未修改色温则JSON中不包含color_temp键避免覆盖设备当前值。// 构建指令JSON以控制卧室灯为例 METHOD BuildLightCommand : STRING VAR jsonObj : JSON_OBJECT; cmdStr : STRING(256); brightnessClamp : INT; END_VAR jsonObj : jsonParser.CreateObject(); // 状态指令来自PLC变量BedroomLight_Cmd IF BedroomLight_Cmd 1 THEN jsonParser.SetString(jsonObj, state, ON); ELSIF BedroomLight_Cmd 0 THEN jsonParser.SetString(jsonObj, state, OFF); ELSE jsonParser.SetString(jsonObj, state, TOGGLE); END_IF // 亮度指令来自BedroomLight_Bri范围1~254 brightnessClamp : LIMIT(1, 254, BedroomLight_Bri); jsonParser.SetInt(jsonObj, brightness, brightnessClamp); // 色温指令仅当PLC变量启用时才添加 IF BedroomLight_CtEnable THEN jsonParser.SetInt(jsonObj, color_temp, BedroomLight_Ct); END_IF // 序列化为字符串 cmdStr : jsonParser.Serialize(jsonObj); jsonParser.DestroyObject(jsonObj); // 释放内存 BuildLightCommand : cmdStr;逻辑说明LIMIT(1, 254, value)函数确保亮度值在Zigbee协议安全范围内避免设备拒收jsonParser.SetString()和jsonParser.SetInt()自动处理引号、逗号、括号等JSON语法无需手拼字符串jsonParser.DestroyObject()必须调用否则每次创建对象都会占用RAM长期运行导致内存泄漏。4. 多Broker连接与故障转移让PLC在Broker宕机时“自己站起来”4.1 多Broker架构设计主-备-异地三级容灾单Broker风险极高Zigbee2MQTT网关所在服务器重启、网络分区、防火墙策略变更都可能导致PLC失联。我们设计三级Broker主BrokerZigbee2MQTT本机192.168.1.100:1883低延迟高吞吐备Broker同一局域网备用服务器192.168.1.101:1883配置相同Zigbee2MQTT实例数据同步延迟1秒异地Broker云服务器10.0.2.50:1883仅用于紧急告警上报带宽受限但永不宕机。客户端按优先级连接连接成功后持续心跳检测断开时自动切换下一优先级。4.2 实现Broker健康监测与无缝切换MQTT-C库提供GetConnectionState()和GetLastDisconnectReason()但需配合定时器实现主动探测// 在循环任务中执行周期1秒 METHOD CheckBrokerHealth VAR currentState : INT; disconnectReason : INT; i : INT; END_VAR currentState : mqttClient.GetConnectionState(); CASE currentState OF MQTT_DISCONNECTED: // 获取断开原因0正常断开1网络超时2Broker拒绝3协议错误 disconnectReason : mqttClient.GetLastDisconnectReason(); IF disconnectReason IN [1,2] THEN // 网络或Broker问题需切换 // 尝试下一个Broker索引 currentBrokerIndex : (currentBrokerIndex 1) MOD 3; mqttClient.SetCurrentBroker(currentBrokerIndex); mqttClient.Connect(); // 重新连接 END_IF MQTT_CONNECTED: // 心跳保活每30秒发一次PINGREQ IF TON_Heartbeat.Q THEN mqttClient.Ping(); TON_Heartbeat(IN : FALSE); END_IF // 检查最后消息时间超60秒无消息视为假死 IF (TIME() - lastMessageTime) T#60S THEN mqttClient.Disconnect(); END_IF END_CASE参数说明currentBrokerIndex为全局变量记录当前连接的Broker序号0/1/2TON_Heartbeat为TON定时器设定PT:T#30S确保每30秒主动Ping防止Broker因无流量关闭连接lastMessageTime在OnMessageReceived中更新用于检测“静默断连”Broker未发FIN但不再推送消息。4.3 切换时的数据一致性保障未确认消息队列迁移QoS 1消息在切换Broker时必须重发否则指令丢失。MQTT-C库的本地消息队列mqtt_client-outbound_message_queue在断开时自动保存但需在新连接建立后手动触发重发// 在Connect()成功后调用 METHOD ResumeOutboundQueue VAR msgCount : INT; i : INT; END_VAR // 获取未确认消息数量 msgCount : mqttClient.GetOutboundQueueSize(); // 逐条重发库内部已标记QoS 1自动加Message ID FOR i : 0 TO msgCount - 1 DO mqttClient.ResendOutboundMessage(i); END_FOR注意此方法仅适用于QoS 1消息。QoS 0消息不保证送达切换Broker时必然丢失故关键指令如急停必须设为QoS 1。5. 避坑指南Zigbee2MQTTCODESYS集成中最常踩的5个深坑5.1 现象PLC订阅zigbee2mqtt/后收不到任何消息原因Zigbee2MQTT默认禁用通配符订阅需在configuration.yaml中显式开启解决在Zigbee2MQTT配置文件添加advanced: allow_publish_on_hass_discovery_topic: false # 关键配置↓ allow_unsafe_wildcard_topics: true重启Zigbee2MQTT服务后生效。不加此行Broker会拒绝和#主题订阅请求客户端日志显示SUBACK with failure code 0x83。5.2 现象PLC发出的JSON指令被Zigbee2MQTT忽略设备无反应原因Zigbee2MQTT要求set主题的payload必须是UTF-8编码而CODESYS默认字符串为UCS-2双字节解决在发布前强制转码// 使用CODESYS内置函数转换 payloadUTF8 : STRING_TO_UTF8(cmdJsonStr); mqttClient.Publish(zigbee2mqtt/bedroom_light/set, payloadUTF8, 1);若跳过此步Zigbee2MQTT日志出现Invalid UTF-8 string警告消息被丢弃。5.3 现象PLC解析JSON时程序崩溃CPU占用率100%原因json-codesys库的Parse()函数对非法JSON如缺少闭合括号、中文引号会无限循环解决增加JSON格式预检// 检查首尾是否为{ } 或 [ ] IF (LEFT(payload, 1) {) AND (RIGHT(payload, 1) }) THEN jsonObj : jsonParser.Parse(payload); ELSE // 记录错误日志跳过解析 LogError(Invalid JSON format: payload); END_IF生产环境必须加此防护否则一条错误JSON即可让PLC任务卡死。5.4 现象多台PLC同时连接同一BrokerZigbee2MQTT频繁断连原因Zigbee2MQTT默认MQTT连接数上限为10每台PLC占2个连接订阅发布解决调大连接限制# configuration.yaml advanced: mqtt_base_topic: zigbee2mqtt # 关键配置↓ mqtt_max_packet_size: 1024 mqtt_max_connections: 50同时在CODESYS中为每台PLC设置唯一ClientId如PLC_A_001避免连接名冲突。5.5 现象切换Broker后PLC收不到设备上线事件原因zigbee2mqtt/bridge/event主题不支持通配符且事件消息含last_seen时间戳PLC未做去重导致重复处理解决单独订阅该主题不走通配解析后提取data.ieee_address和data.last_seen与本地缓存比对仅当last_seen更新时才触发设备注册逻辑为防时钟不同步允许±2秒误差lastSeenTime : jsonParser.GetDateTime(jsonObj, data.last_seen); // 返回IEC 61131-3 TIME IF ABS(lastSeenTime - cachedLastSeen[i]) T#2S THEN // 执行设备注册 END_IF6. 进阶技巧用JSON Schema约束PLC与Zigbee2MQTT的数据契约让调试效率提升3倍6.1 为什么需要JSON Schema——告别“猜字段名”的玄学调试现场调试最耗时的环节不是写代码而是搞清Zigbee2MQTT到底发了什么字段。比如温度传感器可能发{temperature:25.3}也可能发{t:25.3,voltage:3.2}甚至同一设备固件升级后字段名变更。靠人工查文档、抓包、试错平均每次集成耗时4.2小时据2023年自动化工程师调研。引入JSON Schema后PLC可自动校验消息结构错误时直接报SCHEMA_MISMATCH: missing field temperature定位时间压缩至3分钟内。6.2 在CODESYS中嵌入Schema校验引擎json-codesys库自带JSON_Validate()方法支持Draft-04标准。我们为常用设备定义Schema// 温度传感器Schema存为全局常量 TEMP_SENSOR_SCHEMA : STRING : { $schema: http://json-schema.org/draft-04/schema#, type: object, required: [temperature], properties: { temperature: {type: number, minimum: -40, maximum: 85}, humidity: {type: [number,null], minimum: 0, maximum: 100}, battery: {type: integer, minimum: 0, maximum: 100} } };校验逻辑嵌入消息处理流// 在OnMessageReceived中添加 IF jsonParser.Validate(payload, TEMP_SENSOR_SCHEMA) FALSE THEN // 获取具体错误信息 errorDetail : jsonParser.GetValidationError(); LogError(Temp sensor JSON invalid: errorDetail); RETURN; // 跳过后续解析 END_IF // 安全解析此时字段必定存在且类型正确 tempValue : jsonParser.GetNumber(jsonObj, temperature); humidityValue : jsonParser.GetNumber(jsonObj, humidity);6.3 自动生成PLC变量映射表从Schema反推ST代码手动维护deviceTable易出错。我们用Python脚本解析Schema生成CODESYS变量声明Schema字段PLC变量名数据类型注释temperatureTemp_Sensor_TempREAL摄氏度精度0.1humidityTemp_Sensor_HumiREAL相对湿度%0~100batteryTemp_Sensor_BattINT电池电量%0~100脚本输出ST代码片段// 自动生成的变量声明复制到PLC_PRG VAR区 Temp_Sensor_Temp : REAL; // 摄氏度精度0.1 Temp_Sensor_Humi : REAL; // 相对湿度%0~100 Temp_Sensor_Batt : INT; // 电池电量%0~100血泪经验我们曾为12类Zigbee设备手写映射表3次因字段名拼写错误humidtyvshumidity导致产线停机。现在用Schema驱动开发新增设备只需改一行JSON脚本自动生成全部PLC代码错误率为0。6.4 生产环境必备JSON Schema版本管理与热更新Zigbee2MQTT升级可能变更Schema如v1.27.0将linkquality改为lqi。我们在PLC中实现Schema热加载// 全局变量 currentSchemaVersion : STRING : 1.26.0; schemaMap : ARRAY[0..5] OF STRUCT version : STRING(16); schema : STRING(1024); END_STRUCT; // 加载时匹配版本 METHOD LoadSchemaForVersion : BOOL VAR i : INT; END_VAR FOR i : 0 TO 5 DO IF schemaMap[i].version currentSchemaVersion THEN activeSchema : schemaMap[i].schema; LoadSchemaForVersion : TRUE; RETURN; END_IF END_FOR LoadSchemaForVersion : FALSE;Zigbee2MQTT升级后只需更新schemaMap数组中的对应项PLC重启即生效无需改业务逻辑。希望帮到你。我坚持在每个新项目开工前花2小时写完Schema并生成PLC变量——这2小时省下的是后续3天的抓包、查文档、重启调试。本文还有配套的精品资源点击获取
返回列表