ECUFlasherManager 是整个 ECU 刷写框架的顶层编排器(Orchestrator),承担三大核心职责:ECU 注册与元数据管理、刷写文件到 ECU 的绑定、以及按优先级分组的调度执行。它继承自 ECUFlasherImpl(获得事件发布基础设施)同时实现 ECUFlasherEventHandler 接口(作为内部 ParallelECUFlasher 的事件接收者),形成"接收→转发"的事件中继模式。在集度 OTX 代理的实际使用中,manager 被声明为模块级全局单例,由 jd_ecuflash_start() 创建并注入 DefaultEventHandler,随后通过独立线程执行下载流程。
Sources: ECUFlasherManager.hpp, jidu_flasher.cpp, jidu_flasher.cpp
类继承体系与在框架中的位置
ECUFlasherManager 位于刷写器继承链的最上层,其多重继承结构体现了"既是刷写器,又是事件处理器"的双重身份:
classDiagram
class ECUFlasher {
<<interface>>
+download() bool
+getRemainTime(ECU*) unsigned long long
+addEventHandler(ECUFlasherEventHandler*)
+clearEventHandlers()
}
class ECUFlasherImpl {
#mEventHandlers vector
#mTotalSize size_t
#mDownloadedSize size_t
#mStartTime unsigned long long
#mFieldLock mutex
+postECUDownloadStart(ECUFlasherECUEventArg&)
+postECUDownloadEnd(ECUFlasherECUEventArg&)
+postFileDownloadStart(ECUFlasherFileEventArg&)
+postFileDownloadEnd(ECUFlasherFileDownloadEndEventArg&)
+postFileDownloadProgress(ECUFlasherFileDownloadProgressEventArg&)
+downloadFile(ECU*, File*) bool
}
class ECUFlasherEventHandler {
<<interface>>
+onECUDownloadStart(ECUFlasherECUEventArg&)
+onECUDownloadEnd(ECUFlasherECUEventArg&)
+onECUResetStart(ECUFlasherECUEventArg&)
+onECUResetEnd(ECUFlasherECUEventArg&)
+onFileDownloadStart(ECUFlasherFileEventArg&)
+onFileDownloadEnd(ECUFlasherFileDownloadEndEventArg&)
+onFileDownloadProgress(ECUFlasherFileDownloadProgressEventArg&)
}
class ECUFlasherManager {
-mPriorityECUs map~int,vector~ECU*~*~
-mECUs map~string,ECU*~
-mSequenceECUNames vector~string~
-mStarted bool
-mPool TaskPool*
+addECU(name,id,dbLogicalLinkName,priority,...)
+addECUFile(ecuName,fileType,pinCode,keyInfo,parser)
+addECUFile(ecuName,filePath,fileType,fileSize,pinCode,keyInfo)
+download() bool
+getProjectPath(string&) bool
+getcmd(ecuName,vecCmd) bool
+onECUDownloadStart(...)
+onECUDownloadEnd(...)
+onFileDownloadEnd(...)
+onFileDownloadProgress(...)
}
class ParallelECUFlasher {
-mPool TaskPool*
+download() bool
+addECU(ECU*)
}
ECUFlasher <|-- ECUFlasherImpl
ECUFlasher <|.. ECUFlasherManager
ECUFlasherImpl <|-- ECUFlasherManager
ECUFlasherEventHandler <|.. ECUFlasherManager
ECUFlasherManager ..> ParallelECUFlasher : creates per priority这种设计的关键洞察在于:ECUFlasherManager 自身并不执行任何实际的刷写操作——它的 downloadFile() 方法直接抛出异常(throw -1,标注为"永远不要调用这个方法")。刷写工作完全委托给按优先级组创建的 ParallelECUFlasher 实例,Manager 充当的是工厂、调度器和事件代理的三合一角色。
Sources: ECUFlasherManager.hpp, ECUFlasherManager.cpp, ECUFlasherImpl.hpp, ECUFlasher.hpp, ECUFlasherEventHandler.hpp
三大内部数据结构:ECU 的多维索引
ECUFlasherManager 维护三套并行的索引结构,各自服务不同访问模式:
| 数据结构 | 类型 | 用途 | 访问模式 |
|---|---|---|---|
mECUs | std::map<std::string, ECU*> | 按名称快速查找 ECU | addECUFile 时按 ecuName 定位目标 ECU;getcmd 时按名称检索命令 |
mPriorityECUs | std::map<int, std::vector<ECU*>*> | 按优先级分组 ECU | download() 时迭代优先级分组,同组内并行刷写 |
mSequenceECUNames | std::vector<std::string> | 保留 ECU 添加的先后顺序 | 记录插入时序(当前代码中未在调度逻辑中使用,为扩展保留) |
std::map 的自动排序特性(按 key 升序)被直接利用来实现优先级调度:优先级值越小,在 map 中的迭代顺序越靠前,越先被调度执行。这意味着 priority=0 的 ECU 组最先刷写,priority=1 次之,依此类推。所有三套结构在析构函数中被完整清理——mECUs 中的 ECU* 和 mPriorityECUs 中的 vector<ECU*>* 均需手动 delete,因为这些指针的生命周期由 Manager 管理。
Sources: ECUFlasherManager.hpp, ECUFlasherManager.cpp, ECUFlasherManager.cpp
addECU:ECU 注册与优先级分组
addECU 接受八个参数,覆盖 ECU 刷写所需的全部元数据:
void addECU(
const std::string& name, // 友好名称(如 "EV_BGM")
const std::string& id, // 唯一标识符(ECU ID 的字符串形式)
const std::string& dbLogicalLinkName, // MCD 数据库中的逻辑连接名
int priority, // 优先级(越小越先刷写)
int securityAccessMode = 0, // 0=普通刷写(优先pinCode), 1=换件刷写(优先FFFFFFFF)
int signatureMode = 0, // 0=prod/dev都使用, 1=dev, 2=prod
int securityAccess = 1, // 是否进行安全校验
int installVerify = 1 // 是否进行安装校验
);方法内部有严格的阶段守卫:mStarted 为 true 时(即 download() 已被调用)抛出异常,防止运行时修改刷写计划。这确保了"配置阶段"与"执行阶段"的清晰分离。ECU 的 name 作为主键,重复注册同名的 ECU 也会被拒绝。注册成功后,ECU 被同时插入三套索引结构——若该优先级分组尚不存在,则新建 std::vector<ECU*>* 并插入 map,随后将 ECU 指针追加到对应分组尾部。
Sources: ECUFlasherManager.cpp
addECUFile:两种文件绑定重载
ECUFlasherManager 提供两个重载版本的 addECUFile,分别对应不同的刷写文件来源:
重载一(VBF Parser 版本):接受已解析的 VBFParserSmall* 对象,从 parser 中提取 VBF 数据、Block 信息和文件头,构建完整的 File 对象。该方法会深拷贝 parser 的全部内容——包括 Header、VBFBlocks 和原始数据缓冲区——确保外部释放 parser 后不影响内部 File 对象。
Sources: ECUFlasherManager.cpp
重载二(文件路径版本):接受文件系统路径、文件类型、文件大小和 pinCode/keyInfo,构建轻量 File 对象。适用于 Bin 文件(域控制器如 TCAM、BGM、CDC、ACU 的非 VBF 格式刷写包)。
Sources: ECUFlasherManager.cpp
两种重载的共同流程如下:
flowchart TD
A[addECUFile 调用] --> B{mStarted?}
B -->|true| C[抛出异常: 启动后不能修改]
B -->|false| D[在 mECUs 中按名称查找 ECU]
D --> E{ECU 存在?}
E -->|否| F[抛出异常: 先调用 addECU]
E -->|是| G{重载类型?}
G -->|VBF Parser| H[从 parser 深拷贝 VBF 数据/Blocks/Header]
G -->|文件路径| I[从路径/大小构建 File]
H --> J[设置 File 属性: type, pinCode, keyInfo]
I --> J
J --> K[ecu->addFile(file)]
K --> L[累加 mTotalSize]File 对象通过值传递存入 ECU::mFiles 向量(见 ECU.cpp 第 34 行:this->mFiles.push_back(file)),这是因为 File 结构体包含 VBF 数据等较大缓冲区,值语义简化了生命周期管理。
download():优先级调度的核心算法
download() 方法是整个 Manager 的调度引擎。其算法流程以 MCDCmdBoardcast 单例的可用性为第一判断分支:
flowchart TD
START[download 调用] --> SET[mStarted = true]
SET --> BC{MCDCmdBoardcast 可用?}
BC -->|否| FAIL[遍历所有优先级组]
FAIL --> POSTFAIL[对每个 ECU 发送 onECUDownloadEnd status=false]
POSTFAIL --> RETFALSE[返回 false]
BC -->|是| ITER[按优先级升序遍历 mPriorityECUs]
ITER --> CREATE[为该优先级组创建 ParallelECUFlasher]
CREATE --> REG[注册 this 为 EventHandler]
REG --> ADD[将组内所有 ECU 添加到 flasher]
ADD --> DL[flasher.download 阻塞等待本组完成]
DL --> MARK[将组内每个 ECU 的 downloadedSize 设置为 TotalSize]
MARK --> NEXT{还有下一优先级组?}
NEXT -->|是| ITER
NEXT -->|否| CLEAN[清理 MCDCmdBoardcast 实例]
CLEAN --> RETTRUE[返回 true]优先级调度规则可以总结为三点:
- 组间串行:优先级 0 的 ECU 组全部刷写完毕后,才开始优先级 1 的 ECU 组,以此类推
- 组内并行:同一优先级组内的所有 ECU 通过
ParallelECUFlasher利用共享TaskPool并行刷写 - 共享线程池:同一个
TaskPool*在所有优先级组之间复用,默认大小为 5,通过parallelSize构造函数参数可在 [1, 7] 范围内调整
当 MCDCmdBoardcast(基于 MCD3D 协议的诊断通信单例)不可用时,Manager 不会静默失败,而是主动遍历所有已注册 ECU 并发送下载结束事件(状态为 false),确保上层调用者能通过事件机制感知到刷写失败。
Sources: ECUFlasherManager.cpp
TaskPool 的创建与约束
TaskPool 在构造函数中创建,其并行线程数受严格约束:
Sources: ECUFlasherManager.cpp
| 构造方式 | parallelSize 取值 | 最终线程数 |
|---|---|---|
无参构造 ECUFlasherManager() | — | 5(硬编码默认值) |
有参构造 ECUFlasherManager(n) | n ≤ 0 | 5(下限钳位) |
有参构造 ECUFlasherManager(n) | 1 ≤ n ≤ 7 | n(直接使用) |
有参构造 ECUFlasherManager(n) | n > 7 | 7(上限钳位) |
上限 7 的设定与 MCD3D 诊断通信的物理通道限制相关——过多的并行诊断会话会超出车辆总线带宽。析构时先调用 mPool->shutdown() 优雅关闭线程池,再释放所有 ECU 对象。
事件中继模式:从 ParallelECUFlasher 到外部 Handler
ECUFlasherManager 实现了 ECUFlasherEventHandler 的全部七个回调方法,但自身不产生任何业务逻辑——它纯粹作为透明代理,将从子级 ParallelECUFlasher 接收到的事件转发给通过 addEventHandler() 注册的外部处理器:
sequenceDiagram
participant PF as ParallelECUFlasher
participant MGR as ECUFlasherManager
participant EXT as DefaultEventHandler(外部)
PF->>MGR: onECUDownloadStart(arg)
MGR->>EXT: postECUDownloadStart(arg) → onECUDownloadStart(arg)
PF->>MGR: onFileDownloadProgress(arg)
MGR->>EXT: postFileDownloadProgress(arg) → onFileDownloadProgress(arg)
PF->>MGR: onFileDownloadEnd(arg)
Note over MGR: 补偿 99%→100% 进度缺口
MGR->>EXT: postFileDownloadEnd(arg) → onFileDownloadEnd(arg)
PF->>MGR: onECUDownloadEnd(arg)
MGR->>EXT: postECUDownloadEnd(arg) → onECUDownloadEnd(arg)事件转发链路中有一个值得注意的进度补偿机制:在 onFileDownloadEnd 中,Manager 检测 ecuFile->getDownloadedSize() < ecuFile->getSize() 的情况——某些底层下载引擎在接近完成时,最后 1% 的进度可能直接跳至 100% 并触发 End 事件而不经过 Progress 事件。Manager 在持锁状态下计算差额并补入 ecu->setDownloadedSize(),确保剩余时间估算的准确性。
Sources: ECUFlasherManager.cpp
同时存在一个已知缺陷:onECUResetStart 的实现错误地调用了 this->postECUResetEnd(arg) 而非 this->postECUResetStart(arg),这意味着 ECU 复位开始事件会被误发为复位结束事件,外部监听者无法区分这两个生命周期阶段。
Sources: ECUFlasherManager.cpp, ECUFlasherImpl.cpp
getRemainTime 与 getProjectPath:辅助查询接口
getRemainTime(ECU* ecu) 在调用前检查 mStarted 状态——启动前访问会抛出异常。通过后委托给 ECUFlasherImpl::getRemainTime(),后者基于该 ECU 已下载字节数和全局起始时间戳计算下载速率,进而推算剩余时间(单位:微秒)。
Sources: ECUFlasherManager.cpp, ECUFlasherImpl.cpp
getProjectPath(std::string& filepath) 通过 MCDProjectProvider::getSelectedProject() 获取当前 MCD 项目,进而提取项目短名称。这在 pthread_download() 中被用于构造刷写数据临时文件的输出路径(projects\<projectPath>\flashdata\)。
Sources: ECUFlasherManager.cpp
getcmd:ECU 命令收集接口
getcmd 为特定 ECU 提供命令收集功能——返回该 ECU 通过 ECU::addCmd() 累积的诊断命令字符串向量,并在读取后立即清空。这一机制支持刷写框架之外的诊断命令注入:外部代码可在不直接访问 ECU 对象的情况下,通过 Manager 获取并消费命令。
Sources: ECUFlasherManager.cpp, ECU.cpp, jidu_flasher.cpp
在 jidu_flasher 中的实际使用流程
Manager 在 jidu_flasher.cpp 中被声明为模块级静态指针,由三个入口函数之一创建:
| 入口函数 | 触发场景 | Manager 使用模式 |
|---|---|---|
jd_ecuflash_start() | 整车批量刷写 | 创建 → 注册事件 → 异步线程执行 pthread_download |
jd_ecuFlash_start2() | 单 ECU 指定文件刷写 | 创建 → 清空任务表 → 异步线程执行 pthread_download2 |
jd_ecuFlash_start3() | 桥接 ECU 刷写 | 创建 → 清空任务表 → 异步线程执行 pthread_download3 |
pthread_download() 的典型流程展示了 Manager 的完整生命周期:先注册 DefaultEventHandler 监听刷写事件,获取项目路径,然后遍历 mapEcuFlasherTask 中的每个待刷写 ECU——对于域控制器(TCAM/BGM/CDC/ACU)走 Bin 文件路径,对其余 ECU 通过 CVehicleConfig 获取 VBF 解析后的文件信息,依次调用 addECU 和 addECUFile(VBF Parser 版本)完成注册与绑定。最后,只要存在任何一个状态为 Ecu_FotaStatus_Expect 的 ECU,就调用 manager->download() 启动优先级调度刷写。
Sources: jidu_flasher.cpp, jidu_flasher.cpp
设计模式总结
ECUFlasherManager 综合运用了多种设计模式:
| 模式 | 体现 |
|---|---|
| Facade(外观) | 对上层屏蔽 ParallelECUFlasher、ECU、File 的复杂交互,暴露简洁的 addECU → addECUFile → download 三步接口 |
| Observer(观察者) | 通过 ECUFlasherEventHandler 接口实现事件中继,Manager 既是 ParallelECUFlasher 的观察者,又是外部 DefaultEventHandler 的被观察者 |
| Strategy(策略) | 优先级分组本身就是一种调度策略——组内并行的策略委托给 ParallelECUFlasher,组间串行的策略由 Manager 的迭代循环实现 |
| Two-Phase Lifecycle | mStarted 标志将对象生命周期严格划分为"配置"与"执行"两阶段,防止竞态和状态不一致 |
建议阅读路径
在理解 ECUFlasherManager 的编排角色后,建议按以下路径深入:
- ParallelECUFlasher:基于 TaskPool 的多 ECU 并行刷写引擎 — Manager 的核心委托对象,理解组内并行刷写的实现机制
- 刷写器架构总览:从 ECUFlasher 接口到多 ECU 并行刷写的继承体系 — 理解 ECUFlasherManager 在整个继承树中的位置
- jidu_flasher:刷写任务编排、硬件版本检查与 FOTA 状态跟踪 — 查看 Manager 在真实业务场景中的完整调用链
- 事件驱动模型:刷写生命周期事件的发布与订阅机制 — 深入理解 Manager 事件中继背后的完整事件体系