# 数据流转

## 1. 概念约定

- **物理设备**：真实硬件端口（当前主要是串口），由 `SerialQtDriver` 直接读写。
- **设备实例**：`SerialDevice`，负责单设备生命周期、驱动回调、上下行数据接入。
- **原始总线消息**：`RawBusMessage`，包含 `endpointHash`、`protocol`、`routingKey`、`header`、`payload`、`traceId`、`direction` 等基础字段。
- **流水线上下文**：`PipelineContext`，继承 `RawBusMessage`，增加 `frameKind`、`frameSeq` 等管道字段。
- **业务消息**：经过协议解析和映射后得到 `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. 按 `protocol`（`ProtocolType`）从插件管理器选择协议分帧插件。
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，补充富化字段。

说明（与代码一致）：
- `runStages234` 在 `stage2Parser` 返回空时才返回 `false`。
- 只要 `stage2Parser` 有消息，哪怕全部在 Stage3/Stage4 被跳过，当前实现仍返回 `true`。
- `EnrichedMessage` 在内部生成，但尚未进入独立发布/持久化通道。

### 3.4 出口与有序保证

- 未开启有序出口：解析成功后直接进入 `m_egressQ`。
- 开启 `orderedEgress`：
  - 解析线程先写入 `m_mergeQ`。
  - `mergerLoop` 按 `endpointHash(映射键) + 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. 当前链路时序（上行）

```text
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` 已实现基于 `endpointHash` 的 `SerialDevice` 查找和 `writePayload(...)` 发送。
- 当前关键缺口：`SerialDeviceManager::initializeSerialDeviceWithDeps` 中 `bindSerialDevice(...)` 仍被注释，导致路由表通常为空，`send` 多数返回失败。
- 因此当前链路是“egress 框架已存在，但默认没有绑定到串口实例”。

---

## 7. 维护建议

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