Files
softbus_daemon/docs/数据流转.md
flower_linux 7280fca08d dommessage
2026-04-28 21:56:56 +08:00

9.9 KiB
Raw Blame History

数据流转

1. 概念约定

  • 物理设备:真实硬件端口(当前主要是串口),由 SerialQtDriver 直接读写。
  • 设备实例SerialDevice,负责单设备生命周期、驱动回调、上下行数据接入。
  • 原始总线消息RawBusMessage,包含 endpointHashprotocolroutingKeyheaderpayloadtraceIddirection 等基础字段。
  • 流水线上下文PipelineContext,继承 RawBusMessage,增加 frameKindframeSeq 等管道字段。
  • 业务消息:经过协议解析和映射后得到 DOMMessage(统一语义数据)。

2. 上行主链路(串口 -> 消息总线)

2.1 设备初始化

SerialDeviceManager 完成以下动作:

  1. 根据配置创建并配置 SerialQtDriver(波特率、校验、停止位、流控)。
  2. 创建 SerialDevice 并注入 DeviceInfo(含协议提示,如 modbus_rtu)。
  3. 通过 bindIngress(...) 绑定:
    • IIngressPort(当前实现为 DriverIngressAdapter
    • MemoryPool(用于 payload 内存块分配)
  4. 调用 start() 打开端口并注册读回调/错误回调。

2.2 读回调进入 ingress

当串口收到字节流时,SerialDevice::onRead(...) 执行:

  1. MemoryPool 分配内存块并拷贝原始字节。
  2. 构造 RawBusMessage
    • direction = Upstream
    • endpointHash = makeEndpointHash(串口名)
    • protocol = protocolTypeFromHint(DeviceInfo.protocol)
    • routingKey = makeRoutingKey(endpointHash, deviceId)
    • header = ProtocolHeader{}(由后续 framer/parser 按协议填充 OOB 元数据)
    • payload = 原始字节数据(长度由 payload.length 表示,不再维护 payloadSize
    • traceId = serial:<endpoint>:<timestampMs>
  3. 调用 m_ingress->push(std::move(ctx)) 进入统一入口。
  4. 若内存池不足或 ingress 队列满,通过 reportError(...) 记录错误。

2.3 ingress 适配为 PipelineContext

DriverIngressAdapter::push(...) 负责轻量转换:

  1. 新建 PipelineContext
  2. pipelineCtx.raw() = std::move(ctx) 将原始消息搬运进管道上下文。
  3. 调用 PipelineEngine::enqueueIngress(...) 入总线处理队列。

3. PipelineEngine 内部流转

3.1 入口分流

enqueueIngress(...) 分两种模式:

  • 并行模式parallelPipeline=true
    • endpointHash 创建/复用独立 EndpointFramingState(每端点一个分帧线程 + chunk 队列)。
    • chunk 先进入端点队列,端点线程负责分帧并赋值 frameSeq
    • 完整帧进入 m_framedQ,由解析线程池处理。
  • 单线程模式parallelPipeline=false
    • 直接进入 m_ingressQ,由单 worker 串行处理。

3.2 Stage 1分帧Framer

dispatchFramer(...) 的选择策略:

  1. protocolProtocolType)从插件管理器选择协议分帧插件。
  2. 按结构化键 SessionKey{endpointHash, pluginId} 复用 framer/parser/mapper session保持协议状态机连续性
  3. 若没有可用插件,降级到 PassthroughFramer(透传/整包策略)。
  4. framer session 创建接口已升级为 createFramerSession(uint32_t endpointHash, ...),不再传递 endpoint 字符串。

输出:frameKind = CompleteFrame 的帧批次。

3.3 Stage 2/3/4解析 -> 过滤 -> 校验(当前返回语义)

runStages234(...) 核心流程:

  1. Stage2 Parser
    • 选择 IProtocolPlugin,复用 parser session。
    • parse(rawCtx, envelope) 产出协议封装 ProtocolEnvelope
    • 选择 mapper 插件并映射为 std::vector<DOMMessage>
  2. Stage3 Filter
    • DOMMessage 执行过滤规则(ProtocolParseFilter::process)。
  3. Stage4 Validator
    • 最低校验当前为 messageId 非空。
  4. Transform/Enrich
    • 仅对通过 filter+validator 的 DOMMessage 应用动态规则scale/offset/unit
  • 规则作用域按 endpointHash/deviceId/stableKey 匹配,不再按 endpoint 字符串匹配。
  • 写入 trace补充富化字段。

说明(与代码一致):

  • runStages234stage2Parser 返回空时才返回 false
  • 只要 stage2Parser 有消息,哪怕全部在 Stage3/Stage4 被跳过,当前实现仍返回 true
  • EnrichedMessage 在内部生成,但尚未进入独立发布/持久化通道。

3.4 出口与有序保证

  • 未开启有序出口:解析成功后直接进入 m_egressQ
  • 开启 orderedEgress
    • 解析线程先写入 m_mergeQ
    • mergerLoopendpointHash(映射键) + frameSeq 重排。
    • 使用 nextExpected 保证同端点有序输出。
    • 超过 maxReorderDepth 的乱序缓存上限会丢弃并计数。

4. 错误与背压点

主要失败点及表现(按当前实现):

  1. 内存池分配失败SerialDevice::onRead 直接 reportError(memory pool exhausted)
  2. 入口队列满enqueueIngress 返回 false计入 drop。
  3. 分帧/解析/映射插件缺失或失败stage2Parser 返回空,runStages234=false,该帧不进入 egress。
  4. 过滤/校验失败:对应 DOMMessage 被跳过(不做 enrich但该 PipelineContext 仍可能继续进入 egress只要 Stage2 非空)。
  5. 重排深度超限mergeDrop 增加并告警。

5. 当前链路时序(上行)

SerialQtDriver(read callback)
  -> SerialDevice::onRead
    -> MemoryPool::allocate + RawBusMessage
      -> IIngressPort::push (DriverIngressAdapter)
        -> PipelineEngine::enqueueIngress
          -> [Framer] dispatchFramer/feedChunk
            -> [Stage2] parse + map -> DOMMessage
              -> [Stage3] filter
                -> [Stage4] validate
                  -> transform/enrich
                    -> egressQ (有序模式下先 merge 再 egress)
                      -> DeviceBusManager::processEgressLoop
                        -> EgressRouter::send
                          -> SerialDevice::writePayload

6. egress 到设备(现状)

  • DeviceBusManager 已启动 processEgressLoop,持续从 PipelineEngine::tryPopEgress 取数据并调用 EgressRouter::send
  • EgressRouter 已实现基于 endpointHashSerialDevice 查找和 writePayload(...) 发送。
  • 当前关键缺口:SerialDeviceManager::initializeSerialDeviceWithDepsbindSerialDevice(...) 仍被注释,导致路由表通常为空,send 多数返回失败。
  • 因此当前链路是“egress 框架已存在,但默认没有绑定到串口实例”。

7. 维护建议

  1. traceId 建议在全链路透传到最终发布侧,便于定位帧级问题。
  2. 为 Stage2/Stage3/Stage4 增加细粒度指标成功率、耗时、drop 原因)。
  3. 文档后续可新增“协议插件开发约定”章节framer/parser/mapper 三类插件接口对照)。

8. 无规则冷启动与发现池(新增)

8.1 设计目标

  • 默认保持极致性能:未命中映射规则的寄存器不会产出 DOMMessage
  • 仅在配置页面开启时进入“嗅探模式Sniffer Mode将未命中点位以低成本写入内存发现池。
  • 发现池只维护“最近值”,不做全量历史存储,避免高频总线拖垮系统。

8.2 Mapper 行为Modbus RTU

ModbusRtuProtocolMapperPlugin + PipelineEngineIMappingLookup 组合行为:

  1. ReadHoldingRegistersRequest 记录 unitId -> startAddress,用于后续响应地址回填。
  2. ReadHoldingRegistersResponse 遍历寄存器并构造 compositeKey
    • compositeKey = endpointHash + protocol + slaveId + registerAddress(位拼接)。
  3. 解析优先级(统一语义)MappingRegistry(运行时绑定)优先;未命中则查 ProfileRegistrydevice_profiles.json,按 runtimeDeviceIdmodbus_rtu 协议匹配,支持 * 通配设备)。
  4. 若上述之一命中:
    • 生成轻量 DOMMessagemessageId/pointId 来自绑定或 profilemetadataId 来自 profile若有value 为工程值;静态元数据不随每条样本重复序列化,由 metadataId 在控制面/Registry 解析。
  5. 若仍未命中:
    • 若存在 discoverySink,始终上报 DiscoveryRecord(发现池内部仍受嗅探开关与限频约束);
    • 仅当嗅探开启时,主链路额外产出 modbus.unmapped:*DOMMessage 供规则调试;嗅探关闭时不产出未映射 DOM减少总线冗载。

8.3 DiscoveryPool 约束

  • 数据结构:unordered_map<uint64_t, DiscoverySample>
  • 样本内容:compositeKey/endpointHash/protocol/slaveId/registerAddress/rawValue/firstSeenMs/lastSeenMs/hitCount
  • 性能保护:
    • maxEntries 限制最大条目数(超限按最旧 lastSeenMs 淘汰);
    • minSampleIntervalMs 做按键限频,降低写放大;
    • 支持 ttlMs 自动关闭嗅探,防止遗留长期开启。

8.4 REST API配置工作台

  • POST /api/v1/discovery/start:开启嗅探(可传 ttlMs)。
  • POST /api/v1/discovery/stop:关闭嗅探。
  • GET /api/v1/discovery/status:查看嗅探状态与池容量信息。
  • GET /api/v1/discovery/samples:拉取发现样本(支持 offset/limit/endpointHash/protocol)。
  • POST /api/v1/discovery/clear:清空发现池。
  • POST /api/v1/mapping/bind:新增或更新绑定规则(compositeKey -> domPath + scale/offset/unit)。
  • POST /api/v1/mapping/unbind:删除绑定规则。
  • GET /api/v1/mapping/list:查看当前绑定规则。

8.5 双向匹配工作流

  1. Vue 打开配置页 -> 调用 discovery/start
  2. 左侧“发现列表”轮询 discovery/samples
  3. 用户执行 Bottom-Up从样本创建 DOM 绑定)或 Top-Down将样本拖到 DOM 占位符)。
  4. 前端调用 mapping/bind,规则即时生效。
  5. 下一帧同 compositeKey 数据在 mapper 命中,进入 DOM 主链路;该点位在发现列表中自动消失(查询时过滤已映射键)。