标签: 开发工具

  • 四个开发工具的信任边界:回放导出与 CarParams 注入

    结论:四个开发工具的信任等级并不相同:print_flags.py 只读枚举,test_fw_query_on_routes.py 只读已记录固件,run_process_on_route.py 会把新旧消息合并导出,而 set_car_params.py 会写入当前参数库。前两者可分析,后两者带副作用,其中最危险的是把 CarParams 写入下放给任意插件。

    工具清单与副作用等级

    工具 源码链路 静态结论
    test_fw_query_on_routes.py 参数可为单一路段或文件路径;文件分支取每行再交 SegmentRange,未指定段则拼 /0,随后按 QLOG 读 carParams 并分别做精确/模糊固件匹配。 原主入口探针确认列表行尾换行未剥离,正则在内部 try 前抛断言并中断整批;读取器构造失败也在 try 前传播。脚本只读已记录固件,不向 ECU 发查询。
    批量统计与不匹配输出 以 dongle_id 去重,在读路段前就把它加入集合;对不匹配固件按车型、地址去重汇总,最终打印精确/未识别/模糊计数。 假消息探针确认首个路段没有 carParams 时,同设备第二路段仍被跳过;不支持的 panda 类型会提前结束,随后又报告无 CarParams。输出含 VIN 与固件字节,需视为敏感诊断数据。
    run_process_on_route.py 默认从 CONFIGS 选全部进程,再减去被排除的两个模型进程;读完整路段调用回放,只按实际产生的消息类型删除旧输入,再将新旧消息按时间合并并保存。 探针确认零回放输出会保留并重排全部旧消息,未知白名单会选出空配置但仍读取和导出原日志。文件名进程部分来自集合迭代顺序,不是稳定标识。导出不调用车型日志净化器,副本可能携带原始 CAN/VIN。
    set_car_params.py 有路段时只读第一条 qlog 中第一条 carParams;无参数时新建空白消息,只写两个纵向标志;随后为每个键新建 Params(),按三个键顺序阻塞写入。 探针确认缺少 carParams 时在任何写入前抛 IndexError;第二键抛异常时第一键已调用成功、第三键未调用。原 Python Params.put() 会丢弃底层负错误码,故底层写盘失败可能不抛异常。本层无回滚或读回,不校验平台、VIN、safety 或当前车辆身份。
    print_flags.py 遍历 BRANDS 与各平台配置标志,按标志列出车型名称。 Platforms 与归档的 CAR 枚举均未定义脚本调用的 with_flags();存在非零标志配置,非零假枚举时原主体抛 AttributeError,零标志枚举则无输出。

    run_process_on_route:替换与导出的边界

    该脚本默认从 CONFIGS 选全部进程,再减去被排除的两个模型进程,读完整路段后调用 replay_process(),只按本轮实际产生的消息类型删除旧输入,再把新旧消息按 logMonoTime 合并并 save_log()。

    假依赖主入口探针确认:零回放输出会保留并重排全部旧消息;未知白名单会选出空配置但仍读取和导出原日志。另外,文件名中进程部分来自集合迭代顺序,不是稳定标识;save_log() 不调用车型日志净化器,副本可能携带原始 CAN/VIN。因此不能以脚本名称替代对进程副作用的审查。

    回放导出还有一个隐含前提:所选进程必须在当前代码版本下可复现,否则混合出的日志既不等于原始输入,也不等于一次真实回放;这正是需要把代码版本与被替换主题一并写入导出元数据的原因。

    test_fw_query_on_routes:批量审计的失败面

    该脚本的参数可为单一路段或文件路径;文件分支用 list(open(...)) 直接取每行,再交 SegmentRange;未指定段则拼 /0,随后按 QLOG 读取 carParams 并分别做精确/模糊固件匹配。探针确认列表行尾换行未剥离,正则在内部 try 前抛断言并中断整批;读取器构造失败同样在 try 前传播。

    统计侧以 dongle_id 去重,且在读路段之前就把该键加入集合;对不匹配固件按车型、地址去重汇总,最终打印精确、未识别与模糊匹配计数。需要提醒的是:首个路段没有 carParams 时,同设备第二路段仍被跳过;不支持的 panda 类型会提前结束,随后又报告无 CarParams。这些输出包含 VIN 与固件字节,应视为敏感诊断数据。

    set_car_params:三键写入无补偿

    有路段时,脚本只读取第一条 qlog 中第一条 carParams;无参数时新建空白消息,只把 openpilotLongitudinalControl=True、alphaLongitudinalAvailable=False 写入。随后为每个键新建 Params(),按 CarParams、CarParamsCache、CarParamsPersistent 的顺序阻塞写入。

    假 Params 探针确认:缺少 carParams 时在任何写入前抛 IndexError;第二键抛异常时第一键已调用成功、第三键未调用。但原 Python Params.put() 会丢弃底层负错误码,故底层写盘失败可能不抛异常,脚本仍继续写第三键。本层没有回滚或读回,也不校验平台、VIN、safety 或当前车辆身份。另据键属性,前两者会被 manager 启动清理,CarParamsPersistent 持久化。

    键 写入时机 生命周期
    CarParams 三键顺序写入的第一键 被 manager 启动清理。
    CarParamsCache 第二键 被 manager 启动清理。
    CarParamsPersistent 第三键 持久化。

    插件化:写入权归谁

    print_flags.py 的只读枚举也提示了一件事:即便修复了缺少 with_flags() 的问题,它仍只枚举本地声明,不证明硬件或 safety 已启用。实际车型能力仍须从车型接口、DBC 与 panda safety 交叉验证。这也说明开发工具的结论天然是局部的:它们验证的是脚本自身的控制流,而不是目标车辆的参数正确性。

    探针 覆盖的契约
    标志枚举探针 核对 Platforms 与各品牌方法清单,确认缺少 with_flags() 的异常与零输出分支。
    参数注入主入口探针 验证默认两个标志、三键写入顺序、只取首个 carParams,以及第二键异常后不触发第三键。
    固件审计主入口探针 验证行尾换行、读取器构造异常、同设备去重及不支持 panda 后的状态报告。
    回放导出主入口探针 确认剔除被排除的模型进程、只替换实际产生的类型、零输出仍导出全部旧消息。

    批量固件审计应先把路段列表解析、去重与错误计数做成显式结果;精确/模糊候选只能用于平台审核,不自动修改固件库。回放导出的日志需携带输入路段、所选进程、代码版本、被替换主题、生成主题和隐私标记,避免把混合旧/新消息误当统一来源。CarParams 的写入应归受控车辆身份服务所有,要求平台/线束/DBC/safety 绑定、三键一致性门和读回验证,不能下放给任意插件。

    常见问题

    为什么 set_car_params.py 不能在车端随意试跑?

    因为它会写入当前参数库:按 CarParams、CarParamsCache、CarParamsPersistent 三键顺序阻塞写入,且本层没有回滚或读回、不校验平台/VIN/safety/车辆身份。底层写盘失败还可能因负错误码被丢弃而不抛异常。

    回放导出会混入旧消息吗?

    会。探针确认零回放输出时,全部旧消息仍会进入导出并按时间排序;脚本只按本轮实际产生的消息类型删除旧输入。导出还不调用车型日志净化器,副本可能携带原始 CAN/VIN。

    为什么固件审计脚本可能整批中断?

    因为文件分支取每行时行尾换行未剥离,SegmentRange 的正则在内部 try 之前就抛断言并中断整批;读取器构造失败也在 try 前传播,同样会中断。

    print_flags.py 能证明某车型能力已启用吗?

    不能。它只遍历本地品牌与平台配置来列出车型名称,且归档中并未定义它调用的 with_flags();即使修复,它仍只枚举本地声明,不证明硬件或 safety 已启用。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/DEV_FW_REPLAY_CARPARAMS_TOOLS_TRUST_BOUNDARY_DEEP_DIVE.md(取用了四个开发工具的入口、数据流与失败边界及探针结论)。
    • 项目 02 —— openpilot-research/PROCESS_REPLAY_VALIDATION_DEEP_DIVE.md(仅用于确认回放进程与验证的边界)。

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

    更新记录

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

  • 开发日志工具的三个权限等级:转储、音频导出与状态刺激

    结论:四个开发脚本的权限差异很大,不应统称「开发插件」。dump.py 与 filter_log_message.py 主要读取消息/日志,extract_audio.py 会导出含乘员信息的音频,而 cycle_alerts.py 会主动发布核心状态主题(selfdriveState、deviceState、pandaStates)。至少两个反直觉的事实:--json 对 Cap’n Proto 原始字节调用 json.loads 通常无法解析,真正转 JSON 的是 --dump-json;cycle_alerts.py 先声明九个告警却随即重赋值为只剩两项,实际只循环 cameraMalfunction 与 cameraFrameRate。远端 --addr 不能当作权限、脱敏或租户隔离策略。

    四个脚本,三种权限等级

    把数据流和副作用放在一起看,权限差异一目了然:前三个工具的输出主要是「读出来的内容」,第四个则会争用发布入口。

    工具 数据流 实际输出或副作用
    dump.py raw_live_logreader() 订阅指定地址主题,默认为 SERVICE_LIST 全部服务;每条原始字节先按 log.Event 解码 可打印 Event、十六进制或字段;--addr 非环回时底层切换 ZMQ 并重置消息上下文
    filter_log_message.py 有路段参数时用排序后的 LogReader 查 logMessage、errorLogMessage、operatingSystemLog;无路段时实时订阅前者与 OS 日志 打印 JSON 结构化日志或 OS 日志;实时模式未订阅 errorLogMessage
    extract_audio.py ReadMode.AUTO_INTERACTIVE 选路段来源,LogReader.filter("rawAudioData") 得载荷,拼成 int16 样本 经 write_wav_file() 导出单声道 WAV,或经 play_audio() 本机播放
    cycle_alerts.py 构造 Honda 参数与随机化 SubMaster 状态,用 Events.create_alerts()、AlertManager 生成告警 每 200 个控制周期发布 selfdriveState、deviceState、pandaStates,循环不停

    dump.py 与 filter_log_message.py 的失效点

    dump.py 即使指定 --no-print 仍逐条解析 Event;--pipe 用 str(msg) 写标准输出,得到的是 Python 字节表示文本而不是原始二进制流;--json 对原始字节调用 json.loads 通常失败。--values 只检查路径第一段存在,后续 getattr 缺失可直接抛错。顶层 codecs.register_error("strict", ...) 还会影响进程内使用该错误处理器的解码行为,插件封装时不应默许这种全局修改。

    filter_log_message.py 的坑集中在时间与字段两处。等级过滤只作用于结构化日志,不影响 OS 日志;离线非 --absolute 的时间从第一条读取到的消息起算,而不是第一条匹配日志,且多个路段共享该时间原点、段间不重排序;实时模式则用原始 logMonoTime 纳秒换算秒数,两者语义不同。print_logmessage() 只捕获 JSON 解析异常,若 JSON 缺 levelnum、filename 或 msg 会抛键错误。

    工具 易踩边界 原因
    dump.py --json 通常解析失败 对 Cap’n Proto 原始字节调用 json.loads;转换应使用 --dump-json
    dump.py 解码行为被全局改写 顶层注册了 "strict" 错误处理器
    filter_log_message.py 相对时间原点不是首条匹配日志 离线时间从第一条读取到的消息起算,多路段共享
    filter_log_message.py 实时模式拿不到错误日志 实时订阅未包含 errorLogMessage

    两个工具都会打印原始事件或日志内容,可能包含标识符、位置、进程路径及异常栈,因此远端 --addr 不能作为生产环境的权限、脱敏或租户隔离策略。

    extract_audio.py:WAV 连续性与隐私

    extract_audio.py 把所有 rawAudioData.data 按 NumPy 原生端序的 int16 视为连续样本,再原样写成 WAV,未显式转换字节序。它只取第一条的 sampleRate,不检查之后的采样率变化、通道数、消息时间戳或缺包间隔,因此导出文件不承诺与原车时间轴严格连续。它先把所有消息和样本拼成内存数组,长路段可能占用较大内存。

    隐私侧还有一个关键前提:rawAudioData 缺省 should_log=False,loggerd 只有在启动时读取 RecordAudio 为真才订阅该主题。所以没有音频消息不等于麦克风硬件故障;而一旦录制,导出的语音可能含高度敏感的乘员信息。使用上,输出文件已存在时会交互确认,--play 会使用本机音频设备;只传 --play 或 -o 而不传路段名,主程序仍执行 args.route_or_segment_name.strip(),会报 AttributeError。

    cycle_alerts.py:发布核心状态的边界

    cycle_alerts.py 先声明九个可听告警,但随后把 alerts 重赋值为仅 cameraMalfunction 与 cameraFrameRate 两项,本快照实际只循环这两项。它随机改写 SubMaster 内的设备状态、模型速度、进程列表和主题 alive/valid/freq_ok,没有对这些订阅数据调用 sm.update(),由此产生的告警文本是随机夹具结果,不是车辆健康检查。

    发送消息由 new_message() 构造,默认 Event valid=False,脚本没有覆写。即便如此,在实际设备上争用 selfdriveState、deviceState、pandaStates 的发布入口仍会干扰 UI 和状态消费者,不能作为行车调试插件。脚本没有发送 sendcan 或直接控制 ECU,但这不等于发布核心状态没有风险。

    硬件与插件化约束

    建议把原始消息/日志检索、音频导出、状态刺激拆成三个权限等级,而不是统称「开发插件」:观测插件仅获指定主题、时间范围及字段脱敏后的只读视图;音频导出需要单独授权、留审计并限定本地文件生命周期;状态刺激仅允许仿真进程或隔离测试总线,不能发布到生产 deviceState/pandaStates。源文档明确这些是重构要求,不是当前工具已有的隔离保证。

    常见问题

    dump.py 的 –json 为什么常常解析失败?

    因为它对 Cap’n Proto 原始字节直接调用 json.loads(msg),通常无法作为 JSON 解析。源文档指出真正把 Event 转为 JSON 文本的是 --dump-json;--pipe 得到的也只是 Python 字节表示文本,不是原始二进制流。

    导出的音频能当作和原车同步的时间轴证据吗?

    不能。extract_audio.py 只取第一条的 sampleRate,不检查之后的采样率变化、通道数、消息时间戳或缺包间隔,而且是按 NumPy 原生端序把 int16 视为连续样本原样写 WAV,因此导出文件不承诺与原车时间轴严格连续。

    cycle_alerts.py 会循环几个告警?

    本快照实际只循环两个:cameraMalfunction 与 cameraFrameRate。脚本先声明九个可听告警,随后把 alerts 重赋值为这两项,且告警文本来自随机夹具结果,不是车辆健康检查。

    这些开发工具能当作「只读」吗?

    不能一概而论。前三个工具主要读取数据,但远端 --addr 不是权限或脱敏策略;cycle_alerts.py 则主动发布三个核心状态主题,会干扰 UI 与状态消费者。音频导出还涉及高度敏感的乘员信息,应单独授权与审计。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/DEV_LOG_AUDIO_ALERT_TOOL_BOUNDARIES_DEEP_DIVE.md(取用了四个开发脚本的数据流与副作用、转储与日志过滤失效点、音频导出连续性与隐私门、告警刺激的真实范围)。

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

    更新记录

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

  • 开发工具链的四种权限:摇杆、机动、消息桥与仿真各能做什么

    结论:这套开发工具链包含四种不同权限等级的入口:摇杆调试控制(替代 controlsd)、纵向机动(替代 plannerd)、横向机动(覆盖期望曲率)、以及 msgq/ZMQ 消息桥。它们都能影响 carControl 或 sendcan 路径,但配置层的「实验开关互斥」不是安全门。此外「bridge」在本项目有两个不同含义:受管进程 bridge 是 msgq/ZMQ 主题桥,tools/sim/bridge 是物理仿真与 openpilot 的适配器,二者不能混为一个控制入口。

    四类调试入口与运行条件

    摇杆输入由 joystick_control.main() 提供,车载由 process_config.py 管理;脚本要求驻车启动或在 ZMQ 模式下运行,并设置 JoystickDebugMode——但这不是安全固件许可。摇杆控制器 joystickd_thread() 在 JoystickDebugMode 或 notCar 时运行,模式启用时 controlsd 不运行;testJoystick 超过 0.2 秒未收到会把轴归零。

    纵向机动由 maneuversd.main() 在 LongitudinalManeuverMode 时运行,与 plannerd 互斥,按目标速度及纵向激活连续满足约三秒后执行预置加速度曲线——它不是一般规划器的插件实现。横向机动由 lateral_maneuversd.main() 在 LateralManeuverMode 时运行,只在目标速度、直路、低侧倾且横向激活时起动,转向/油门输入或速度偏差会重置动作。

    组件 入口与条件 输入 → 输出 关键限制
    摇杆输入 joystick_control.main() 键盘/游戏手柄 → testJoystick 要求驻车启动或 ZMQ 模式;设置调试模式不等于固件许可
    摇杆控制器 joystickd_thread() testJoystick/carState 等 → carControl 启用时 controlsd 不运行;0.2 秒超时轴归零
    纵向机动 maneuversd.main() 模型时钟/车速/carControl → longitudinalPlan 与 plannerd 互斥;约三秒条件后执行预置曲线
    横向机动 lateral_maneuversd.main() 车速/曲率/驾驶员输入 → lateralManeuverPlan 仅在目标速度、直路、低侧倾且横向激活时起动
    msgq/ZMQ bridge bridge.main() msgq 服务 ↔ ZMQ 网络主题 只传输消息,不校验驾驶控制语义
    崩溃收集 tombstoned.main() apport crash → cloudlog 摘要/日志副本 仅处理 .crash,限制 100 MB;不在实时闭环内

    开发者界面把三个实验参数设置为互斥,但这只是配置层保障;插件架构仍应在进程启动层做独立冲突检测。selfdrived 对收到的机动主题添加专用事件,如果外部插件冒充这些主题,就会影响使能和告警路径。

    关键执行路径

    Keyboard.update() 与 Joystick.update() 把人工输入变成双轴,send_thread() 以 100 Hz 发布;游戏手柄使用动态记录的轴范围、中心死区和三次曲线,重插拔异常时轴归零,而键盘输入是逐键累积、不是弹簧归中。joystickd_thread() 同样以 100 Hz 发布 carControl:纵轴经系数 4 转加速度,横轴经车辆模型转换曲率及转角,latActive 依赖 selfdriveState.active 与转向故障,longActive 还依赖纵向控制能力和覆盖事件。软件层后续仍走 card 和 panda 安全钩子。

    纵向 Maneuver.get_accel() 等待初始车速、纵向激活与驻车状态,再由 _step() 插值并推进动作/重复次数;main() 发布目标加速度、停止条件及调试文本。横向 Maneuver.get_accel() 等待约两秒稳定条件、记下基准曲率,main() 把横向加速度换算成 baseline + a/v²;Controls.state_control() 仅在该消息有效时替代模型期望曲率,随后调用 clip_curvature()。这里的「加速度」是横向试验扰动,不是纵向油门命令。

    入口 节拍/条件 语义要点
    Keyboard.update() / Joystick.update() 输入事件 键盘逐键累积;手柄动态轴范围、死区与三次曲线
    send_thread() 100 Hz 发布 testJoystick
    joystickd_thread() 100 Hz 纵轴系数 4 转加速度;横轴经车辆模型转曲率/转角
    横向 main() 稳定约两秒后 baseline + a/v² 覆盖期望曲率;未发消息则不替代

    网络消息桥与崩溃诊断

    get_services() 决定转发主题,zmq_to_msgq() 建立网络订阅到本地发布。白名单使用子串查找,不是严格集合匹配;get_port() 按主题名散列端口,源码注明可能冲突。新增跨设备插件必须显式定义主题允许集、身份鉴别和冲突处理,不能把此调试桥当生产控制 API。

    崩溃收集侧,get_tombstones() 只扫描至多 1000 个目录项,report_tombstone_apport() 提取可执行路径、信号和首个相关栈帧,清洗文件名后复制崩溃文件,main() 每五秒比较增量。报告路径适合诊断插件订阅,不能影响实时安全控制。

    仿真闭环与其证据边界

    run_bridge.create_bridge() 启动 MetaDrive 进程,SimulatorBridge._run() 创建车、传感器与各自周期线程,读取人工输入及 carControl,按使能状态决定应用人工还是 openpilot 输出。SimulatedCar.send_can_messages() 生成 Honda Civic 2022 风格 CAN,send_panda_state() 发布虚拟 pandaStates,SimulatedSensors.update() 发布 IMU、GPS、驾驶员状态和周边设备状态,send_camera_images() 送 VisionIPC 图像;launch_openpilot.sh 使用 NOBOARD=1、SIMULATION=1 和强制 Honda 指纹。

    但这条路径没有证明真实 panda 安全钩子、线束、电气故障、实际相机时序或目标车 DBC 行为。test_sim_bridge.py 的驾驶闭环测试被 unittest.skip 明确跳过,test_metadrive_bridge.py 继承该测试且还可能因未安装 MetaDrive 跳过。因此测试文件存在也不能声称当前仿真闭环通过。调试控制、纵向规划替代、横向曲率覆盖和网络消息桥分别是不同权限等级;测试插件不能将 UI 的互斥开关视为唯一安全门,仿真适配器也应保持独立于生产硬件抽象。

    常见问题

    开启了 JoystickDebugMode 是否就等于可以实车控制?

    不等于。源文档明确脚本设置 JoystickDebugMode「不是安全固件许可」,且脚本要求驻车启动或在 ZMQ 模式下运行。使能、故障和纵向覆盖状态仍参与计算,软件层后续仍走 card 和 panda 安全钩子。

    UI 里三个实验参数互斥,是否已经足够安全?

    不足。开发者界面的互斥只是配置层保障,源文档建议插件架构仍应在进程启动层做独立冲突检测。另外若外部插件冒充机动主题,会影响使能和告警路径,因此需要进程级仲裁与回放基线。

    仿真测试文件存在,是否说明仿真闭环已经通过?

    不能这样推断。test_sim_bridge.py 的驾驶闭环测试被 unittest.skip 明确跳过,test_metadrive_bridge.py 继承该测试且可能因未安装 MetaDrive 跳过。而且仿真路径没有证明真实 panda 安全钩子、线束、电气故障、实际相机时序或目标车 DBC 行为。

    msgq/ZMQ bridge 能不能当作生产控制接口?

    不能。源文档明确它「传输消息,不校验驾驶控制语义」,白名单使用子串查找而非严格集合匹配,端口按主题名散列且源码注明可能冲突。新增跨设备插件必须显式定义主题允许集、身份鉴别和冲突处理。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/DEVELOPMENT_TOOLS_DEEP_DIVE.md(取用了四类调试入口的运行条件、关键执行路径、消息桥与崩溃诊断边界、仿真闭环的证据边界)。
    • 项目 02 —— openpilot-research/PLUGIN_REFACTOR.md(仅用于确认调试工具在插件化中的权限分级约束)。

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

    更新记录

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