首页
/ openpilot 的 juggle.py 详解:用 PlotJuggler 可视化行驶日志、解析 CAN 与实时流数据

openpilot 的 juggle.py 详解:用 PlotJuggler 可视化行驶日志、解析 CAN 与实时流数据

2026-09-04 22:48:52作者:仰钰奇

openpilot 通过 tools/plotjuggler/ 目录下的辅助脚本 juggle.py,把 PlotJuggler(一个通用的时间序列可视化工具)与 openpilot 的 cereal 日志格式打通:自动下载 PlotJuggler 及其 openpilot 专用插件、解析路由/段日志、推断车型 DBC 以解码 CAN 信号,并支持从 comma 设备实时串流数据。读完本文,你将掌握 juggle.py 的全部命令行参数、14 个预置布局(Layout)的用途与结构,以及从 LogReader.rlog 再到 PlotJuggler 插件的完整源码调用链,能够独立完成从"回放一次行驶记录"到"从车机实时观察控制量"的完整调试工作流。

PlotJuggler 在 openpilot 中的定位

openpilot 的日志是 Cap'n Proto 序列化、经 zstd 压缩的 cereal 消息流(rlog/qlog),直接读取并不直观。juggle.py 解决的核心问题是:把这些日志转换成 PlotJuggler 能加载的时间序列视图。comma 团队为 PlotJuggler 编写了两个关键插件(随 --install 一起安装到 openpilot/tools/plotjuggler/bin/ 插件目录):

  • DataLoad Rlog:加载并解析 openpilot 的 rlog 文件;
  • Cereal Subscriber:以 ZeroMQ 订阅 cereal 消息总线,实现实时流式绘图。

这两个插件 ID 直接写死在仓库自带的每个布局文件中,例如 tuning.xml 末尾的:

<Plugins>
  <plugin ID="DataLoad Rlog"/>
  <plugin ID="Cereal Subscriber"/>
</Plugins>

因此布局文件与本地安装的插件是配套关系——这也是 --install 需要同时安装 PlotJuggler 和插件的原因。

安装:下载 PlotJuggler 与插件

配置好 openpilot 开发环境 之后,执行:

cd openpilot/tools/plotjuggler && ./juggle.py --install

juggle.pyinstall() 实现(L43-L61)可以看到安装细节:

  1. platform.system() + platform.machine() 组合出平台标识,仅支持 Linux-x86_64Linux-aarch64Darwin-arm64 三种平台,其他平台直接抛异常;
  2. 删除并重建 openpilot/tools/plotjuggler/bin/ 安装目录;
  3. 从 commaai/PlotJuggler 的 releases/download/latest/<平台>.tar.gz 流式下载(1MB 分块),解包到 bin/ 目录。

脚本还有两条"自愈"逻辑,保证日常无需手动维护版本:

  • bin/plotjuggler 不存在,自动提示并安装(L158-L160);
  • 若已安装版本低于 MINIMUM_PLOTJUGGLER_VERSION = (3, 5, 2),通过执行 plotjuggler -v 解析版本号后自动更新(L162-L164、L64-L67)。

命令行参数全解

./juggle.py -h 的输出(README 中的快照)与实际 argparse 定义一致,此外当前源码中还有一个 README 帮助文本未列出的 --no-migration 选项。以 juggle.py 的参数定义 为准,完整参数如下:

参数 类型 说明
route_or_segment_name 位置参数(可选) 要绘制的路由或段名称,接受 cabana 分享 URL;不提供且不指定 --demo 时无法绘图
--demo 开关 使用内置演示路由 5beb9b58bd12b691/0000010a--a51155e496(即源码常量 DEMO_ROUTE,L23)
--can 开关 解析 CAN 数据(默认过滤掉 can/sendcan 消息)
--stream 开关 以流式模式启动 PlotJuggler(不加载历史数据文件)
--layout [LAYOUT] 可选值 使用预定义布局文件启动,如 --layout layouts/tuning.xml
--install 开关 安装或更新 PlotJuggler + 插件(执行完即退出)
--dbc DBC 指定解析 CAN 数据所用的 DBC 文件名;不指定时从日志自动推断
--no-migration 开关 跳过旧版日志字段迁移(源码 L138,README 帮助文本未列出)

另外注意:不带任何参数直接运行 ./juggle.py 时,会先打印一段紫色横幅(指向后继工具 JotPluggler,见文末),再打印帮助并退出(L144-L148)。

路由与段的命名约定

README 给出了三类典型用法(5beb9b58bd12b691 为设备名,0000010a--a51155e496 为路由 ID,两者均沿用演示路由):

# 整个路由(跨所有段合并绘制)
./juggle.py "5beb9b58bd12b691/0000010a--a51155e496"

# 单个段
./juggle.py "5beb9b58bd12b691/0000010a--a51155e496/1"

# 单个段,使用 qlog(调试日志)而非 rlog
./juggle.py "5beb9b58bd12b691/0000010a--a51155e496/1/q"

# 段范围
./juggle.py "5beb9b58bd12b691/0000010a--a51155e496/0:1"

源码流程:从路由名到绘图窗口

juggle_route()juggle.py L108-L128)串起了完整流程,值得逐步拆解:

  1. 加载日志LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACTIVE)AUTO_INTERACTIVE 模式定义在 logreader.py 中,语义是"优先读取 rlog,缺失时与用户交互确认后回退到 qlog",所以传入 /1/q 这样的后缀即可显式指定 qlog。
  2. 多进程聚合lr.run_across_segments(24, partial(process, can)) 用最多 24 个进程跨段并行处理。process()(L104-L105)的过滤规则是:不带 --can 时丢弃 cansendcan 以及所有 customReserved* 开头的消息——CAN 原始报文体积大且默认无意义,故被排除;带 --can 时全部保留。
  3. 日志迁移:除非指定 --no-migration,调用 migration.py 中的 migrate_all() 把旧版字段名重映射为当前版本,保证旧路由也能用新版布局打开。
  4. DBC 自动推断(见下节)。
  5. 落盘临时 rlogsave_log(tmp.name, all_data, compress=False) 把聚合后的消息写入 openpilot/tools/plotjuggler/ 下以 .rlog 为后缀的临时文件(解压状态),供 DataLoad Rlog 插件加载;随后 start_juggler() 启动图形界面。

start_juggler()L70-L101)在启动前做了一件容易忽略但很关键的事:在临时目录中拼出 PlotJuggler 插件所需的 Cap'n Proto 模式文件——复制 log.capnpdeprecated.capnpcustom.capnp 并重写其中的 import 路径(/include/c++.capnp./include/c++.capnp/car.capnpcar.capnp),再符号链接 cereal 的 include/ 目录与 opendbc_repo/opendbc,最后把该临时目录设为插件的 BASEDIR 环境变量。这使插件能在不依赖完整构建产物的情况下正确反序列化消息。最终执行的命令形如:

bin/plotjuggler --buffer_size 1000 --plugin_folders <bin> -d <临时rlog> -l <布局文件> --window_title "<路由名> (<车型平台>)"

其中 --buffer_size 1000 来自常量 MAX_STREAMING_BUFFER_SIZE--window_title 会附带推断出的车型平台名,便于多窗口调试时区分。

解析 CAN 数据:--can 与 DBC

加上 --can 后,CAN 原始报文会进入绘图数据源,但要把报文解码成有意义的信号,还需要 DBC 文件。juggle_route() 中的推断逻辑(L116-L123)为:

  • 若未显式 --dbc,取日志中第一条 carParams 消息,用 CP.carFingerprintMIGRATION 表(来自 opendbc 子模块的 opendbc.car.fingerprints)映射出车型平台;
  • 再通过 openpilot.tools.cabana.dbc.generate_dbc_json 中的 generate_dbc_dict() 查该平台对应的 DBC 名,并以环境变量 DBC_NAME 传给 PlotJuggler(L73-L76),由 CAN 解析插件据此解码。

因此通常无需手动指定 DBC;当路由中缺少 carParams(如纯 CAN 录制)或推断失败时(此时会打印 "Failed to get DBC name from logs!"),才需要用 --dbc 显式给出 DBC 文件名或路径(本地路径会被转换为绝对路径)。

实时流式数据:--stream

--stream 分支跳过一切日志加载,直接 start_juggler(layout=args.layout) 启动 PlotJuggler,在 Streaming 下拉菜单中选择 Cereal Subscriber 插件并点击 Start,即可订阅本机 cereal 总线实时绘图(L166-L167)。数据源有两种:

从 comma 设备串流到笔记本

  1. 在 comma 设备上开启 Wi-Fi 热点(tethering);
  2. SSH 进入设备后执行 cd /data/openpilot && ./openpilot/cereal/messaging/bridge——该 bridge 二进制由 bridge.cc 构建,作用是把设备本地的共享内存消息总线桥接为 ZeroMQ 对外发布;
  3. 笔记本连接该热点;
  4. 执行 ZMQ=1 ./juggle.py --stream,找到 Cereal Subscriber 插件并点击 StartZMQ=1 环境变量告诉消息层走 ZeroMQ 传输。

从本地回放串流

如果在 PC 上用 replay 工具回放路由,直接运行 ./juggle.py --stream 并启动 cereal subscriber 即可——replay 进程发布的本地消息同样会被订阅到。

快速演示:--demo

完成安装后,最快看到效果的方式是:

./juggle.py --demo --layout=layouts/tuning.xml

这会下载/复用内置演示路由 DEMO_ROUTE,加载调参布局打开 PlotJuggler。仓库的自动化测试 test_plotjuggler.py 也以 "{DEMO_ROUTE}/:2"(前 3 个段)为输入验证整条链路:在无显示环境下以 QT_QPA_PLATFORM=offscreen 启动 juggle.py,等待 stderr 出现插件打印的 Done reading Rlog data(180 秒超时),再确认进程没有崩溃、输出中不含 Raw file read failed。该测试依赖 Qt(qmake),未安装时自动跳过。

Layouts:14 个预置布局及其结构

openpilot/tools/plotjuggler/layouts/ 目录内置了 14 个开箱即用的布局,README 鼓励社区把自己的有用布局上游化。各布局的适用场景可从文件名与内容对应:

布局文件 典型用途
tuning.xml 横向/纵向整体调参,生成调参 PR 所需图表
longitudinal.xml 纵向控制:aEgo、MPC 加速度、执行器输出、油门状态
torque-controller.xml 转向扭矩控制器深入调试(仓库中最大的布局,249 行)
max-torque-debug.xml 最大扭矩/限幅问题排查
CAN-bus-debug.xml 三条 CAN 总线的 RX/TX/错误计数(对累计值做 Derivative 变换得到速率)
can-states.xml CAN 总线状态
controls_mismatch_debug.xml controls 输出不一致排查
locationd_debug.xml 定位模块(locationd)调试
gps.xml / gps_vs_llk.xml / ublox-debug.xml GNSS 信号、GPS 与里程计对比、u-blox 调试
camera-timings.xml 相机帧时序
system_lag_debug.xml 系统时延/卡顿排查
thermal_debug.xml 热状态排查

以 tuning.xml 为例拆解布局文件

--layout layouts/tuning.xml 为例,布局文件是一个 PlotJuggler 的 XML 工程,包含三大块:

  1. tabbed_widget:主窗口标签页结构。tuning 布局含 LateralLongitudinalLateral Debug 三个 Tab,每个 Tab 内用 DockSplitter 把若干 DockArea 均分,每个 DockArea 是一个 TimeSeries 折线图,<curve> 直接引用 cereal 字段路径,例如 /carState/vEgo/carControl/orientationNED/0(横滚角)、/controlsState/lateralControlState/pidState/saturated(横向 PID 饱和标志)等。
  2. customMathEquations:自定义数学公式区。tuning 布局中的每个公式片段(snippet)都实现了统一的"engage_delay = 5 秒"逻辑:当驾驶员干预(steeringPressed)或系统未接管(enabled == 0)时记录 last_bad_time,只有距最后一次干预超过 5 秒的时段才返回真实曲率/加速度,否则返回 0。这样调参图表只反映"纯 openpilot 控制"的片段,排除人为干扰。例如 engaged curvature plan/modelV2/action/desiredCurvatureengaged_accel_plan/longitudinalPlan/accels/0 并在 brakePressed/gasPressed 非零时置零。
  3. Plugins:声明该布局依赖 DataLoad RlogCereal Subscriber 两个插件。

测试代码 test_layouts 还规定了一个上游约束:布局文件中不允许残留 fileInfopreviouslyLoaded_Datafiles 字段——因为 PlotJuggler 在加载引用了先前数据文件的布局时会告警,这些内容必须在提交前剔除。

与 JotPluggler 的过渡关系

需要说明的一点是:当前仓库的 juggle.py 每次运行时都会打印横幅,提示 "JotPluggler is the future" 与 "PlotJuggler will be deleted soon"(juggle.py L31-L40),并给出等价命令 ./openpilot/tools/jotpluggler/jotpluggler --demo --layout tuning。从源码结构看,openpilot/tools/jotpluggler/ 是一套 C++ 实现的新版可视化工具,支持相同的 demo 与 layout 概念;本文所述的 PlotJuggler 工作流在过渡期内仍然完整可用,且其日志解析(LogReader、迁移、DBC 推断)逻辑是理解 openpilot 日志生态的良好切入点。

速查小结

  • 安装/更新:cd openpilot/tools/plotjuggler && ./juggle.py --install(支持 Linux x86_64/aarch64 与 macOS arm64,自动校验最低版本 3.5.2);
  • 回放绘图:./juggle.py "<设备名>/<路由ID>",支持 /段号/段号/q/起:止 后缀,接受 cabana 分享 URL;
  • CAN 调试:--can [--dbc <名>],DBC 默认由首条 carParams 自动推断;
  • 实时流:设备侧跑 cereal/messaging/bridge + 笔记本 ZMQ=1 ./juggle.py --stream,或本地 replay + ./juggle.py --stream,均通过 Cereal Subscriber 插件订阅;
  • 快速体验:./juggle.py --demo --layout=layouts/tuning.xml
  • 核心源码入口:juggle.py、布局目录 layouts/、回归测试 test_plotjuggler.py、日志读取层 tools/lib/logreader.py
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384