嵌入式C++Linux工业网关开发:实现多协议工业设备的统一接入
工业现场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生态又避免协议碎片化导致主程序膨胀。





