Skip to content

ECUFlasherManager 是整个 ECU 刷写框架的顶层编排器(Orchestrator),承担三大核心职责:ECU 注册与元数据管理刷写文件到 ECU 的绑定、以及按优先级分组的调度执行。它继承自 ECUFlasherImpl(获得事件发布基础设施)同时实现 ECUFlasherEventHandler 接口(作为内部 ParallelECUFlasher 的事件接收者),形成"接收→转发"的事件中继模式。在集度 OTX 代理的实际使用中,manager 被声明为模块级全局单例,由 jd_ecuflash_start() 创建并注入 DefaultEventHandler,随后通过独立线程执行下载流程。

Sources: ECUFlasherManager.hpp, jidu_flasher.cpp, jidu_flasher.cpp

类继承体系与在框架中的位置

ECUFlasherManager 位于刷写器继承链的最上层,其多重继承结构体现了"既是刷写器,又是事件处理器"的双重身份:

mermaid
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 维护三套并行的索引结构,各自服务不同访问模式:

数据结构类型用途访问模式
mECUsstd::map<std::string, ECU*>按名称快速查找 ECUaddECUFile 时按 ecuName 定位目标 ECU;getcmd 时按名称检索命令
mPriorityECUsstd::map<int, std::vector<ECU*>*>按优先级分组 ECUdownload() 时迭代优先级分组,同组内并行刷写
mSequenceECUNamesstd::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 刷写所需的全部元数据:

cpp
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

两种重载的共同流程如下:

mermaid
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 数据等较大缓冲区,值语义简化了生命周期管理。

Sources: ECU.cpp, File.hpp

download():优先级调度的核心算法

download() 方法是整个 Manager 的调度引擎。其算法流程以 MCDCmdBoardcast 单例的可用性为第一判断分支:

mermaid
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]

优先级调度规则可以总结为三点:

  1. 组间串行:优先级 0 的 ECU 组全部刷写完毕后,才开始优先级 1 的 ECU 组,以此类推
  2. 组内并行:同一优先级组内的所有 ECU 通过 ParallelECUFlasher 利用共享 TaskPool 并行刷写
  3. 共享线程池:同一个 TaskPool* 在所有优先级组之间复用,默认大小为 5,通过 parallelSize 构造函数参数可在 [1, 7] 范围内调整

MCDCmdBoardcast(基于 MCD3D 协议的诊断通信单例)不可用时,Manager 不会静默失败,而是主动遍历所有已注册 ECU 并发送下载结束事件(状态为 false),确保上层调用者能通过事件机制感知到刷写失败。

Sources: ECUFlasherManager.cpp

TaskPool 的创建与约束

TaskPool 在构造函数中创建,其并行线程数受严格约束:

Sources: ECUFlasherManager.cpp

构造方式parallelSize 取值最终线程数
无参构造 ECUFlasherManager()5(硬编码默认值)
有参构造 ECUFlasherManager(n)n ≤ 05(下限钳位)
有参构造 ECUFlasherManager(n)1 ≤ n ≤ 7n(直接使用)
有参构造 ECUFlasherManager(n)n > 77(上限钳位)

上限 7 的设定与 MCD3D 诊断通信的物理通道限制相关——过多的并行诊断会话会超出车辆总线带宽。析构时先调用 mPool->shutdown() 优雅关闭线程池,再释放所有 ECU 对象。

事件中继模式:从 ParallelECUFlasher 到外部 Handler

ECUFlasherManager 实现了 ECUFlasherEventHandler 的全部七个回调方法,但自身不产生任何业务逻辑——它纯粹作为透明代理,将从子级 ParallelECUFlasher 接收到的事件转发给通过 addEventHandler() 注册的外部处理器:

mermaid
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 解析后的文件信息,依次调用 addECUaddECUFile(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 LifecyclemStarted 标志将对象生命周期严格划分为"配置"与"执行"两阶段,防止竞态和状态不一致

建议阅读路径

在理解 ECUFlasherManager 的编排角色后,建议按以下路径深入: