Skip to content

本文档深入剖析集度 OTX 代理插件中的 DTC(Diagnostic Trouble Code)故障码管理子系统。该模块负责从 ECU 原始诊断响应中解析 SAE J2012 标准格式的故障码,支持工厂版与售后版两套白名单配置路径,并在报告生成阶段自动过滤被掩码的故障码项。

架构总览

DTC 模块由三个静态全局容器与六个对外 API 构成,所有对容器数据的访问均受 std::mutex mItemLock 保护,保证多线程环境下的操作原子性。模块不直接发起诊断通信,而是接收外部传入的原始 UDS 响应字节流,内部完成解析、白名单过滤、上报三道工序。

mermaid
flowchart TD
    subgraph 外部调用者
        A[OTX 诊断序列] -->|原始响应字节流| B[jd_dtc_readAll]
        C[工厂端配置] -->|分号分隔字符串| D[jd_dtc_setWhiteList]
        E[售后端配置] -->|触发重载| F[jd_dtc_reloadDtcMask]
    end

    subgraph DTC模块 jidu_dtc.cpp
        B -->|dtc_calc 解析| G[vecDtcNo]
        G -->|填充| H["m_allEcuDtcInfo<br/>map&lt;ecuName, vector&lt;JIDU_DTC_INFO_t&gt;&gt;"]
        B -->|保存原始响应| I["m_allEcuDtcAck<br/>map&lt;ecuName, CBinary&gt;"]
        D -->|解析 ECUN:dtc1,dtc2| J["m_mapEcuDtcWhiteList<br/>map&lt;ecuName, vector&lt;string&gt;&gt;"]
        F -->|加载配置文件 JSON| J

        H --> K[jd_dtc_parsedByIndex]
        J --> L[jd_dtc_isInWhiteList]
    end

    subgraph 上报阶段
        M[jd_dtc_report] -->|遍历 m_allEcuDtcInfo| N{在白名单中?}
        N -->|是| O[跳过]
        N -->|否| P[jd_report_addNokItem]
    end

    J -.-> N

    style B fill:#e1f5fe
    style D fill:#fff3e0
    style F fill:#fff3e0
    style M fill:#e8f5e9

核心设计原则:读取与过滤分离 —— jd_dtc_readAll 只负责解析与缓存原始数据,白名单过滤延迟到 jd_dtc_report 阶段执行。这意味着即使白名单后续发生变化(如通过 reloadDtcMask 重载),已缓存的 DTC 数据仍可按新规则重新过滤上报。

Sources: jidu_dtc.cpp

DTC 数据结构

JIDU_DTC_INFO_t

定义于 jidu_macro.h,是 DTC 信息的最小存储单元:

字段类型说明
strDTCNostd::stringSAE J2012 标准 7 字符故障码,如 P0301U0073
strContentstd::string故障码描述文本,当前固定为 "未定义"
cpp
typedef struct JIDU_DTC_INFO
{
    std::string strDTCNo;
    std::string strContent;
}JIDU_DTC_INFO_t;

Sources: jidu_macro.h

模块级全局容器

容器类型用途生命周期
m_allEcuDtcInfomap<string, vector<JIDU_DTC_INFO_t>>按 ECU 名称存储已解析的 DTC 列表每次 jd_dtc_readAll 覆盖对应 ECU 的条目
m_allEcuDtcAckmap<string, CBinary>按 ECU 名称保存原始 UDS 响应字节流m_allEcuDtcInfo 同步更新
m_mapEcuDtcWhiteListmap<string, vector<string>>按 ECU 名称存储白名单 DTC 编号jd_dtc_setWhiteList 全量替换;jd_dtc_reloadDtcMask 追加

这三个容器通过 std::mutex mItemLock 实现互斥访问。m_allEcuDtcAck 的存在是为了在 jd_dtc_report 上报时将原始诊断请求响应字节流一并写入报告记录,满足可追溯性要求。

Sources: jidu_dtc.cpp

故障码解析:从原始字节到 SAE 标准格式

SAE J2012 编码规则

UDS 诊断响应中,每个故障码占用 4 个字节,其中第 3~5 字节包含 DTC 编号的编码信息。第 3 个字节的高 2 位(bit7-bit6)决定了故障码的前缀字母:

高 2 位值前缀字母故障域
00 (0x00)P (Powertrain)动力总成
01 (0x40)C (Chassis)底盘
10 (0x80)B (Body)车身
11 (0xC0)U (Network)网络通信

jd_PCBU_4BYTE — 前缀判定与格式化

cpp
int jd_PCBU_4BYTE(uint8_t* binId, int inLen, char* outBuffer, int outBufferSize)

该函数接收 6 字节输入(0x8B 0x01 0x01 XX XX XX),取第 3 字节的高 2 位判断前缀字符,然后将该字节的高 2 位清零,与第 4、第 5 字节拼接为 {P|C|B|U}XX XX XX 格式的 7 字符故障码。

示例:若输入 binId[3]=0x03,由于 0x03 & 0xC0 = 0x00,前缀为 P,清零后 binId[3]=0x03,结合 binId[4]=0x01binId[5]=0x00,最终输出 P030100。实际调用中此函数仅输出 7 字符(sprintf 格式化 %c%02X%02X%02X),返回值固定为 7。

Sources: jidu_dtc.cpp

dtc_calc — 批量解析

cpp
inline bool dtc_calc(uint8_t* rf, int rfLen, vector<string>& vecDtcNo)

该内联函数负责将 UDS ReadDTCInformation(0x19 服务)响应的原始字节流解析为 DTC 编号列表。响应格式遵循 ISO 14229-1:

  • 前 3 字节为响应头(SID + 子功能 + 状态掩码)
  • 之后每 4 字节为一个 DTC 记录(HighByte + MiddleByte + LowByte + StatusByte)

函数计算 iDtcNo = (rfLen - 3) / 4 确定 DTC 数量,迭代提取每个 DTC 的 3 字节编号部分,过滤全零记录(表示无故障),调用 jd_PCBU_4BYTE 进行格式化。

cpp
uint8_t binDtcId[8] = { 0x8B, 0x01, 0x01, 0x00, 0x00, 0x00 };
int iDtcNo = (rfLen - 3) / 4;

for (int i = 0; i < iDtcNo; i++)
{
    binDtcId[3] = rf[3 + i * 4];  // DTC HighByte
    binDtcId[4] = rf[4 + i * 4];  // DTC MiddleByte
    binDtcId[5] = rf[5 + i * 4];  // DTC LowByte
    // 过滤全零记录
    if ((binDtcId[3] == 0) && (binDtcId[4] == 0) && (binDtcId[5] == 0))
        continue;
    // 格式化为 SAE 标准字符串
    jd_PCBU_4BYTE(binDtcId, 6, dtcIdBuffer, 128);
    vecDtcNo.push_back(dtcIdBuffer);
}

Sources: jidu_dtc.cpp

API 详解

jd_dtc_readAll — 读取全部故障码

cpp
OTX_API int jd_dtc_readAll(const char* ecuName, uint8_t* dtcBytefiled, uint32_t inLen);

这是 DTC 模块的数据入口。工作流程:

mermaid
sequenceDiagram
    participant Caller as OTX 诊断序列
    participant ReadAll as jd_dtc_readAll
    participant Calc as dtc_calc
    participant Map as 模块全局容器

    Caller->>ReadAll: ecuName, 原始响应字节流, inLen
    ReadAll->>ReadAll: lock(mItemLock)
    ReadAll->>ReadAll: Tracer::hex 记录原始字节
    ReadAll->>Calc: dtc_calc(dtcBytefiled, inLen, vecDtcNo)
    Calc-->>ReadAll: 返回 DTC 编号列表
    ReadAll->>Map: 清空 m_allEcuDtcInfo[ecuName]
    ReadAll->>Map: m_allEcuDtcAck[ecuName] = CBinary(原始字节)
    loop 每个 DTC 编号
        ReadAll->>Map: m_allEcuDtcInfo[ecuName].push_back({编号, "未定义"})
    end
    ReadAll-->>Caller: 返回 DTC 数量

每次调用会覆盖该 ECU 之前缓存的全部 DTC 数据——先执行 m_allEcuDtcInfo[ecuName].clear() 再重新填充。返回值为解析出的 DTC 总数。

关键观察strContent 字段始终设为 "未定义",表明当前版本不查询 DTC 描述数据库。jd_dtc_queryByDTCNo 函数体直接返回 -1(空实现),佐证了这一点。

Sources: jidu_dtc.cpp

jd_dtc_parsedByIndex — 按索引查询

cpp
OTX_API int jd_dtc_parsedByIndex(const char* ecuName, int index, char* outBuffer, int outBufferSize);

从已缓存的 DTC 列表中按索引取出 DTC 编号字符串。返回值为写入 outBuffer 的字节数,错误时返回 -1(ECU 无数据)或 false(缓冲区过小)。

mermaid
flowchart LR
    A[输入: ecuName, index] --> B{ecuName 存在于 m_allEcuDtcInfo?}
    B -->|否| C[返回 -1]
    B -->|是| D{index < vecDtcInfo.size?}
    D -->|否| E[返回 -1]
    D -->|是| F[memcpy strDTCNo 到 outBuffer]
    F --> G[返回 true]

Sources: jidu_dtc.cpp

jd_dtc_isInWhiteList — 白名单判定

cpp
OTX_API int jd_dtc_isInWhiteList(const char* ecuName, const char* dtcNo);

判定逻辑的核心是 ECU 名称的域控后缀剥离。代码注释明确指出:"白名单中的 Ecu 名称不包含域控后缀"。对于以 _SOC_MCU 结尾的 ECU 名称(如 BGM_SOCBGM_MCU),函数会截取下划线之前的部分(BGM)用于白名单匹配。

cpp
string newName = ecuName;
if (MyStringUtils::endWith(newName, "_SOC") || MyStringUtils::endWith(newName, "_MCU")) {
    newName = newName.substr(0, newName.find('_'));
}

返回值:1 表示在名单中(应被过滤),0 表示不在(有效故障码,需要上报)。

Sources: jidu_dtc.cpp

白名单配置的双路径设计

系统支持两种白名单配置路径,分别服务于工厂端和售后端场景:

维度jd_dtc_setWhiteList(工厂版)jd_dtc_reloadDtcMask(售后版)
输入格式分号+冒号+逗号分隔字符串配置文件中的 JSON 数组
ECU 标识方式直接使用 ECU 名称字符串通过 DoIP 地址反查 ECU 名称
车型过滤vehicleModelCode 匹配当前车辆类型
容器操作clear() 后全量替换clear() 后重新加载
触发方式诊断序列主动调用OTX 运行时调用 callServer 后自动触发

工厂版:jd_dtc_setWhiteList

输入字符串格式为 "ECU1:DTC1,DTC2;ECU2:DTC3,DTC4"。解析过程使用三级分割:

mermaid
flowchart TD
    A["输入: ECU1:DTC1,DTC2;ECU2:DTC3"] -->|";" 分割| B["ECU1:DTC1,DTC2", "ECU2:DTC3"]
    B -->|每个元素 ":" 分割| C{vecTemp.size() == 2?}
    C -->|是| D["ecuName=vecTemp[0]<br/>dtcList=vecTemp[1]"]
    D -->|"," 分割| E["vecDtc = [DTC1, DTC2]"]
    E --> F["m_mapEcuDtcWhiteList[ecuName].push_back(每个DTC)"]
    C -->|否| G[丢弃]

每次调用先 m_mapEcuDtcWhiteList.clear(),执行全量替换而非增量追加。

Sources: jidu_dtc.cpp

售后版:jd_dtc_reloadDtcMask

售后版的配置加载链路更为复杂,涉及两级配置文件读取:

mermaid
flowchart TD
    A["jd_dtc_reloadDtcMask()"] --> B["获取 appPath + Diagnosis/config/{env}/config.json"]
    B --> C{文件存在?}
    C -->|否| D["Tracer::error, 返回 -1"]
    C -->|是| E["逐项遍历 JSON 数组"]
    E --> F{vehicleModelCode 匹配?}
    F -->|不匹配| E
    F -->|匹配或无此字段| G{configOptionName == 'DtcMask'?}
    G -->|否| E
    G -->|是| H["取出 resultFile 路径"]
    H --> I["调用 reloadDtcMask(resultFile)"]

第一级 config.json 的结构为 JSON 数组,每项包含可选字段 vehicleModelCode(车型代码,如 "MarsOne""Venus")、configOptionName(配置项名称)、resultFile(实际掩码文件路径)。程序会匹配 DataCenter::m_vehicleInfo.vehicleType 与车型代码,然后找到 configOptionName"DtcMask" 的条目,将其 resultFile 路径传给 reloadDtcMask

Sources: jidu_dtc.cpp

reloadDtcMask — 底层掩码文件加载器

cpp
int reloadDtcMask(string filePath)

掩码文件的 JSON 结构是一个数组,每项包含:

  • doipAddr:ECU 的 DoIP 逻辑地址(16 进制字符串)
  • dtcList:该 ECU 的 DTC 白名单数组
json
[
  {
    "doipAddr": "1001",
    "dtcList": ["U0073", "U0100", "P0606"]
  },
  {
    "doipAddr": "1201",
    "dtcList": ["U0073"]
  }
]

函数通过 get_jd_ecuName_byIpAddr(MyStringUtils::toLong(doipAddr, 16), ...) 将 DoIP 地址转换为 ECU 名称,然后将白名单条目通过 emplace 插入 m_mapEcuDtcWhiteList。如果 DoIP 地址无法解析为已知 ECU,则记录错误并返回 -1

文件读取有两个容错路径:优先用 fopen 直接读取;若 cJSON_Parse 失败(文件可能经 FlashFileUtils 编码),则回退到 CFlashFileUtils::LoadConfig 重新尝试解析。

Sources: jidu_dtc.cpp

上报阶段:jd_dtc_report

cpp
OTX_API int jd_dtc_report(char* statisticId, char* statisticName, char* blockId, char* blockName);

此函数遍历 m_allEcuDtcInfo 中每个 ECU 的每条 DTC,在白名单过滤后,将未命中白名单的故障码通过 jd_report_addNokItem 写入诊断报告。

mermaid
flowchart TD
    A["遍历 m_allEcuDtcInfo"] --> B["构建 itemInfo:<br/>blockId, ECU_DTC_Read, 01, ecuName, 0x1EFF, 0x00"]
    B --> C{vecDtcInfo.size() > 0?}
    C -->|否| A
    C -->|是| D["遍历每条 DTC"]
    D --> E{ecuName 在白名单中<br/>且当前 DTCNo 命中?}
    E -->|是| F["continue 跳过"]
    E -->|否| G["jd_report_addNokItem<br/>itemInfo, cmdReq=0x190209, cmdAck=原始响应, failReason=ecuName: DTCNo,内容"]
    G --> D

上报时 itemInfo 参数固定为 "{blockId},ECU_DTC_Read,01,{ecuName},0x1EFF,0x00"

  • itemName = "ECU_DTC_Read"(测试项名称)
  • itemId = "01"(测试项 ID)
  • ecuAddr = "0x1EFF"(固定逻辑地址)
  • expectedData = "0x00"(期望无故障码)

cmdReq 固定为 \x19\x02\x09(UDS ReadDTCInformation 按状态掩码 0x09 请求),cmdAckm_allEcuDtcAck[ecuName] 中保存的原始响应字节。failReason 采用 "{ecuName}: {dtcNo},{strContent}" 格式。

设计考量:每条未过滤的 DTC 都作为独立的 NOK(不合格)项上报,而非聚合成单条。这意味着一个有 20 个故障码的 ECU 会产生 20 条上报记录,每条都携带完整的请求/响应诊断数据。

Sources: jidu_dtc.cpp

完整数据流

mermaid
sequenceDiagram
    participant Diag as 诊断序列
    participant DTC as DTC 模块
    participant WL as 白名单管理
    participant Rpt as 报告系统

    Note over Diag,Rpt: 阶段一:配置白名单(二选一)
    Diag->>WL: jd_dtc_setWhiteList("BGM:U0073;CDC:U0100")
    WL->>WL: 解析 → m_mapEcuDtcWhiteList

    Note over Diag,Rpt: 阶段二:读取故障码
    Diag->>DTC: jd_dtc_readAll("BGM_SOC", rawBytes, len)
    DTC->>DTC: dtc_calc 解析 → vecDtcNo
    DTC->>DTC: m_allEcuDtcInfo["BGM_SOC"] = DTC列表
    DTC->>DTC: m_allEcuDtcAck["BGM_SOC"] = 原始字节

    Note over Diag,Rpt: 阶段三:按需查询
    Diag->>DTC: jd_dtc_isInWhiteList("BGM_SOC", "U0073")
    DTC->>DTC: 剥离后缀 → "BGM"
    DTC->>DTC: 在 m_mapEcuDtcWhiteList["BGM"] 中查找
    DTC-->>Diag: 1(在名单中)

    Note over Diag,Rpt: 阶段四:上报
    Diag->>DTC: jd_dtc_report(statId, statName, blockId, blockName)
    DTC->>DTC: 遍历每 ECU 每条 DTC
    DTC->>WL: 白名单过滤
    DTC->>Rpt: jd_report_addNokItem(未过滤项)

线程安全设计

模块中 mItemLockstd::mutex)在以下函数中加锁:

函数锁策略
jd_dtc_readAlllock_guard 覆盖整个函数
jd_dtc_setWhiteList无显式加锁(仅操作 m_mapEcuDtcWhiteList,调用方应保证串行)
jd_dtc_reloadDtcMasklock_guard 覆盖整个函数
jd_dtc_isInWhiteList无显式加锁(只读访问,但存在 TOCTOU 风险)
jd_dtc_parsedByIndex无显式加锁(只读访问)
jd_dtc_report无显式加锁(遍历期间数据可能被修改)

jd_dtc_setWhiteListjd_dtc_isInWhiteListjd_dtc_report 之间缺少同步机制,理论上存在数据竞争。实际使用中,白名单配置通常在诊断序列启动阶段完成,DTC 读取与上报在后续阶段执行,时间上的自然隔离降低了并发冲突概率。

与其他模块的关系

mermaid
graph TD
    DTC[jidu_dtc.cpp<br/>DTC 模块] --> Macro[jidu_macro.h<br/>JIDU_DTC_INFO_t 定义]
    DTC --> Tracer[tracer.h<br/>分级日志输出]
    DTC --> DC[jidu_dataCenter.h<br/>m_vehicleInfo.vehicleType<br/>车型匹配]
    DTC --> EC[jidu_envConfig.h<br/>getAppPath / getEnvPath<br/>配置文件路径]
    DTC --> Str[MyStringUtils.h<br/>split / endWith / toLong]
    DTC --> Bin[binary.h<br/>CBinary 字节缓冲]
    DTC --> Flash[FlashFileUtils.h<br/>加密文件读取容错]
    DTC --> cJSON[cjson.h<br/>JSON 解析]
    DTC --> Rpt[jidu_report.cpp<br/>jd_report_addNokItem<br/>NOK 项上报]
    DTC --> Func[jidu.cpp<br/>get_jd_ecuName_byIpAddr<br/>DoIP→ECU 名称映射]

    style DTC fill:#1565c0,color:#fff
    style Rpt fill:#2e7d32,color:#fff
    style DC fill:#e65100,color:#fff

扩展阅读