Skip to Content
技术文档10 · 开发者扩展业务插件二开说明
本页目录

业务插件二开说明

本文把继承 DataForwardBase 的数据转发目标、协议服务端、数据库写入、云平台对接、同步桥接统一称为“业务插件”。业务插件不负责从现场设备采集原始数据,而是消费采集变量、设备状态、报警或插件事件,把它们写入外部系统,或对外提供服务端协议能力。

如果要扩展 PLC、仪表、OPC、MQTT 采集等设备驱动,请阅读 采集插件二开说明。

阅读路线

实现模板 → 参数与缓存契约 → 排障与验收

首次开发先验证一个最小例,再按参考补全参数、失败处理和释放流程。MCP 材料入口位于页末,可按需展开。

开发准备与基类选择

源码入口

入口作用
TGateway.Application/Driver/DataForward/DataForwardBase.cs所有业务插件的运行时基类。
TGateway.Application/Driver/DataForward/DataForwardPropertyBase.cs目标级插件属性根类。
TGateway.Application/Driver/DataForward/DataForwardVariablePropertyBase.cs目标变量级属性根类。
TGateway.Application/Driver/DataForward/DataForwardChannelPropertyBase.cs需要 TCP、串口、DTU、SSL 的目标属性基类。
TGateway.Application/Driver/DataForward/Cache/*缓存队列、离线文件缓存、Topic 模板、脚本模型、批量上传基类。
TGateway.Application/Task/DataForward/DataForwardMange/DataForwardMange.cs转发组、目标启动、事件分发、组级数据生产、冗余切换的调度入口。
TGateway.Application/Model/DataForwardGroupRuntime.cs转发组范围解析后的运行态变量和设备索引。
TGateway.Plugin/Plugin/*MQTT、Kafka、RabbitMQ、Webhook、ModbusSlave、SyncBridge 等实现。
TGateway.NOAOTPlugin/Plugin/OpcUa/OpcUaServerOPC UA Server 业务插件实现。
TGateway.PRO/src/TGateway.NOAOTPROPlugin/Plugin/*IEC104、IEC61850 Server、ZeroMQ 等专业版实现。

业务插件和采集插件的边界

项目采集插件业务插件
基类CollectBaseDataForwardBase
配置位置采集设备数据转发目标
数据来源PLC、仪表、外部上报、虚拟计算运行时变量、设备状态、报警、插件事件
主要任务读写现场点位上传、存储、服务端协议、桥接、反写
变量范围设备下启用变量转发组解析后的变量范围
写入入口InvokeWriteAsync、InvokeMethodAsync目标协议反写后通常再调用采集侧 RPC

不要把业务插件写成“再去全局扫描所有变量”。变量范围由转发组决定,业务插件应通过 GetVariables()、IdVariableRuntimes、CollectDevices 或组生产入口读取当前目标可见的数据。

插件发现规则

规则说明
继承 DataForwardBasePluginService 只把 DataForwardBase 的非抽象派生类识别为数据转发插件。
公开无参构造运行时通过 Activator.CreateInstance 创建,每个目标一个独立实例。
插件全名是配置键数据转发目标保存类型 FullName,重命名命名空间或类名会影响旧配置。
目标属性先注入再初始化DataForwardMange.StartTargetAsync 先调用 SetDriverProperties,再 InitTarget、InitAsync、StartAsync。
目标变量属性不决定变量范围目标变量属性只保存外部映射、权限、数据类型等插件专属配置。变量是否进入目标由转发组范围和组变量关系决定。
UsesGroupDataProducer 决定数据入口缓存型目标通常为 true,消费转发组统一生产的数据;服务端协议类通常为 false,直接处理变量变化。

基类选择

基类适合场景必须重点实现现有参考
DataForwardBase协议服务端、同步桥、需要自管内存映射或连接循环的目标。TargetProperties、TargetPropertyType、可选 VariablePropertyType、ProtectedInitAsync、AfterVariablesChangedAsync、OnVariableChanged、ProtectedExecuteAsync、IsConnected。ModbusSlave、OpcUaServer、IEC104Slave、IEC61850Server、SyncBridge。
DataForwardBaseWithCache需要离线缓存,但触发模型不完全等同周期变量上传的目标。DataForwardPropertyWithCache、启用的模型、Update*Model 或 AcceptProduced*。HisAlarmForwardTarget。
DataForwardBaseWithCacheInterval消费转发组统一生产的数据,支持周期/变化/批处理和离线缓存。DataForwardPropertyWithCacheInterval、模型开关、AcceptProduced* 或 Update*Model。HisDataForwardTarget、RealDataForwardTarget、ThingsBoardClientProducer。
DataForwardBaseWithCacheIntervalScript需要 Topic 模板、实体脚本、自定义上传模板,但只想复用转换能力。继承后按模型调用 GetVariableBasicDataTopicArray 等方法。脚本上传类目标的父类。
DataForwardBaseWithCacheIntervalScriptAllMQTT、Kafka、RabbitMQ、Webhook、ZeroMQ 这类“生成 Topic/Payload 后上传”的目标。实现 Upload(TopicArray, CancellationToken),初始化客户端连接,重写 IsConnected。MqttClientProducer、KafkaProducer、RabbitMQProducer、Webhook、ZeroMQProducer。
DataForwardChannelPropertyBase目标需要 TCP 客户端/服务端、串口、DTU、SSL 参数。属性类继承它,并在 ProtectedInitAsync 调用 InitChannelAsync。ModbusSlaveProperty、IEC104SlaveProperty。

如果目标只是“把变量转成 JSON 发到某个系统”,优先使用 DataForwardBaseWithCacheIntervalScriptAll。如果目标要对外模拟一个协议服务端,让外部系统主动读写内存点表,优先使用 DataForwardBase。

定义配置属性

目标属性和变量属性

类型作用
DataForwardPropertyBase目标级属性根类。连接地址、认证、表名、缓存、模板都属于目标属性。
DataForwardVariablePropertyBase目标变量级属性根类。单个变量在某个目标下的外部地址、数据类型、写入权限等属于这里。
DataForwardVariableProperty通用预留字段 数据1 到 数据10,适合简单外部映射。
DataForwardPropertyWithCache离线缓存、内存队列上限、上传分片、过滤离线数据、上传并发。
DataForwardPropertyWithCacheInterval继承缓存属性,触发模式和周期由转发组统一决定。
DataForwardPropertyWithCacheIntervalScript增加详细日志、JSON 格式、列表/字典上传、Topic 模板、实体脚本、上传模板。
DataForwardChannelPropertyBase增加通道类型、远程地址、本地绑定、SSL、串口、心跳、DTU、并发、连接超时等。

实现目标属性时要同时保证目标属性实例和类型一致:

private readonly MyTargetProperty _properties = new(); public override DataForwardPropertyBase TargetProperties => _properties; public override Type TargetPropertyType { get; } = typeof(MyTargetProperty);

插件有变量级配置时,在插件类上声明 [VariablePropertyModel(typeof(MyVariableProperty))],将类声明为 partial,并通过 VariablePropertys 返回该类型的属性实例。VariablePropertyType 由生成器生成,不再手写同名覆盖成员。检查生成目录包含变量属性字段,再用实例元数据接口验证页面表单。

DataForwardVariablePropertyBase.Enable 不是动态属性,页面不会出现第二个启用开关。是否转发某个变量由转发组变量范围、组内变量启用状态、目标启用状态和触发条件共同决定。

DynamicProperty 约定

业务插件和采集插件使用同一套动态属性规则。

项目说明
只有 [DynamicProperty] 会显示和保存运行时连接对象、客户端对象、缓存字段、临时索引不要标记。
默认值会进入新建表单默认端口、Topic、表名、缓存关闭/开启策略应保守。
CertificatePurpose 用于证书下拉使用 CertificatePurposeFilter.Client、Server 或 CA;可用 CertificatePurposeFilter.Server | CertificatePurposeFilter.Client 合并候选用途。证书实体仍为单一用途,运行时继续按实际角色校验。
Remark 要写清单位和格式例如毫秒、秒、Topic 模板、SQL 表名、连接字符串格式。
复杂对象也可以作为属性例如上传模板配置、SOE 配置,但要保证 JSON 序列化和导入导出能恢复。

运行时与缓存契约

生命周期钩子

阶段运行时动作插件开发注意事项
构造函数创建目标插件实例。只初始化轻量字段,不连接外部系统。
属性注入SetDriverProperties(forwarder.TargetProperties, forwarder.TargetPropertyType, target.TargetPropertys)。ProtectedInitAsync 可以读取完整目标属性。
InitTarget挂载 CurrentGroup、CurrentTarget、日志、target.Forwarder,调用 ProtectedInitTarget。可以缓存组/目标基础信息,不要启动连接。
InitAsync依次调用 InitializeLocalResourcesAsync、ProtectedInitAsync、AfterVariablesChangedAsync。先建立缓存等本地资源,再初始化远端配置与变量映射,避免远端失败导致本地缓存未准备。
StartAsync调用 ProtectedStartAsync,受 StartTimeout 控制,成功后设置 IsStarted。连接外部系统或启动服务端监听,失败要抛异常或返回失败状态。
GetTasks建立目标调度循环。默认按组周期调用 ProtectedExecuteAsync。大多数缓存型目标复用父类任务;服务端目标可在执行中刷新内存或检查连接。
初始快照目标启动后,转发管理器会给组生产型目标推一次当前快照。不要假设必须等下一次变量变化才有数据。
变量/设备/报警/事件变化根据 UsesGroupDataProducer 走 AcceptProduced* 或 On*Changed。缓存型目标不要自己重复订阅全局事件。
StopAsync/SafetyDisposeAsync停止调度循环,释放通道、日志、缓存、客户端和服务端。必须关闭连接、释放缓存、取消订阅,避免重启后重复发送。

转发组是变量范围的唯一入口

DataForwardGroupRuntime.RebuildVariables() 会根据组配置生成两个运行态索引。

索引说明
IdVariableRuntimes当前组范围解析后的变量,key 为变量 Id。
CollectDevices当前组范围内变量反推出来的采集设备。

业务插件应通过这些入口读取数据:

入口适合场景
GetVariables()遍历当前目标可见变量,最常用。
IdVariableRuntimes按变量 Id 快速查找。
CollectDevices生成设备快照、协议服务端节点或设备连接状态。
GetVariableProperty<TProperty>(variable)读取当前目标下某个变量的强类型目标变量属性。
GetVariablePropertyValue(variable, propertyName)读取目标变量属性单字段,未配置时回退到默认属性实例。
TryGetVariablePropertyValue(...)需要区分“未配置”和“配置为空值”时使用。

目标变量属性不能把变量“拉进”转发范围。现场出现“变量有值但目标没有发送”时,先查转发组范围、组内变量启用、触发模式、目标启用和目标连接。

数据入口:直接事件和组生产

模式UsesGroupDataProducer数据入口适合插件
直接事件falseOnVariableChanged、OnDeviceChanged、OnAlarmChanged、OnPluginEventChangedModbusSlave、IEC104Slave、OPC UA Server、IEC61850Server、SyncBridge。
组生产trueAcceptProducedVariableChange、AcceptProducedVariableSnapshot、AcceptProducedDeviceSnapshot、AcceptProducedAlarm、AcceptProducedPluginEventMQTT、Kafka、RabbitMQ、Webhook、ZeroMQ、历史数据、实时数据、历史报警。

缓存型基类会把 UsesGroupDataProducer 固定为 true。转发管理器会根据组触发模式、批处理模式和最大批量统一生产数据,再调用 AcceptProduced*。这样多个目标不会各自重复做范围解析和批处理。

缓存和失败语义

DataForwardBaseWithCache 为每种模型维护独立的内存队列和文件缓存。

属性说明
CacheEnable是否保留发送失败的数据用于重试。关闭后失败批次不保留重试;不表示正常路径完全不经过 CacheDB。
CacheFileMaxLength单个缓存文件最大行数,超过后删除旧数据。
SplitSize上传或补发时的批量拆分大小。
QueueMaxCount内存队列上限,达到后先落盘;落盘失败保留已接收数据并向上游报告接收失败。
OnlineFilter是否过滤离线变量。
ConcurrencyTopic 上传并发数量,由具体插件发送实现使用。

正常发送同样经过缓存出站流程,CacheEnable 控制失败后的保留与重试。脚本类上传目标还会把已经生成但未确认的 TopicArray 写入 Topic outbox,连接恢复后 ReplayTopicUploadCache 先补发旧数据,再处理新数据。重试可能重复,接收端应按业务键做幂等;强杀前仍在内存、尚未提交的批次可能丢失。

Topic、脚本和上传模板

DataForwardBaseWithCacheIntervalScriptAll 已经处理了这些工作:

能力说明
Topic 模板TGateway/Variable/${DeviceName} 这类模板会按实体属性分组并替换。
实体脚本变量、设备、报警、插件事件都可以通过动态模型脚本重塑字段。
上传模板配置内容模板后,用 ${属性名} 生成自定义 payload。
列表/逐条IsVariableList、IsDeviceList 等控制合并发送还是逐条发送。
字典上传变量/报警可转换为 DeviceName -> Name -> 实体 的字典结构。
JSON 格式缩进和忽略 null 由属性控制。

这类插件通常只需要实现 Upload(TopicArray topicArray, CancellationToken cancellationToken)。

实现模板

脚本上传目标模板

完整可构建模板使用技能 ZIP 的 BatchHttpForwarder.cs.txt、BatchHttpProperties.cs.txt 与 PlantGatewayPlugins.csproj.txt,见 技能获取与打包步骤。实现时重点保持以下边界:

  1. 复用 DataForwardBaseWithCacheIntervalScriptAll 的组生产、四类模型、脚本、模板和缓存,只在 Upload 完成最终发送。
  2. 在目标生命周期内复用 HttpClient,用 TopicArray.Topic 作为地址、Payload.Memory 作为借用载荷;发送完成前不缓存其内存引用,不释放父链拥有的输入。
  3. 非 2xx、连接失败与超时返回失败 OperResult,生命周期取消继续传播。失败等待有界、可取消且先释放本次响应资源,不在插件内另建重试循环;由缓存父链重试。
  4. 停止时先等待 base.SafetyDisposeAsync 使缓存在途操作退场,再在 finally 中释放 HttpClient,避免发送尚未结束就销毁客户端。

配置目标后,在 数据转发 → 详细信息 核对组范围、定时/变化触发、批量、在线状态和缓存参数,再到 调试 检查管线、缓存出站和插件特殊方法。接收端同时记录实际请求与条数;详见 通讯与业务链验收。

示例的 GetLastHttpStatus 可在 协议调试 → 特殊方法 中用空参数数组 [] 查询最近 HTTP 状态。返回 200 只表示最近一次 HTTP 响应,完整交付仍需接收端内容核对与故障恢复验收。

协议服务端目标模板

协议服务端目标通常不使用脚本上传基类,而是维护一份外部协议内存映射。

查看协议服务端结构片段

以下 MyServer 是示意类型,需实现具体协议与资源释放;不是独立可编译工程。

[VariablePropertyModel(typeof(MyServerVariableProperty))] public sealed partial class MyServerTarget : DataForwardBase { private readonly MyServerProperty _properties = new(); private readonly MyServerVariableProperty _variableProperties = new(); private readonly ConcurrentQueue<VariableRuntime> _changedVariables = new(); private MyServer? _server; public override DataForwardPropertyBase TargetProperties => _properties; public override Type TargetPropertyType { get; } = typeof(MyServerProperty); public override DataForwardVariablePropertyBase VariablePropertys => _variableProperties; public override bool IsConnected() => _server?.IsRunning == true; protected override async Task ProtectedInitAsync(CancellationToken cancellationToken) { _server = new MyServer(_properties.BindUrl); await base.ProtectedInitAsync(cancellationToken).ConfigureAwait(false); } public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken) { await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false); foreach (var variable in GetVariables()) { var map = GetVariableProperty<MyServerVariableProperty>(variable); if (map != null) _server!.Register(map.Address, variable.DataType); } } protected override async Task ProtectedStartAsync(CancellationToken cancellationToken) { await _server!.StartAsync(cancellationToken).ConfigureAwait(false); await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false); } public override void OnVariableChanged(VariableRuntime variableRuntime, VariableBasicData variableData, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return; _changedVariables.Enqueue(variableRuntime); } protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) { while (_changedVariables.TryDequeue(out var variable)) { var map = GetVariableProperty<MyServerVariableProperty>(variable); if (map != null) _server!.SetValue(map.Address, variable.Value); } return Task.CompletedTask; } }

ModbusSlave、IEC104Slave、OpcUaServer、IEC61850Server 都属于这种思路:变量变化先进入插件维护的映射或队列,再由协议服务端对外提供读写。

现有插件参考

当前源码业务插件参考矩阵

下表按当前源码快照整理业务插件参考。实际可选目标会随安装包、授权、平台和运行时扫描条件变化;开发新目标前,优先找最接近的类型参考。

插件基类目标属性变量属性参考重点
HisDataForwardTargetDataForwardBaseWithCacheIntervalHisDataForwardPropertyHisDataForwardVariableProperty历史数据入库、采样策略、条件表达式、分表、保留天数。
RealDataForwardTargetDataForwardBaseWithCacheIntervalRealDataForwardProperty无实时数据表覆盖写入、周期/变化触发、数据库写入器。
HisAlarmForwardTargetDataForwardBaseWithCacheHisAlarmForwardProperty无历史报警入库、报警等级过滤、缓存补写。
ModbusSlaveDataForwardBaseModbusSlavePropertyModbusSlaveVariablePropertyModbus 从站内存映射、通道属性、变量地址、RPC 写入权限。
MqttClientProducerDataForwardBaseWithCacheIntervalScriptAllMqttClientProducerPropertyMqttClientProducerVariablePropertyMQTT 客户端上传、Topic/Payload 模板、订阅 RPC 写入。
MqttServerProducerDataForwardBaseWithCacheIntervalScriptAllMqttServerProducerPropertyMqttServerProducerVariablePropertyMQTT 服务端上传、客户端连接管理、Topic/Payload 模板。
ThingsBoardClientProducerDataForwardBaseWithCacheIntervalThingsBoardClientProducerPropertyThingsBoardClientProducerVariablePropertyThingsBoard Gateway 协议、遥测上传、设备连接/断开、RPC 写入。
KafkaProducerDataForwardBaseWithCacheIntervalScriptAllKafkaProducerPropertyDataForwardVariablePropertyKafka Producer 初始化、TopicArray 上传、并发和缓存。
RabbitMQProducerDataForwardBaseWithCacheIntervalScriptAllRabbitMQProducerPropertyDataForwardVariablePropertyRabbitMQ 连接、Exchange/RoutingKey、TopicArray 上传。
WebhookDataForwardBaseWithCacheIntervalScriptAllWebhookPropertyDataForwardVariablePropertyHTTP Webhook、请求方法、Header、Payload 模板、失败缓存。
SyncBridgeDataForwardBase、IRpcDriverSyncBridgePropertySyncBridgeVariableProperty网关间同步桥、变量变化队列、RPC 反写代理。
OpcUaServerDataForwardBaseOpcUaServerPropertyOpcUaServerVariablePropertyOPC UA 服务端节点、证书、安全策略、变量写入权限。
IEC104SlaveDataForwardBaseIEC104SlavePropertyIEC104SlaveVariablePropertyIEC104 从站、遥信/遥测/位数组映射、通道属性。
IEC61850ServerDataForwardBaseIEC61850ServerPropertyIEC61850ServerVariablePropertyIEC61850 Server 建模、数据集、写入索引、对象类型转换。
ZeroMQProducerDataForwardBaseWithCacheIntervalScriptAllZeroMQProducerPropertyDataForwardVariablePropertyZeroMQ Push/Pub/Dealer 上传、绑定模式、TopicArray 和缓存。

插件类型参考

类型推荐参考
外部消息系统上传MqttClientProducer、KafkaProducer、RabbitMQProducer、ZeroMQProducer。
HTTP/REST 推送Webhook。
工业协议服务端ModbusSlave、IEC104Slave、OpcUaServer、IEC61850Server。
数据库存储HisDataForwardTarget、RealDataForwardTarget、HisAlarmForwardTarget。
平台专用协议ThingsBoardClientProducer。
网关间桥接和反写代理SyncBridge。

排障与验收

常见开发错误

错误后果正确做法
在构造函数连接外部系统属性还没注入,日志和取消令牌不可用。连接放到 ProtectedStartAsync,客户端配置放到 ProtectedInitAsync。
TargetProperties 和 TargetPropertyType 不一致表单字段和运行时对象错位,导入导出异常。两者始终对应同一个强类型属性。
把目标变量属性当成范围过滤变量变化不会进入目标或排查方向错误。变量范围只由转发组决定,目标变量属性只做外部映射。
缓存型目标自己订阅全局变量事件重复发送,批处理和触发模式失效。继承缓存基类后使用 AcceptProduced* 和 Update*Model。
发送失败仍返回成功离线缓存无法接管,数据丢失且日志误导。发送失败返回失败 OperResult。
IsConnected 永远返回 true前端状态和缓存补发判断失真。根据客户端、socket、server 或 channel 真实状态返回。
忽略 CancellationToken停止目标、切换冗余或刷新配置时卡住。所有连接、发送、等待、补发都传入取消令牌。
释放时不关闭客户端/服务端端口占用、重复连接、文件缓存未释放。按依赖顺序停止在途操作并释放资源;缓存上传目标先等待父链退场,再在 finally 中释放发送客户端。
上传模板不释放 TopicArray高频上传产生内存压力。使用父类 UpdateTopicArrays,不要绕过其释放逻辑。

验证清单

场景验证点
插件发现插件管理和数据转发目标下拉能看到插件,类型为数据转发。
属性表单目标属性、变量属性、默认值、证书、枚举、导入导出都正确。
范围解析Manual、All、CollectDevice、CollectGroup 模式下,GetVariables() 数量符合预期。
变化触发单点变化能进入目标,组变量 参与组触发、更新模式 生效。
周期触发周期快照按组间隔、批处理模式和最大批量执行。
外部连接目标连接失败时 LastErrorMessage 清楚,恢复后能重连或重启成功。
离线缓存外部系统断开时失败数据进入缓存,恢复后按批次补发。
反写/RPC服务端协议或平台 RPC 反写能落到采集变量,权限关闭时拒绝。
目标重启修改属性保存、多次启停后没有重复订阅、端口占用或重复发送。
冗余目标启用冗余时主备切换不丢运行态引用,失败目标能清理干净。

可选:通过 MCP 获取开发技能和随包工程

正常开发可直接阅读上面的准备、示例和契约,不需要先配置 AI 客户端。没有源码时,可通过以下材料入口取得公开契约、同版本发布引用及配套示例。

连接目标网关 MCP,按 获取二次开发技能 读取 tgateway-development。转发任务重点读取 references/data-forward-plugins.md、references/plugin-properties-and-methods.md、references/plugin-build-and-deploy.md 与 references/communication-debugging.md。下面的源码索引供持有源码时定位;无源码用户可用公开契约、配套发布 DLL/XML 和随包工程开发。

examples/plugins/index.json 包含批量 HTTP 转发示例 BatchHttpForwarder。下载完整技能 ZIP 中的生成器,以 Analyzer 方式构建独立 net10.0 类库;将插件 DLL 安装到项目数据根目录的 PluginDlls 顶层,重启 JIT 网关并检查目录。构建、发现、目标启用、接收成功和缓存恢复分别验收,完整流程见 MCP 文档。