Skip to content

本文档深入剖析 otxFlash 刷写框架中的两种基础刷写模式——SingleECUFlasher(单 ECU 刷写器)和 SequenceECUFlasher(顺序多 ECU 刷写器)。它们与 ParallelECUFlasher 共同构成多 ECU 刷写策略的三叉戟,分别对应"单目标串行"、"多目标串行"和"多目标并行"三种拓扑。理解这两种模式是掌握整个刷写框架数据流与事件传播机制的关键。

继承体系与菱形虚拟继承

SingleECUFlasherSequenceECUFlasher 并非平级的两个类——它们处于继承体系的不同深度,通过菱形虚拟继承(diamond virtual inheritance)共享 ECUFlasher 接口和 ECUFlasherImpl 公共实现。下图展示了完整的静态结构:

mermaid
classDiagram
    class ECUFlasher {
        <<interface>>
        +download()* bool
        +getRemainTime(ECU*)* unsigned long long
        +addEventHandler(ECUFlasherEventHandler*)* void
        +clearEventHandlers()* void
    }

    class ECUFlasherImpl {
        #mTotalSize size_t
        #mDownloadedSize volatile size_t
        #mStartTime unsigned long long
        #mFieldLock mutex
        -mEventHandlers vector~ECUFlasherEventHandler*~
        +addEventHandler() void
        +clearEventHandlers() void
        +getRemainTime(ECU*) unsigned long long
        #downloadFile(ECU*, File*) bool
        #postECUDownloadStart() void
        #postECUDownloadEnd() void
        #postFileDownloadStart() void
        #postFileDownloadEnd() void
        #postFileDownloadProgress() void
    }

    class SingleECUFlasher {
        -mECU ECU*
        +download() bool
        +getECU() ECU*
        +setECU(ECU*) void
    }

    class MultipleECUFlasher {
        #mECUs vector~ECU*~
        +getECUs() vector~ECU*~*
        +addECU(ECU*) void
    }

    class SequenceECUFlasher {
        +download() bool
        -downloadECU(ECU*) bool
    }

    class ParallelECUFlasher {
        -mPool TaskPool*
        -mCompletedECUs atomic~int~
        +download() bool
        -downloadECU(ECU*) void
    }

    ECUFlasher <|.. ECUFlasherImpl : virtual
    ECUFlasher <|.. SingleECUFlasher : virtual
    ECUFlasher <|.. MultipleECUFlasher : virtual
    ECUFlasherImpl <|-- SingleECUFlasher : virtual
    ECUFlasherImpl <|-- MultipleECUFlasher : virtual
    MultipleECUFlasher <|-- SequenceECUFlasher
    MultipleECUFlasher <|-- ParallelECUFlasher

菱形顶点的双重角色ECUFlasher 是纯抽象接口,定义 download()getRemainTime()addEventHandler()clearEventHandlers() 四个契约方法。ECUFlasherImpl 提供所有这些方法的默认实现以及事件广播基础设施(七个 post* 方法),但它本身并不实现 download()——这个核心方法被推迟到 SingleECUFlasherSequenceECUFlasherParallelECUFlasher 三个具体类中分别完成。

Sources: ECUFlasher.hpp, ECUFlasherImpl.hpp, SingleECUFlasher.hpp, MultipleECUFlasher.hpp, SequenceECUFlasher.hpp

SingleECUFlasher:原子刷写单元

设计定位

SingleECUFlasher 是刷写框架的最小操作粒度——它封装单个 ECU 及其多个刷写文件的完整下载流程。其职责可以概括为:给定一个 ECU 对象,按文件列表顺序逐一完成所有文件的传输,并在整个过程中以同步方式驱动事件广播。

数据绑定:setECU

在调用 download() 之前,必须通过 setECU(ECU *ecu) 完成 ECU 绑定。该方法执行两个关键操作:

操作说明
指针关联this->mECU = ecu,建立与 ECU 对象的直接引用
总大小累加遍历 ecu->getFiles() 返回的所有 File 对象,将其 size 累加到继承自 ECUFlasherImplmTotalSize 字段

这种在绑定阶段就完成总大小预计算的模式,使得后续的剩余时间估算(getRemainTime())可以直接基于已下载量与总量的差值计算传输速度,无需二次遍历。

Sources: SingleECUFlasher.cpp

download() 的执行流

SingleECUFlasher::download() 是一个同步阻塞方法,其执行流程可以用以下状态机描述:

mermaid
flowchart TD
    A["download() 入口"] --> B["构造 ECUFlasherECUEventArg\npostECUDownloadStart(arg)"]
    B --> C["记录 mStartTime = getTimeStamp()"]
    C --> D["获取文件列表 files = mECU->getFiles()"]
    D --> E{"遍历每个 File\n(i < size)?"}
    E -->|是| F["调用 downloadFile(mECU, &ecuFile)"]
    F --> G{"downloadFile 返回?"}
    G -->|false| H["break 退出循环"]
    G -->|true| E
    E -->|否| I["设置 mDownloadedSize = mTotalSize"]
    H --> I
    I --> J["构造 ECUFlasherECUEventArg(endArg)\npostECUDownloadEnd(endArg)"]
    J --> K["返回 isSucceed"]

关键设计决策:

  1. Fail-fast 策略:任一文件下载失败(downloadFile 返回 false),立即 break 终止剩余文件的下载,不会尝试继续。这保证了刷写原子性的语义——要么全部成功,要么在第一个失败点停止。

  2. 完成后强制对齐:循环结束后,无论成功或失败,都执行 this->mDownloadedSize = this->mTotalSize。这意味着外部观察者看到的进度(通过 getRemainTime() 间接反映)在 download 返回后始终显示"已完成"状态。

  3. 事件对称性postECUDownloadStartpostECUDownloadEnd 形成严格配对,后者通过 endArg 携带 isSucceed 状态,使得事件订阅者可以区分成功与失败的结束。

Sources: SingleECUFlasher.cpp

SequenceECUFlasher:顺序编排器

设计定位

SequenceECUFlasher 继承自 MultipleECUFlasher,在单 ECU 刷写的基础上引入多 ECU 顺序编排能力。它将 MultipleECUFlasher::mECUs 向量中的 ECU 对象按添加顺序逐个处理,每个 ECU 内部的文件仍然保持顺序下载。与 ParallelECUFlasher 的 TaskPool 并发模型形成鲜明对比——SequenceECUFlasher 保证确定的执行顺序简化的竞态模型

ECU 注册:继承自 MultipleECUFlasher

SequenceECUFlasher 自身不定义 ECU 管理接口,完全复用父类 MultipleECUFlasheraddECU(ECU *ecu)getECUs()addECU 的实现包含一个值得注意的细节:

cpp
void MultipleECUFlasher::addECU(ECU* ecu) {
    this->mECUs.push_back(ecu);
    this->mTotalSize += ecu->getTotalSize();
}

SingleECUFlasher::setECU 逐文件累加不同,这里直接使用 ecu->getTotalSize()——该方法返回 ECU 内部通过 addFile 累加好的总大小。这种差异反映了两个类不同的关注点:SingleECUFlasher 对 ECU 内部的 File 结构有"侵入式"了解,而 MultipleECUFlasher 将 ECU 视为已封装好的聚合体。

Sources: MultipleECUFlasher.cpp, ECU.cpp

download() 与 downloadECU() 的双层循环

SequenceECUFlasher 的刷写逻辑分为两层:

  • 外层 download():遍历 mECUs 向量,对每个 ECU 调用 downloadECU(ecu)
  • 内层 downloadECU(ECU *ecu):遍历该 ECU 的 files 向量,对每个 File 调用 downloadFile(ecu, &ecuFile)
mermaid
flowchart TD
    subgraph "download() - 外层循环"
        A["开始: size = mECUs.size()\nmStartTime = getTimeStamp()"] --> B{"遍历 ECU\n(i < size)?"}
        B -->|是| C["ecu = mECUs[i]\nisSucceed = downloadECU(ecu)"]
        C --> B
        B -->|否| D["mDownloadedSize = mTotalSize\nreturn isSucceed"]
    end

    subgraph "downloadECU(ecu) - 内层循环"
        E["postECUDownloadStart(arg)"] --> F["获取 files = ecu->getFiles()"]
        F --> G{"遍历 File\n(i < size)?"}
        G -->|是| H["isSucceed = downloadFile(ecu, &ecuFile)"]
        H --> I{"成功?"}
        I -->|否| J["break"]
        I -->|是| G
        G -->|否| K["postECUDownloadEnd(arg)\nreturn isSucceed"]
        J --> K
    end

    C -.-> E

一个值得审视的行为:不中断外层循环

观察外层 download() 的循环体:

cpp
for (int i = 0; i < size; i++) {
    ECU* ecu = this->mECUs[i];
    isSucceed = downloadECU(ecu);
}

SingleECUFlasher 的内层循环不同,此处的 for 循环没有 break 语句。这意味着即使第 i 个 ECU 刷写失败,循环仍会继续处理第 i+1 个 ECU。最终返回值 isSucceed 仅反映最后一个 ECU 的刷写结果。

这种设计有两种解读角度:

视角分析
设计意图可能是有意为之——在产线环境中,即使某个 ECU 刷写失败,操作员可能仍希望看到其余 ECU 的完成状态,以便一次性汇总所有问题
潜在缺陷前序 ECU 的失败状态被无声地覆盖,调用方无法通过返回值判断是否存在中间失败;此外,失败 ECU 的后续 ECU 是否应该被刷写(可能存在依赖关系)是一个架构层面的未决问题

这一点与 ParallelECUFlasher 形成对比——后者通过 std::atomic<int> mCompletedECUs 追踪完成数量,且 download() 总是返回 true,将成败判断完全交由事件系统处理。

Sources: SequenceECUFlasher.cpp, SequenceECUFlasher.cpp, ParallelECUFlasher.cpp

共享基础:ECUFlasherImpl::downloadFile()

无论是 SingleECUFlasher 还是 SequenceECUFlasher,最终的文件传输都委托给 ECUFlasherImpl::downloadFile(ECU *ecu, File *ecuFile)。该方法通过编译期宏 FLASHER_USE_MCD_METHOD(定义为 1)控制两种实现路径:

生产路径(FLASHER_USE_MCD_METHOD = 1)

mermaid
flowchart LR
    A["downloadFile(ecu, ecuFile)"] --> B{"ECU 名称判断"}
    B -->|"包含 TCAM/BGM/CDC/ACU"| C["BinFileDownloader\n(域控 Bin 格式)"]
    B -->|其他 ECU| D["VbfFileDownloader\n(标准 VBF 格式)"]
    C --> E["postFileDownloadStart"]
    D --> E
    E --> F["downloader.downloadFile(ecu, ecuFile)"]
    F --> G["postFileDownloadEnd\n(携带 errorCode + message)"]

两种下载器的选择依据是 ECU 名称的字符串匹配——若名称中包含 "TCAM""BGM""CDC""ACU",则使用 BinFileDownloader 处理非标准格式的域控制器固件;其余 ECU 使用 VbfFileDownloader 处理标准 VBF(Vector Binary Format)格式。

每次调用 downloadFile 都会创建新的 downloader 实例(栈上对象),这意味着:

  • 每次文件下载的上下文完全隔离
  • downloader 的 errorCodemessagepostFileDownloadEnd 事件参数中透传给所有订阅者
  • 不存在 downloader 对象的复用或状态残留问题

模拟路径(FLASHER_USE_MCD_METHOD = 0)

当宏定义为 0 时,downloadFile 进入模拟模式——以 200ms 为间隔,每次递增文件大小的 10%,并通过 postFileDownloadProgress 报告进度百分比。这条路径在无 MCD 硬件环境的单元测试或 UI 调试场景中提供可控的进度模拟。

Sources: ECUFlasherImpl.cpp

事件系统的分层传播

两个刷写器都依赖 ECUFlasherImpl 提供的事件广播基础设施。事件传播遵循观察者模式的经典实现——mEventHandlers 向量存储所有已注册的 ECUFlasherEventHandler*,每个 post* 方法遍历该向量并调用相应的回调:

事件生命周期post 方法EventHandler 回调触发时机
ECU 开始postECUDownloadStartonECUDownloadStartECU 级循环开始时
ECU 结束postECUDownloadEndonECUDownloadEndECU 所有文件处理完毕后
文件开始postFileDownloadStartonFileDownloadStart每个文件下载前
文件结束postFileDownloadEndonFileDownloadEnd每个文件下载后(含错误码)
文件进度postFileDownloadProgressonFileDownloadProgress下载过程中周期性报告

SingleECUFlasher 的 ECU 级事件在 download() 方法内部直接发布(ECU 开始/结束各一次),而 SequenceECUFlasher 的 ECU 级事件在内层 downloadECU() 中发布——每个 ECU 触发一对开始/结束事件,使得外部观察者可以追踪每个 ECU 的独立生命周期。

Sources: ECUFlasherImpl.cpp, ECUFlasherEventHandler.hpp

三种刷写模式的对比

SingleECUFlasherSequenceECUFlasherParallelECUFlasher 并置,可以清晰看到它们在不同维度上的设计取舍:

维度SingleECUFlasherSequenceECUFlasherParallelECUFlasher
ECU 数量1N(有序)N(有序,按优先级分组)
并发模型单线程同步单线程同步TaskPool 多线程
执行顺序N/A严格按添加顺序同优先级内并行,组间串行
失败处理(ECU 级)第一个文件失败即停止不停止,继续后续 ECU通过 atomic 计数器追踪
ECU 级事件1 对(开始/结束)N 对(每 ECU 一对)N 对(每 ECU 一对,线程安全)
底层传输downloadFile()downloadFile()downloadFile() + 安全访问 + 复位流程
适用场景单 ECU 诊断/测试有严格顺序依赖的多 ECU 刷写产线环境高吞吐并行刷写

ECUFlasherManager::download() 的实际调用路径中,当前实现使用的是 ParallelECUFlasher——它按优先级分组,组内并行、组间串行。SingleECUFlasherSequenceECUFlasher 作为更基础的构建块,为未来扩展或特殊场景保留了接口空间。

Sources: ECUFlasherManager.cpp, ParallelECUFlasher.cpp

在框架中的位置与阅读指引

SingleECUFlasherSequenceECUFlasher 是刷写框架的"执行单元"层。向上,它们被 ECUFlasherManager 根据 ECU 注册情况选择性地实例化和管理;向下,它们通过 ECUFlasherImpl::downloadFile() 委托给 BinFileDownloader 与 VbfFileDownloader 完成实际的文件传输(通过 MCD3D 诊断协议);横向,它们与 ParallelECUFlasher 形成串行/并行的策略矩阵。事件传播机制则依赖于 事件驱动模型 中定义的完整事件体系。

如需了解刷写文件的数据结构(VBF 解析与 Block 管理),请参阅 VBF 文件解析器;上层业务编排逻辑请参阅 jidu_flasher:刷写任务编排