Appearance
车机调试与刷机工具链
车机开发的日常一半是「在车上改个状态看看效果」,另一半是「把台架刷到对的版本」。前者我们做了 AdbForNapa:一条 adb 广播透传字符串直达 Unity 业务,加指令不改链路代码;后者把 CanStudio 诊断和 XPFlash 刷机的坑全部固化成手顺。
调试指令注入免改代码
指令入口脚本 + 编辑器 + adb 三端
刷机流程诊断 3 信号 + 6 步
背景
Napa5 工程跑在车机上,调试一个功能往往要先让车进入某个状态:切换驾驶模式、调手势灵敏度、打开 FPS 显示。传统做法是改代码加临时入口、重新出包、装车验证,一轮下来半小时没了。另一边,台架刷机涉及 CAN 诊断和高通 9008 端口,步骤错一步就卡半天,新人基本要人带着刷过一次才敢上手。
问题
- 调试入口成本高:每加一个调试开关都要动 UI 或改代码,验证类工作被出包周期卡住。
- adb 与 Unity 之间没有通用通路:adb 指令能到 Android 层,但 Android 到 Unity 没有业务解耦的透传机制。
- 刷机步骤碎片化:CanStudio 要发哪几个诊断信号、XPFlash 要选哪个端口和哪类 ROM 包,全靠口口相传,刷坏一台台架就耽误半天测试。
方案
AdbForNapa:adb 字符串透传协议
核心思路一句话:把 adb 命令转化成 Napa5 工程内可接收的数据信号。入口只有一条广播:
bash
adb shell am broadcast -a com.xiaopeng.subreality.adbdebug \
-e type 'adbStrToUnity' -e adbStrToUnityValue "xxxxxx"Android 收到后把 "xxxxxx" 原样透传给 Unity。为了业务解耦,字符串格式约定为 EAdbMessage枚举:内容,比如 DebugAttribute:性能:fps显示——第一个冒号之前解析为 EAdbMessage 枚举类型,Unity 解析成功会打印 [ProtoMessage]AdbForNapa 日志。工程内监听 MSG_ID_RECEIVE.DEBUG_ADB_JSON_TO_UNITY 信号,事件带两个参数:EAdbMessage id 和 string content。
mermaid
sequenceDiagram
participant PC as 电脑端脚本
participant AD as Android 层
participant UT as Unity Napa5
PC->>AD: adb broadcast adbdebug<br>携带调试字符串
AD->>UT: 原样透传字符串
UT->>UT: 首个冒号前解析为<br>EAdbMessage 枚举
UT->>UT: 广播 DEBUG 事件<br>"id + content"
UT-->>PC: 日志 "[ProtoMessage]AdbForNapa"无参指令:一个标签生成三端入口
无参调试指令的定义成本低到一行代码:给公开静态方法加 [DebugFunction] 标签即可。
csharp
[DebugFunction("标准", "驾驶模式")]
public static void TestDriveModeStandard()
{
ObjEvent.Broadcast<int>(null, (int)ESysEvent.ISYS_DRIVVE_MODE, 0);
}标签加完后有三个消费入口,共享同一份指令清单:
- Python 脚本:双击
Napa5/Tools/AdbForNapa/DebugAttributeCmd.py,列出全部指令,输入序号即触发对应函数。 - 编辑器窗口:
Tools > Debug > Open DebugMenuWindow打开 DebugMenuWindow,支持筛选、按分类展开,在编辑器内直接模拟。 - 指令清单生成:脚本读的是
DebugAttributeMenu.csv,新增标签后若列表里看不到,用编辑器菜单Tools > Debug > Generate DebugAttributeMenu file一键重新生成 csv。
有参指令:业务监听一个事件
有参指令走另一条路:业务模块监听 ESysEvent.ISYS_DEBUG_BY_ADB 事件,第一个参数 id 是业务类型(一个监听类一般只处理一个业务类型),第二个参数 content 是数据内容。电脑端双击 adb4napa.bat,输入:
GestureCamera:PitchToYawSensitive:-0.8就会触发 NoticeAdbMessage_GestureCamera(EAdbMessage.GestureCamera, "PitchToYawSensitive:-0.8"),手势相机的俯仰/偏航灵敏度在车上实时生效。编辑器内对应 Tools > Debug > Open AdbForNapa 的 AdbSimulateWindow,可以离线模拟同样的输入。
刷机:CanStudio 诊断 + XPFlash 两段式
刷机分前后两段:先用 CanStudio 通过 CAN 诊断把设备置到可刷状态,再用 XPFlash 走 9008 端口灌 ROM。
mermaid
flowchart LR
A["CanStudio 配置<br>Device/DevIndex/Channel"] --> B["Connect 连接设备"]
B --> C["诊断信号<br>1003 → 2701 → 31 01 C1 FF 00"]
C --> D{"全部 Positive?"}
D -->|否| A
D -->|是| E["XPFlash<br>Close MCU + Reboot EDL"]
E --> F["选 9008 端口<br>elf + rawprogramALL.xml"]
F --> G["Download 到 100%<br>等待自动重启"]CanStudio 侧的关键点:装好 Canlib CAN 总线驱动;Configure 里选对 Device、DevIndex、UseChannel(连不上就换参数重试);Diagnostic 窗口顺序发 1003、2701(有回复之后再点一次)、31 01 C1 FF 00,必须全部 Positive 回复才能进 XPFlash 阶段,否则回到配置步骤重来。
关键实现
XPFlash 的 ROM 包选型是最大的坑。 从内部 ROM 仓库下载时必须选 UserDev/UserDebug 的包,不要下 Release 和 Alpha 包——刷错类型的包,调试功能直接不可用。QFIL 包一般 8-9 GB,解压后用 XPFlash 选 prog_firehose_ddr.elf 和 rawprogramALL.xml(xml 选 ALL)。
9008 端口是第二大的坑。 流程是 Help 菜单 Install Driver 装驱动 → Tools 菜单 Close MCU Monitor → Reboot to EDL → Select Port 选 Qualcomm HS-USB QDLoader 9008 端口 → Download 等到 100%。设备管理器里没有 9008 端口就是驱动没装上,重装驱动再来。
刷完不等于完事。 等台架自动重启、勾选协议进大屏后,台架还要补一组收尾:adb root、adb remount、adb reboot,然后 push 回放文件,台架才回到可测试状态。新刷的 ROM 可能出现地图未激活提示,需要网络环境等待自动激活。刷机后上车路测的数据采集,按路测与台架回放运营体系里的手顺执行。
协议设计上的取舍。 AdbForNapa 没有做成通用 RPC,而是刻意用「枚举 + 字符串」的窄协议:第一个冒号前必须是 EAdbMessage 枚举,Android 层只透传不解析。这样 Android 侧零业务代码,新增调试能力只需要 Unity 侧加标签或加监听,链路本身永远不用动。这和信号链路的白名单思路一致——出口窄一点,误外发的面就小一点,见 Native-Unity 信号链路。
指标
| 指标 | 说明 |
|---|---|
| 调试指令注入成本 | 加一个 [DebugFunction] 标签,链路代码零改动 |
| 指令消费入口 | Python 脚本、编辑器 DebugMenuWindow、adb 直发三端同一份 csv |
| 有参指令 | 业务监听 ISYS_DEBUG_BY_ADB,id + content 两参数 |
| CanStudio 诊断 | 3 条信号顺序发送,全部 Positive 才放行 |
| XPFlash 刷机 | 6 步固化,9008 端口 + UserDev/UserDebug 包两条铁律 |
复盘
- 调试工具的第一要务是消灭出包周期。驾驶模式、手势灵敏度、FPS 显示这些注入能力全部免改代码之后,验证类工作的瓶颈从「等构建」变回「看现象」。
- 窄协议比通用协议活得久。
EAdbMessage:content这种刻意受限的格式,让 Android 透传层一年不用动;做成通用 RPC 反而每加一个业务都要审一遍链路。 - 刷机手顺的价值在「全部 Positive 否则重来」这种判断句。步骤谁都会抄,什么时候该停下来回退,才是手顺真正要沉淀的东西。
- 工具链和监控是配套的:车上调试注入的状态,最终要靠监控上报链路确认没有在稳定性上留下副作用。