当前位置:首页 > 工业控制 > 工业控制
[导读]工业现场Modbus、OPC UA、CAN、S7、MQTT并存,网关别为每个品牌写死协议。落地用“南向插件接设备—统一点表归一化—北向按MQTT/OPC UA/HTTP输出”三层,C++负责插件框架、调度、缓存和品质管理,换现场只改JSON点表。


工业现场Modbus、OPC UA、CAN、S7、MQTT并存,网关别为每个品牌写死协议。落地用“南向插件接设备—统一点表归一化—北向按MQTT/OPC UA/HTTP输出”三层,C++负责插件框架、调度、缓存和品质管理,换现场只改JSON点表。

一、统一数据模型

所有协议进来先转成与点位无关的六元组:设备ID、测点路径、类型、值、时间戳、品质。Modbus寄存器、OPC节点、CAN帧都映射到它,后续北向不用关心来源。

struct Point {

   std::string dev; std::string tag;     // 如 "plc1.temp"

   double value; int quality=0;          // 0坏 1好 2强制

   int64_t ts_ms=0; std::mutex m;

   void set(double v,int q,int64_t ts){std::lock_guard<std::mutex>lk(m);

       value=v;quality=q;ts_ms=ts;}

};

using PointPtr=std::shared_ptr<Point>;

class PointDB{ std::map<std::string,PointPtr>m_;std::mutex mu_;

public:

   PointPtr get(const std::string&k){std::lock_guard<std::mutex>lk(mu_);

       return m_[k]?:m_[k]=std::make_shared<Point>();}

};

内存点表配环形缓冲,重要数据落SQLite,断网恢复可补传。

二、协议插件抽象

用工厂+动态库思路,新增S7、FINS、IEC104只写新插件,核心不重新编译。

class ProtocolAdapter{

public: virtual ~ProtocolAdapter()=default;

   virtual bool open(const Json&cfg)=0;

   virtual void poll(PointDB&db)=0;        // 周期采集

   virtual bool write(const std::string&tag,double v)=0;

   virtual const char*name()const=0;

};

class AdapterFactory{

   std::map<std::string,std::function<std::unique_ptr<ProtocolAdapter>()>>r_;

public:

   void reg(const std::string&t,auto f){r_[t]=f;}

   std::unique_ptr<ProtocolAdapter>create(const std::string&t){

       auto it=r_.find(t);return it==r_.end()?nullptr:it->second();}

};

Modbus RTU/TCP插件内部用libmodbus,按点表读保持寄存器、做字节序和scale:

class ModbusAdapter:public ProtocolAdapter{

   modbus_t*ctx=nullptr;std::vector<Json>pts_;

public:

   bool open(const Json&c)override{

       ctx=c["type"]=="rtu"?modbus_new_rtu(c["port"].c_str(),

           c["baud"].get<int>(),'N',8,1)

         :modbus_new_tcp(c["ip"].c_str(),c["port"].get<int>());

       modbus_set_slave(ctx,c["slave"].get<int>());

       return modbus_connect(ctx)==0;

   }

   void poll(PointDB&db)override{

       for(auto&p:pts_){uint16_t reg[2]={0};

           int base=p["addr"].get<int>()-40001;

           if(modbus_read_registers(ctx,base,2,reg)==2){

               int16_t v=(reg[0]<<16)|reg[1];   // 示例32位合并

               double val=v*p["scale"].get<double>();

               db.get(p["tag"])->set(val,1,now_ms());

           }else db.get(p["tag"])->set(0,0,now_ms());

       }

   }

   bool write(const std::string&tag,double v)override{/*写保持寄存器*/return true;}

   const char*name()const override{return "modbus";}

};

OPC UA侧用open62541起Server,把PointDB挂成变量;MQTT用Paho发JSON,QoS1+遗嘱。CAN用SocketCAN收can_frame,按ID映射测点。

三、调度与高可靠

每个南向适配器一个线程,统一周期调度;北向MQTT/OPC独立线程订阅下发。重连、超时、品质戳全在框架做:

void adapter_loop(std::unique_ptr<ProtocolAdapter>a,PointDB&db,int ms){

   while(run){

       if(!a->poll_safe(db)) reconnect(a);   // 超时指数退避

       std::this_thread::sleep_for(std::chrono::milliseconds(ms));

   }

}

void mqtt_up(PointDB&db,const std::string&broker){

   mqtt::async_client c(broker,"gw01");

   c.set_connection_lost_handler([&]{c.reconnect();});   // 自动重连

   c.connect()->wait(); c.subscribe("gw/down/#",1);

   while(run){ auto p=db.get("plc1.temp");

       c.publish("gw/up/temp",to_json(p),"1");

       std::this_thread::sleep_for(std::chrono::seconds(1)); }

}

断网时北向数据进本地队列,恢复后按时间戳补传;写操作先校验PLC联锁范围,网关只发“建议值”,不绕过安全逻辑。

四、工程要点

点表JSON化:设备类型、从站、寄存器、类型int16/float32、scale、byteorder、周期、MQTT主题,换厂不改C++;

线程模型:南向采集、归一化、北向上报分离,热点用无锁SPSC,共享点用mutex/原子;

资源控制:ARM A53/A72限核绑线程,libmodbus超时500~1000ms,MQTT心跳30s,避免多设备同串口并发抢总线;

安全:OPC UA开证书,MQTT用TLS+账号,配置文件权限600,北向只暴露物模型不暴露原始寄存器;

可观测:每个适配器出连接状态、轮询成功率、平均时延、补传条数,ILA不需要但系统日志要能定位哪台PLC掉线。

按“C++插件框架+六元组点表+Modbus/OPC/CAN/MQTT分流+断网补传”落地,南向扩新PLC只加适配器so,北向接SCADA/云/边缘AI都读同一套PointDB,既保留Linux生态又避免协议碎片化导致主程序膨胀。



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