本文档深入剖析 otxFlash 刷写框架中的两种基础刷写模式——SingleECUFlasher(单 ECU 刷写器)和 SequenceECUFlasher(顺序多 ECU 刷写器)。它们与 ParallelECUFlasher 共同构成多 ECU 刷写策略的三叉戟,分别对应"单目标串行"、"多目标串行"和"多目标并行"三种拓扑。理解这两种模式是掌握整个刷写框架数据流与事件传播机制的关键。
继承体系与菱形虚拟继承
SingleECUFlasher 和 SequenceECUFlasher 并非平级的两个类——它们处于继承体系的不同深度,通过菱形虚拟继承(diamond virtual inheritance)共享 ECUFlasher 接口和 ECUFlasherImpl 公共实现。下图展示了完整的静态结构:
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()——这个核心方法被推迟到 SingleECUFlasher、SequenceECUFlasher 和 ParallelECUFlasher 三个具体类中分别完成。
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 累加到继承自 ECUFlasherImpl 的 mTotalSize 字段 |
这种在绑定阶段就完成总大小预计算的模式,使得后续的剩余时间估算(getRemainTime())可以直接基于已下载量与总量的差值计算传输速度,无需二次遍历。
Sources: SingleECUFlasher.cpp
download() 的执行流
SingleECUFlasher::download() 是一个同步阻塞方法,其执行流程可以用以下状态机描述:
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"]关键设计决策:
Fail-fast 策略:任一文件下载失败(
downloadFile返回false),立即break终止剩余文件的下载,不会尝试继续。这保证了刷写原子性的语义——要么全部成功,要么在第一个失败点停止。完成后强制对齐:循环结束后,无论成功或失败,都执行
this->mDownloadedSize = this->mTotalSize。这意味着外部观察者看到的进度(通过getRemainTime()间接反映)在 download 返回后始终显示"已完成"状态。事件对称性:
postECUDownloadStart和postECUDownloadEnd形成严格配对,后者通过endArg携带isSucceed状态,使得事件订阅者可以区分成功与失败的结束。
Sources: SingleECUFlasher.cpp
SequenceECUFlasher:顺序编排器
设计定位
SequenceECUFlasher 继承自 MultipleECUFlasher,在单 ECU 刷写的基础上引入多 ECU 顺序编排能力。它将 MultipleECUFlasher::mECUs 向量中的 ECU 对象按添加顺序逐个处理,每个 ECU 内部的文件仍然保持顺序下载。与 ParallelECUFlasher 的 TaskPool 并发模型形成鲜明对比——SequenceECUFlasher 保证确定的执行顺序和简化的竞态模型。
ECU 注册:继承自 MultipleECUFlasher
SequenceECUFlasher 自身不定义 ECU 管理接口,完全复用父类 MultipleECUFlasher 的 addECU(ECU *ecu) 和 getECUs()。addECU 的实现包含一个值得注意的细节:
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)
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() 的循环体:
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)
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 的
errorCode和message在postFileDownloadEnd事件参数中透传给所有订阅者 - 不存在 downloader 对象的复用或状态残留问题
模拟路径(FLASHER_USE_MCD_METHOD = 0)
当宏定义为 0 时,downloadFile 进入模拟模式——以 200ms 为间隔,每次递增文件大小的 10%,并通过 postFileDownloadProgress 报告进度百分比。这条路径在无 MCD 硬件环境的单元测试或 UI 调试场景中提供可控的进度模拟。
Sources: ECUFlasherImpl.cpp
事件系统的分层传播
两个刷写器都依赖 ECUFlasherImpl 提供的事件广播基础设施。事件传播遵循观察者模式的经典实现——mEventHandlers 向量存储所有已注册的 ECUFlasherEventHandler*,每个 post* 方法遍历该向量并调用相应的回调:
| 事件生命周期 | post 方法 | EventHandler 回调 | 触发时机 |
|---|---|---|---|
| ECU 开始 | postECUDownloadStart | onECUDownloadStart | ECU 级循环开始时 |
| ECU 结束 | postECUDownloadEnd | onECUDownloadEnd | ECU 所有文件处理完毕后 |
| 文件开始 | postFileDownloadStart | onFileDownloadStart | 每个文件下载前 |
| 文件结束 | postFileDownloadEnd | onFileDownloadEnd | 每个文件下载后(含错误码) |
| 文件进度 | postFileDownloadProgress | onFileDownloadProgress | 下载过程中周期性报告 |
SingleECUFlasher 的 ECU 级事件在 download() 方法内部直接发布(ECU 开始/结束各一次),而 SequenceECUFlasher 的 ECU 级事件在内层 downloadECU() 中发布——每个 ECU 触发一对开始/结束事件,使得外部观察者可以追踪每个 ECU 的独立生命周期。
Sources: ECUFlasherImpl.cpp, ECUFlasherEventHandler.hpp
三种刷写模式的对比
将 SingleECUFlasher、SequenceECUFlasher 和 ParallelECUFlasher 并置,可以清晰看到它们在不同维度上的设计取舍:
| 维度 | SingleECUFlasher | SequenceECUFlasher | ParallelECUFlasher |
|---|---|---|---|
| ECU 数量 | 1 | N(有序) | N(有序,按优先级分组) |
| 并发模型 | 单线程同步 | 单线程同步 | TaskPool 多线程 |
| 执行顺序 | N/A | 严格按添加顺序 | 同优先级内并行,组间串行 |
| 失败处理(ECU 级) | 第一个文件失败即停止 | 不停止,继续后续 ECU | 通过 atomic 计数器追踪 |
| ECU 级事件 | 1 对(开始/结束) | N 对(每 ECU 一对) | N 对(每 ECU 一对,线程安全) |
| 底层传输 | downloadFile() | downloadFile() | downloadFile() + 安全访问 + 复位流程 |
| 适用场景 | 单 ECU 诊断/测试 | 有严格顺序依赖的多 ECU 刷写 | 产线环境高吞吐并行刷写 |
在 ECUFlasherManager::download() 的实际调用路径中,当前实现使用的是 ParallelECUFlasher——它按优先级分组,组内并行、组间串行。SingleECUFlasher 和 SequenceECUFlasher 作为更基础的构建块,为未来扩展或特殊场景保留了接口空间。
Sources: ECUFlasherManager.cpp, ParallelECUFlasher.cpp
在框架中的位置与阅读指引
SingleECUFlasher 和 SequenceECUFlasher 是刷写框架的"执行单元"层。向上,它们被 ECUFlasherManager 根据 ECU 注册情况选择性地实例化和管理;向下,它们通过 ECUFlasherImpl::downloadFile() 委托给 BinFileDownloader 与 VbfFileDownloader 完成实际的文件传输(通过 MCD3D 诊断协议);横向,它们与 ParallelECUFlasher 形成串行/并行的策略矩阵。事件传播机制则依赖于 事件驱动模型 中定义的完整事件体系。
如需了解刷写文件的数据结构(VBF 解析与 Block 管理),请参阅 VBF 文件解析器;上层业务编排逻辑请参阅 jidu_flasher:刷写任务编排。