ECU 探测与 ISO-TP:在场、固件候选与可执行是三件事

作者:

在

结论:诊断链上的三个结论必须严格分开——「探测到响应」「解析到固件」「车型可执行」是三件不同的事。tester present 收到单帧响应只说明该地址在场,返回固件候选只说明查询有结果,二者都不构成车型可执行的许可。此外 get_ecu_addrs() 的 timeout 是循环条件而非硬墙钟上界,0x78 的 pending 判断只看第三个字节、是宽松的超时延长条件而非已认证的 UDS pending 状态。本文为固定源码快照 c8fb906 的静态阅读,未连接 CAN 设备、未发送任何诊断请求。

发现链路:从在场探测到品牌排序

make_tester_present_msg() 生成 0x3e 0x00 服务请求,诊断 RX 地址由 get_rx_addr_for_tx_addr() 按 11-bit 偏移或 29-bit 地址字节交换计算。get_ecu_addrs() 先排空接收,再批量发送 tester present,在时间窗口内收集允许的响应地址与总线。

函数/组件 输入与实际行为 输出边界
get_present_ecus() 从各品牌 FwQueryConfig 取 ECU 地址,按 OBD 复用模式分组;无子地址并行、有子地址逐个查询 返回已响应的 (rx_addr, subaddr, bus) 集合;未响应不等于 ECU 不存在
get_ecu_addrs() 排空接收、批量发 tester present,在时间窗口内收集允许的响应地址/总线 异常被记录后返回已收集的局部集合,不是全有或全无事务
_is_tester_present_response() 验证短单帧格式;接受 SID 0x7e 正响应或 0x7f 0x3e 负响应 「ECU 在场」不表示该 ECU 接受诊断会话或会返回固件
get_brand_ecu_matches() 将探测地址与各品牌预期 RX 地址比较 只作查询顺序启发;实现明确未比较 bus
get_fw_versions_ordered() 按已命中 ECU 数与比例排序,仅查询有命中的品牌,单一固件候选即停止 不是全品牌全 ECU 的完整清单;早停可由 fuzzy 单一候选触发

这里有两个容易被忽略的边界。其一,get_ecu_addrs() 的 timeout 是循环条件,而 can_recv(wait_for_one=True) 可阻塞,因此该值不是整个扫描过程的硬墙钟上界;经 card 的回调使用时订阅端设置了 timeout=20。其二,OBD 复用切换由参数写入及 pandad 确认构成,回调内没有独立握手超时。

独立开发工具 get_all_ecu_addrs() 枚举 11-bit 与 29-bit 发送地址,但直接把 queries 复用为 responses:11-bit 枚举覆盖整个 0x700..0x7ff 范围,29-bit 通常需要交换后的 RX 地址。因此按代码推导,该工具的 29-bit 响应多数不会通过白名单,并非可靠的完整 ECU 清单;生产用的 get_present_ecus() 是单独计算预期 RX 地址的另一条路径。

ISO-TP 查询状态机

IsoTpParallelQuery 把 TX 地址映射成预期 RX 地址,按总线与地址过滤,子地址再逐条筛选;get_data() 用每地址计数器校验多阶段请求的响应前缀,最终只返回成功完成的 (tx_addr, subaddr) -> payload。VIN 的功能寻址先发广播请求,随后由物理寻址的 ISO-TP 对象接收/发流控;固件查询通常用物理地址。

层 职责 边界
CanClient 按 bus/RX 地址过滤,处理子地址和发送节拍 只对匹配地址的帧做后续处理
IsoTpMessage 单帧、多帧、连续帧序号及流控;固件查询设为非阻塞接收、10 ms separation time 发送与接收错误可被上层记录并使对应地址结束
get_data() 每地址计数器校验多阶段响应前缀 只返回成功完成的结果,部分成功不返回
pending 分支 响应第三字节为 0x78 时延长该地址等待 只看第三字节,不先要求完整负响应前缀,属宽松条件

正常多帧进展会刷新单地址截止时间;响应的第三字节为 0x78 时走 pending 分支并延长等待。关键在于 pending 判断仅按第三字节,不先要求完整负响应前缀 0x7f ... 0x78,所以它是宽松的超时延长条件,而非已认证的 UDS pending 状态。另外 total_timeout=60 也在循环尾检查,不能约束本次阻塞式 can_recv() 的耗时。

固件数据契约

get_fw_versions() 为每个成功响应创建 CarFw,记录 ECU 类型、固件字节、TX/RX 地址、请求、品牌、bus、OBD 复用模式及 logging。请求配置允许白名单、特殊 RX offset、子地址,以及需记录但不参与指纹匹配的 ECU。个别查询异常被捕获并继续,因此返回值可以是部分固件清单。build_fw_dict() 丢弃 logging 响应;匹配函数用地址/子地址与固件版本来判定,而不是把 CarFw 中的 bus 和响应地址都作为唯一身份校验条件。

自制硬件与插件边界

以下是改造建议,不是现有运行时的保证。应将 ECU 探测器、VIN 解码器、固件查询器拆成可替换的证据提供者,但对外只暴露受限的诊断代理;插件不得直接持有原始 sendcan 发布权限。诊断代理应对 bus、地址、服务 ID、速率、总时长、同时请求数和点火/行车状态实施硬限制,物理网关/MCU 独立保留 CAN 输出安全门。状态建模上,应把正/负响应、部分超时、未查询、OBD 模式切换失败分成不同状态,不以空集合或空固件列表直接推断「该车型不受支持」;每条证据应附采集时间、硬件拓扑和本次启动标识,缓存或早停结果不得自动获得执行许可。

常见问题

收到 tester present 响应就说明 ECU 支持诊断吗?

不说明。_is_tester_present_response() 只验证短单帧格式并接受 0x7e 正响应或 0x7f 0x3e 负响应。「ECU 在场」不表示它接受诊断会话或会返回固件。

0x78 的 pending 判断是标准 UDS pending 吗?

不是。pending 分支仅按第三个字节是否为 0x78 判断,不先要求完整的负响应前缀,因此它是宽松的超时延长条件,而非已认证的 UDS pending 状态。

get_fw_versions_ordered() 会返回全部 ECU 固件吗?

不会。它按已命中 ECU 数与比例排序,只查询有命中的品牌,并在拿到单一固件候选时即停止,因此返回的是候选子集而非全品牌全 ECU 的完整清单。

为什么 get_all_ecu_addrs() 的 29-bit 结果不可靠?

因为它把 queries 直接复用为 responses,而 29-bit 通常需要交换后的 RX 地址,多数响应不会通过白名单。可靠路径是单独计算预期 RX 地址的 get_present_ecus()。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/ECU_DIAGNOSTIC_TRANSPORT_DEEP_DIVE.md(取用了 ECU 发现链、ISO-TP 查询状态机、固件数据契约、插件边界与离线探针结论)。
  • 项目 02 —— openpilot-research/CAR_MESSAGES_FIELD_CONTRACT_DEEP_DIVE.md(仅用于确认诊断结果进入车型识别与安全配置的衔接位置)。

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注