当前位置:首页 > 工业控制 > 工业控制
[导读]工业网关做多协议转换,最忌“收到OPC UA读请求→现场去问Modbus从站”。RS485半双工经不起以太网侧并发戳,几秒就CRC满天飞。 正确架构是三层解耦:南向采集线程写共享缓存 → 统一数据点模型 → 北向Modbus TCP/OPC UA/MQTT只读缓存。协议差异在采集层和输出层各自消化,中间只传“值+质量码+时间戳”。


工业网关做多协议转换,最忌“收到OPC UA读请求→现场去问Modbus从站”。RS485半双工经不起以太网侧并发戳,几秒就CRC满天飞。 正确架构是三层解耦:南向采集线程写共享缓存 → 统一数据点模型 → 北向Modbus TCP/OPC UA/MQTT只读缓存。协议差异在采集层和输出层各自消化,中间只传“值+质量码+时间戳”。

一、统一数据点是核心

别让OPC UA节点直接绑Modbus寄存器地址,也别让MQTT拼字符串时现读串口。先建内部点表:

typedef struct {

   char name[32];        // "motor_temp"

   uint8_t dev_idx;

   uint8_t func;         // 03/04

   uint16_t addr;        // 40001类

   float scale;

   int16_t raw;

   float value;

   uint8_t quality;      // 0 BAD 1 GOOD

   uint64_t ts_ms;

} tag_t;


tag_t tags[256];

pthread_mutex_t tag_mtx;

Modbus原始值是整数,工程量靠scale换算;OPC UA要的是带类型的Variant,MQTT要的是JSON,都从tag_t派生。

点位表建议从JSON/YAML加载,别写死在代码里:设备多、地址改一动全身。

二、南向Modbus采集线程

libmodbus同时支持RTU/TCP,切换只是modbus_new_rtu/modbus_new_tcp。

void* modbus_poll_task(void*arg){

   modbus_t*ctx = modbus_new_tcp("192.168.1.10",502);

   modbus_set_slave(ctx,1);

   modbus_connect(ctx);

   uint16_t reg[32];

   while(run){

       for(int i=0;i<n_tags;i++){

           tag_t*t=&tags[i];

           if(modbus_read_registers(ctx,t->addr,1,reg)==1){

               pthread_mutex_lock(&tag_mtx);

               t->raw=reg[0];

               t->value=reg[0]*t->scale;

               t->quality=1; t->ts_ms=now_ms();

               pthread_mutex_unlock(&tag_mtx);

           }else{

               t->quality=0;

           }

           usleep(t->poll_ms*1000);

       }

   }

   modbus_close(ctx); modbus_free(ctx);

   return NULL;

}

RTU后端注意:

485方向脚用RTS控制或硬件自动翻转

超时modbus_set_response_timeout按波特率留余量

单总线单主站,别让多个线程共用ctx

读失败标quality=BAD,别拿上次要塞

多设备轮询周期错开,别32台同时100ms,串口带宽会爆。

三、北向Modbus TCP服务(透传上位机)

如果上位机还要走Modbus TCP读网关,另起一个ctx监听502,回调函数从tags[]取数,不碰串口:

void mb_tcp_reply(){

   modbus_mapping_t*map = modbus_mapping_new(0,0,256,0);

   modbus_t*srv = modbus_new_tcp(NULL,502);

   modbus_tcp_listen(srv,1);

   for(;;){

       int cli=modbus_tcp_accept(srv,NULL);

       uint8_t q[256]; int n=modbus_receive(srv,q);

       if(n>0){

           pthread_mutex_lock(&tag_mtx);

           for(int i=0;i<256;i++) map->tab_registers[i]=tags[i].raw;

           pthread_mutex_unlock(&tag_mtx);

           modbus_reply(srv,q,n,map);

       }

   }

}

TCP侧Unit ID映射到RTU从站地址,网关做桥接时别写死0/255。

四、OPC UA服务器(open62541)

OPC UA是给SCADA/上位做对象模型浏览的,节点值从tag缓存刷:

#include <open62541/server.h>

#include <open62541/server_config_default.h>


void add_tag_node(UA_Server*s, tag_t*t){

   UA_VariableAttributes a=UA_VariableAttributes_default;

   UA_Float v=0; a.value=UA_Variant_from_scalar(&v,&UA_TYPES[UA_TYPES_FLOAT]);

   a.displayName=UA_LOCALIZEDTEXT("en-US",t->name);

   UA_Server_addVariableNode(s, UA_NODEID_STRING(1,t->name),

       UA_NODEID_NUMERIC(0,UA_NS0ID_OBJECTSFOLDER),

       UA_NODEID_NUMERIC(0,UA_NS0ID_ORGANIZES),

       UA_QUALIFIEDNAME(1,t->name),

       UA_NODEID_NUMERIC(0,UA_NS0ID_BASEDATAVARIABLETYPE),

       a,NULL,NULL);

}


void refresh_ua(UA_Server*s){

   for(int i=0;i<n_tags;i++){

       pthread_mutex_lock(&tag_mtx);

       UA_Float v=tags[i].value; pthread_mutex_unlock(&tag_mtx);

       UA_Variant var; UA_Variant_setScalar(&var,&v,&UA_TYPES[UA_TYPES_FLOAT]);

       UA_Server_writeValue(s,UA_NODEID_STRING(1,tags[i].name),var);

   }

}

主循环UA_Server_run跑独立线程,定时refresh即可,别在OPC UA回调里调modbus_read。

五、MQTT上云(paho / mosquitto)

MQTT只做北向推送,QoS1防丢,断网本地缓冲:

void mqtt_publish_tag(tag_t*t){

   cJSON*root=cJSON_CreateObject();

   cJSON_AddStringToObject(root,"name",t->name);

   cJSON_AddNumberValue(root,"value",t->value);

   cJSON_AddNumberValue(root,"q",t->quality);

   cJSON_AddNumberValue(root,"ts",t->ts_ms);

   char*txt=cJSON_PrintUnbuffered(root);

   mosquitto_publish(mos,tid,"plant/cnc01/temp",strlen(txt),txt,1,0);

   cJSON_free(txt);

}

要点:

QoS选1,云端ACK前重发

retain只给设备状态topic用,数据topic别retain

断网把payload存环形队列,恢复后补发

TLS+用户名密码,别匿名暴露502/1883

topic层级site/device/point,别堆查询参数

六、线程与数据一致性

架构主干:

[RTU/TCP采集线程] --> tags[] (mutex)

                           |

       +-------------------+-------------------+

       v                   v                   v

  Modbus TCP服务       OPC UA刷新          MQTT发布线程

tags[]是唯一真相源,所有北向只读

写方向只有采集线程(或下行命令线程)

OPC UA/MQTT不持有modbus ctx

南下写命令(置位继电器)走单独写队列,避免和轮询抢帧

七、落坑清单

OPC UA回调里调modbus_read→RS485并发冲突

点位表写死数组→改地址重编译

Float不做4字节对齐→读Real乱



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭