工业数据采集网关架构设计一套 C17 插件化工业数据采集网关的设计思路与关键实现版本 v1.0一、为什么用插件化架构工业现场的设备协议极多Modbus TCP、OPC UA、串口私有协议、UDP 报文、HTTP/JSON……如果每接一种协议就改一次内核项目会迅速失控。本网关的做法是内核只负责点位数据这件事协议全部下沉为插件。内核定义了一套稳定的接口IPlugin/IDriver/IChannel/IDevice任何新协议只要实现这套接口、编译成一个.so放进plugins/目录即可被加载不需要重新编译内核。带来的直接好处好处说明隔离某个驱动崩溃不影响内核与其他驱动可裁剪现场不需要的驱动直接不部署减少攻击面与体积可扩展私有协议对接只写一个插件不动主干代码可替换同一份点位表可以在不同协议驱动间迁移二、整体架构系统按职责分为四层┌─────────────────────────────────────────────────────────────┐ │ 表现层 Web 组态 / 上位机浏览器访问 │ │ 实时画面 · 设备配置 · 点位配置 · 历史查询 · 权限 │ └───────────────────────────┬─────────────────────────────────┘ │ REST / WebSocket ┌───────────────────────────┴─────────────────────────────────┐ │ 服务层 shuwebWeb 服务· shuservice工程管理服务 │ └───────────────────────────┬─────────────────────────────────┘ │ RPC自定义二进制协议 ┌───────────────────────────┴─────────────────────────────────┐ │ 内核层 shucore │ │ · 点位管理DataManager · 设备管理DeviceManager │ │ · 工程加载ProjectManager· 事件Event· 搜索DataSearch│ │ · 冗余Redundency· 授权License· 日志Logger │ └───────────────────────────┬─────────────────────────────────┘ │ 统一接口 IChannel / IDevice ┌───────────────────────────┴─────────────────────────────────┐ │ 驱动层 采集插件全部以 .so 形式动态加载 │ │ Modbus TCP · OPC UA · 串口 · UDP · JSON · … │ └─────────────────────────────────────────────────────────────┘另有守护进程shudaemon负责内核异常退出后的拉起shuupdate负责版本升级shuhwinfo负责读取硬件指纹授权用。三、插件模型3.1 三种容器插件管理器把插件按ContainerType分为三类加载顺序与生命周期不同容器枚举值典型插件说明内核容器CONTAINER_COREDataManager、DeviceManager、Logger系统必需最先加载扩展容器CONTAINER_EXTENDEDEvent、DataSearch、Redundency增强功能可裁剪驱动容器CONTAINER_DRIVERModbus、OPC UA、串口按现场需要加载3.2 插件生命周期每个插件都实现统一的生命周期钩子classIPlugin{public:virtualconstchar*pluginName()0;// 唯一名称如 c.modbus.tcpvirtualintinstall(intargc,char**argv)0;// 安装生成属性定义文件virtualintuninstall()0;// 卸载virtualintinit(intargc,char**argv)0;// 初始化读取配置、注册能力virtualintuninit()0;// 反初始化释放资源virtualintupdate()0;// 周期回调virtualintgetPluginId()const0;// 内核内固定 IDvirtualintgetContainerType()const0;};关键设计init只做注册、不做阻塞工作。真正的采集线程由设备在start()时拉起避免某个慢设备拖垮整个启动流程。3.3 插件的获取方式插件间不直接互相持有指针而是通过内核的单例注册表按类型取用ILogger*loggerIPluginManager::getSingleInstanceILogger,1();IDataManager*dataIPluginManager::getSingleInstanceIDataManager,8();IDeviceManager*devicesIPluginManager::getSingleInstanceIDeviceManager,12();模板参数中的数字是内核约定的插件 ID。这样驱动插件只依赖Interfaces.h不依赖任何具体实现编译期完全解耦。四、核心接口设计4.1 驱动三件套IDriver / IChannel / IDevice这是最关键的抽象三级对应协议 → 通道 → 设备// 一个协议 一个 IDriver 插件classIDriver:publicIPlugin{public:virtualstd::shared_ptrIChannelcreateChannel(intchannelId)0;virtualstd::stringgetChannelUniqueId(intchannelId)0;virtualstd::vectorstd::stringgetAllProperties()0;// 从在线设备反向导出点位表用于扫点virtualintexportPointsFromDevice(conststd::mapstd::string,Variantparams,std::vectorPointProperty*properties,std::liststd::vectorVariant*values,std::string*error)0;};// 一个通道 一类连接如同一网段的 Modbus TCP 站classIChannel{public:virtualstd::stringgetType()const0;// c.modbus.tcpvirtualintcreateDevice(intdeviceId,IDevice*out)0;virtualvoidupdate100ms(unsignedlongtick)0;// 100ms 心跳virtualintrequestDataByRedun(unsignedlongseq,conststd::vectorunsignedchardata)0;virtualintrespondDataByRedun(unsignedlongseq,conststd::vectorunsignedchardata)0;};// 一个设备 一个通信端点IP:Port / COM 口 / Endpoint URLclassIDevice{public:virtualintloadPoints(constint*pointIds,unsignedlongcount)0;virtualintstart()0;// 拉起独立采集线程virtualintstop()0;virtualvoidsetState(DeviceState state)0;virtualvoidonValueChanged()0;// 点位值变化回调virtualvoidonCommandChanged()0;// 下发命令回调};为什么要有 Channel 这一层因为真实现场里一个 TCP 连接往往承载同一台设备或同一网关下的多个站。把连接复用逻辑放在 Channel把点位轮询逻辑放在 Device既避免了重复建连又让每个设备可以独立启停、独立重连。4.2 点位数据总线IDataManager所有采集到的数据都汇聚到IDataManager它是全系统的数据中枢classIDataManager:publicIPlugin{public:// 按名称批量取点位 ID避免运行期字符串比较virtualintpointNameToId(constchar*const*names,unsignedlongcount,int*ids)0;// 写入点位值reason 用于区分来源采集 / 命令 / 冗余同步virtualintsetPointValue(intpointId,constVqtvalue,ChangeReason reason)0;// 批量读取cache 参数决定走缓存还是强制刷新virtualintgetPointsValue(constint*ids,constbool*cache,unsignedlongcount,Vqt*values,Result*results)0;// 订阅点位变化时回调这是 Web 实时刷新的基础virtualvoidaddPointWatcher(intpointId,boolall,PointWatcherSlotslot)0;virtualvoidaddAllPointWatcher(PointWatcherSlotslot)0;};设计要点点位一律用intID 寻址名称到 ID 的转换只在配置加载期做一次。运行期热路径上没有任何字符串操作。Vqt值 质量 时间戳而不是裸值。质量码让上层能区分“值真的是 0” 和 “通信断了所以是 0”。change reason让数据源头可追溯避免冗余同步回环。五、数据流一次完整的采集到展示设备轮询线程 内核 Web 服务 ───────────── ──────────── ───────────── 读寄存器/订阅回调 │ ▼ Devices-submitCollectValues(deviceId, pointIds, values, reason) │ ▼ DataManager::setPointValue ──► 值变化判定 │ │ │ ├─► 写历史库 │ ├─► 触发 Event报警/事件 │ └─► 通知 Watcher │ │ │ ▼ │ WebSocket 推送到浏览器 │ └─► 冗余同步若配置为主机上行控制方向对称Web 下发命令 →IDeviceManager::setCommand→ 设备的onCommandChanged()→ 驱动把写请求发到设备。六、关键技术实现6.1 点位聚合把 N 个请求合并成 1 个Modbus 逐点读取性能极差。核心做法是块合并把同一功能码、地址相近的点位合并成连续块一次请求读回。// 排序后贪心合并相邻块之间的小空洞直接读掉换取请求数下降if(block.funcspec.funcspec.startblockEnd8std::max(blockEnd,specEnd)-block.startlimit){block.countstd::max(blockEnd,specEnd)-block.start;// 扩展块}容许 8 个地址空洞这个阈值是关键权衡多读几个无关寄存器换掉一次 TCP 往返在工业现场几乎总是划算的。6.2 字节序处理Modbus 只有 16 位寄存器32/64 位值如何拼接各家厂商不统一。工程上用ABCD / CDAB / BADC / DCBA四种布局描述点位地址里可指定FUNC:ADDR[.BIT]|TYPE[|ORDER] 例3:100|REAL32|CDAB 保持寄存器 3x 区地址 10032 位浮点CDAB 布局 1:20.3|BOOL 线圈区地址 20 的第 3 位四种布局互为逆变换所以同一段代码既能编码也能解码。6.3 断线重连状态机每个设备一个独立线程状态机非常克制┌──────────┐ 连接失败 ┌─────────┐ │ STARTING │────────────►│ FAULT │ └────┬─────┘ └────┬────┘ │ 连接成功 │ 等待 reconnectMs ▼ │ 并上报坏质量 ┌──────────┐◄─────────────────┘ │ RUNNING │ 读失败 → 关闭连接 → FAULT └──────────┘两个细节值得注意失败必须上报坏质量否则上位机看到的是最后一帧的旧值会误判现场状态。等待重连时按 100ms 切片检查退出标志保证stop()能及时返回不会卡在sleep(reconnectMs)上。6.4 OPC UA 订阅分组OPC UA 用订阅Subscription而不是轮询。但一个会话能创建的MonitoredItem 数量有上限因此需要把点位分成多个SubscriptionGroup每组建一个 Subscription再批量创建 MonitoredItemUA_CreateMonitoredItemsRequest request;UA_CreateMonitoredItemsRequest_init(request);request.subscriptionIdgroup-subscriptionId;// 批量创建一次服务调用挂载整组点位UA_Client_MonitoredItems_createDataChanges(client,request,contexts.data(),callbacks.data(),deleteCallbacks.data());数据变化时由回调s_valueChanged触发直接把值提交给IDeviceManager不需要任何轮询。这是 OPC UA 相比 Modbus 在点位规模大时性能优势的来源。6.5 双机冗余冗余主机之间通过 RPC 同步状态主机周期把点位变化下发给备机sendDataToRedun备机保持热备只接收不同步输出主机故障时备机切换为MASTER接管对外通信同步数据带sequence序号用于去重和丢包检测。七、工程结构shu-gate/ ├── include/ │ ├── shu/ # 内核公共头文件 │ │ ├── Interfaces.h # ★ 所有插件接口定义 │ │ ├── Variant.h # 通用值类型 │ │ ├── DataTypes.h # Vqt / 质量码 / 枚举 │ │ └── PluginManager.h │ └── plugins/ # 各驱动对外头文件 ├── src/ │ ├── shu/ # 基础库配置、字符串、动态库加载 │ ├── shucore/ # 内核主程序 │ ├── shuweb/ (Go) # Web 后端 │ ├── libDataManager/ # 点位数据中枢 │ ├── libDeviceManager/ # 设备管理 │ ├── libProjectManager/ # 工程管理 │ ├── libc.modbus.tcp/ # Modbus TCP 驱动 │ ├── libc.opc.ua/ # OPC UA 驱动 │ ├── libc.serial/ # 串口驱动 │ ├── libc.udp/ libf.json/ # UDP / JSON 驱动 │ └── libRedundency/ libevent/ … ├── etc/ # 配置、工程、授权 ├── plugins/ # 编译产物.so └── web/ # Web 前端资源八、构建与运行mkdir-pbuildcdbuild cmake..make-j$(nproc)依赖CMake ≥ 3.16、支持 C17 的编译器、Boost、SQLite3、OpenSSL、Protobuf、open62541OPC UA 驱动。Web 后端需要 Go 1.20。产物内核shucore、服务shuservice/shuweb、守护shudaemon以及plugins/下各驱动.so。启动./bin/shucore-petc/projects/default九、可扩展点小结想做的事需要改的地方接一种新协议新写一个IDriver插件不动内核增加点位属性在驱动getAllProperties()中声明配置界面自动出现自定义报警逻辑订阅IDataManager的 PointWatcher或扩展 Event 插件数据转第三方平台扩展一个北向插件订阅全量点位后转发更换存储IProjectConfig已支持 File / SQLite 双实现可再加一种附录术语表术语含义点位Point一个可采集/可写入的变量有值、质量、时间戳VqtValue Quality Timestamp点位值的统一载体通道Channel一类通信连接的集合如一个网段设备Device一个通信端点承载若干点位块Block一次 Modbus 请求读取的连续寄存器区间质量码标识当前值是否可信Good / Bad冗余双机热备主机故障时备机接管
企业数字化 ERP 产品动态
相关推荐
电工基础自学笔记 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 6:46:32
CRA Draft Guidance(指导文件草案)解读:谁才是真正的制造商? 为什么需要关注 CRA Draft Guidance
2026 年 3 月,欧盟委员会发布了《Cyber Resilience Act(CRA)Draft Guidance》征求意见稿,旨在帮助企业更好地理解和落实 CRA 的具体要求。CRA 法规本身规定了企业需要履行的义务,而… · 2026/11/5 6:46:32
ESP32-S3 批量清空与批量下载操作说明 ESP32-S3 批量清空与批量下载操作说明
1. 适用范围
本文档适用于在 Windows PowerShell ESP-IDF 环境下,对多台 ESP32-S3 设备进行:
批量清空 Flash:使用自定义 batch_erase.py 脚本,多线程并行调用 esptool erase_flash。批量下… · 2026/11/5 6:45:32
ADS122U04+PIC18F46K42:高精度PT1000测温模块设计与实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:36:44
基于ADS122U04IPWR与STM32F765ZI的工业及HVAC高精度温度测量方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:36:44
ADS122U04+PIC32MZ:HVAC高精度温度采集系统设计与实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:36:44
ADS122U04IPWR+STM32F031C6工业温度测量方案解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:36:44
基于ADS122U04与PIC32MZ的高精度温度采集系统设计 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:36:44
ADS122U04与STM32F410RB工业温度采集方案:UART接口ADC实战详解 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:35:44
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38