CANviz 使用指南|Linux 安装、DBC 解码、实时曲线与 J1939 实战

1. 为什么需要 CANviz

刚接上设备时,用 candump 抓到一条报文 123#112233603B000000,知道第 4、5 个字节是转速,手算一次并不难。同一个 ID 如果每秒发几十次,想看转速在哪一刻掉下去、掉了多久,就得把这些字节按时间排好再看。接下来如果要盯住几个 ID、看信号随工况怎么变,浏览器里的表格和曲线更顺手。下面在虚拟 CAN 上走一遍这个过程,把 60 3B 解成 1900 rpm,再录一段转速变化。

调试时可以两个窗口并开。网页看趋势,终端留逐帧记录。比如转速曲线多了一个尖峰,翻回同一时刻的原始字节,就能判断是设备真的报了那个数,还是解码时遇到了状态值。

2. CANviz 能做什么

CANviz 是开源的 Web CAN 分析工具。它按 ID 汇总收到的帧,可以过滤报文、查看载荷和计数。导入 DBC 后,同一条报文能展开成带单位的信号,也能添加到实时曲线。工具还提供日志录制、原始帧回放和 J1939 查看功能。下文使用 CANviz 0.3.1 。 项目主页 · 下载页

Linux 上的数据先到 SocketCAN 接口,再由 CANviz 接收并推送给浏览器。物理 CAN 接口和 vcan0 都可以做输入,浏览器只需能访问 CANviz 服务。

3. 在 Linux 上安装 CANviz

准备支持 SocketCAN 的 Linux 和 Python 3.10 或更高版本 。做下面的模拟实验还需要内核支持 vcan,并安装 can-utils。先用 python3 -V 看版本。Ubuntu 或 Debian 上如果缺少 venv 组件或 CAN 工具,可以用 sudo apt install python3-venv can-utils 安装对应软件包。

临时调试用 venv 很方便。Python 依赖集中在本次目录,收工时可以一起清理。下面用 /userdata/testapp/canviz-test 作为示例 BASE。普通账号没有写权限时,把第一行换成自己的可写目录,例如 BASE="$HOME/canviz-test"。

创建环境

在同一个 Bash 终端执行,记下打印出来的 RUN。每次安装会得到一个新目录,日志和临时文件也放在里面。

bash 复制代码
BASE="/userdata/testapp/canviz-test"
mkdir -p -- "$BASE/runs" || exit 1
BASE=$(realpath -e -- "$BASE") || exit 1
RUN=$(mktemp -d -- "$BASE/runs/canviz.XXXXXXXX") || exit 1
printf 'RUN=%s\n' "$RUN"
mkdir -- "$RUN/tmp" "$RUN/replay-uploads" "$RUN/logs" || exit 1

python3 -m venv "$RUN/.venv" || exit 1
. "$RUN/.venv/bin/activate" || exit 1
TMPDIR="$RUN/tmp" "$RUN/.venv/bin/python" -m pip install \
  --no-cache-dir 'canviz==0.3.1' || exit 1
"$RUN/.venv/bin/python" -m pip check
"$RUN/.venv/bin/python" -m pip show canviz

pip check 应显示依赖没有冲突,pip show 中的版本为 0.3.1。安装用了 --no-cache-dir,这次下载的包不会留在共享 pip 缓存里。

准备接口并打开网页

先用虚拟接口跑一遍。下面只在缺少 vcan0 时创建它,并把本轮目录名写进接口 alias,方便结束时认领。已有的 vcan0 要确认是 UP 的虚拟接口,且当前可供本次实验使用。如果提示不支持 vcan,可尝试 sudo modprobe vcan,再检查内核是否提供该模块。

bash 复制代码
if ! ip link show dev vcan0 >/dev/null 2>&1; then
  sudo ip link add dev vcan0 type vcan &&
  sudo ip link set dev vcan0 alias "${RUN##*/}" &&
  sudo ip link set dev vcan0 up || exit 1
fi
[[ -n $(ip -o -d link show dev vcan0 up type vcan) ]] || exit 1

在该终端前台启动 CANviz。服务工作目录设为 RUN,录制文件会写到它的 logs/。

bash 复制代码
cd "$RUN" || exit 1
TMPDIR="$RUN/tmp" canviz_UPLOAD_DIR="$RUN/replay-uploads" \
  "$RUN/.venv/bin/canviz" serve \
  --interface socketcan --channel vcan0 \
  --host 127.0.0.1 --port 8080 --headless

同机打开 http://127.0.0.1:8080/。--headless 只跳过自动打开浏览器。等启动终端显示 Auto-connected 后,在第二个终端运行 curl -fsS http://127.0.0.1:8080/status,确认 connected 为 true,channel 为 vcan0。

在远程 Linux 上运行时,保持服务监听回环地址,从电脑走已授权的 SSH 转发。把命令中的用户和主机名换成实际值,连接后打开 http://127.0.0.1:18080/。

bash 复制代码
ssh -N -L 127.0.0.1:18080:127.0.0.1:8080 user@host

这套 venv 安装方法也适合具备相应 Python 环境、CAN 接口和驱动的 Linux 工业设备,例如映翰通 EC312。现场采集时,将启动命令中的通道换成设备上已配置好只监听的 SocketCAN 接口。

4. 用完怎么卸载

做完后面的实验再回到这里。先停掉网页中的发送定时器、录制和回放,下载要留存的日志。回到启动终端按 Ctrl+C ,远程使用时再关闭本轮 SSH 转发。用 ss -ltnp 'sport = :8080' 确认本轮服务已退出。

在原终端沿用 BASE 和 RUN。下面先核对 RUN 的实际路径、目录名和所有者,再交互删除本轮 venv 与临时文件。确认这些目录没有挂入共享存储后执行。

bash 复制代码
[[ -n "${BASE:-}" && -n "${RUN:-}" ]] || exit 1
if [[ ${VIRTUAL_ENV:-} == "$RUN/.venv" ]]; then
  deactivate
fi
cd "$BASE" || exit 1
run_path=$(realpath -e -- "$RUN") || exit 1
[[ "$run_path" == "$RUN" && "${run_path%/*}" == "$BASE/runs" &&
   "${run_path##*/}" =~ ^canviz\.[[:alnum:]]{8}$ &&
   -d "$run_path" && -O "$run_path" ]] || exit 1

rm -rI --one-file-system -- "$run_path"/{.venv,tmp,replay-uploads} || exit 1
du -sh -- "$run_path"

日志仍在 $RUN/logs/,可归档后按需删除。要收回虚拟接口,用 ip -d link show dev vcan0 确认它的类型是 vcan,alias 等于本轮的 ${RUN##*/},并确认其他程序已经不用它,再执行 sudo ip link delete dev vcan0。如果这次复用的是已有接口,跳过删除。

5. 用 vcan0 看报文、过滤和发送

在设备上的另一个终端发模拟帧。先确认网页连的是 SocketCAN (Linux) 的 vcan0,菜单里的 virtual (testing) 是进程内总线,外部 cansend 的帧进不到那里。以下发帧、定时发送和回放都使用虚拟接口。

可以先在一个终端运行 candump -L -n 3 vcan0,再从另一个终端发送。

bash 复制代码
[[ -n $(ip -o -d link show dev vcan0 up type vcan) ]] || exit 1
cansend vcan0 123#1122334455667788
cansend vcan0 18F004E5#FFFF91603BFFFFFF
cansend vcan0 123#AA22334455667788

网页里应该有 0x123 和 0x18F004E5 两行。前者是 11 位标准帧,后者是 29 位扩展帧。两条样例的 DLC 都是 8,表示有 8 个数据字节。最后一帧到达后,0x123 的首字节会变成 AA,Count 增加。要找前一帧,就看刚才的 candump 输出。

打开 Filters → ID Filter ,填 0x100-0x1FF。表格会留下 0x123,扩展帧被筛掉。点 Clear filters 即可恢复。

想从网页发一帧,在 Send Frame 里填 ID 0x321、DLC 8、数据 01 23 45 67 89 AB CD EF,保持 29-bit 未勾选。用 candump -L -n 1 vcan0,321:7FF 等待,然后点击上箭头 Send once ,终端应收到 321#0123456789ABCDEF。同一面板也能设 Interval 做周期发送,用完点 Stop timer。

录制时点 Record → ● Record ,再发一条帧。点 ■ Stop 后可以下载 CSV 或 ASC。录制好的文件也会留在服务端。想重新看一轮报文,可在 Connection 区点 Clear 清空表格和曲线缓冲。

6. 用 DBC 把 60 3B 解成 1900 rpm

DBC 说明某个 ID 的数据区里,哪些位对应什么信号。这个例子给 0x123 定义一个转速信号,从第 4 字节的最低位开始读取 16 位。将下方保存成 engine_demo.dbc。

dbc 复制代码
VERSION "CANviz teaching fixture"

NS_ :

BS_:

BU_: Simulator Viewer

BO_ 291 DemoEngine: 8 Simulator
 SG_ EngineSpeed : 24|16@1+ (0.125,0) [0|8191.875] "rpm" Viewer

BO_ 后的十进制 291 对应 0x123。24|16@1+ 表示从 bit 24 取 16 位,@1 是 Intel 小端,+ 表示无符号。括号里的 0.125 和 0 分别是 Factor 和 Offset 。第 4、5 字节为 60 3B 时,小端原值是 0x3B60 = 15200,乘 0.125 再加 0,得到 1900 rpm。

展开 DBC / Signals ,点击 Drop .dbc file or click to browse 选中 DBC,开启 Show decoded signals。导入后再发一帧,让它走一次接收和解码。

bash 复制代码
[[ -n $(ip -o -d link show dev vcan0 up type vcan) ]] || exit 1
cansend vcan0 123#112233603B000000

展开 0x123 这行,DemoEngine.EngineSpeed 应显示 1900.000 rpm。如果字节看起来对,但数值差了很多,可以先拿 60 3B 手算一遍,检查有没有把高低字节读反。

7. 画实时曲线,再用日志重建

收到转速后,打开 Plot → + add signal ,选 DemoEngine.EngineSpeed,窗口设为 30s 。点 Record → ● Record,切回 Plot。另开设备终端运行下面的循环,五个载荷依次对应 800、1200、1900、1500 和 1000 rpm。

bash 复制代码
[[ -n $(ip -o -d link show dev vcan0 up type vcan) ]] || exit 1
for data in 0019 8025 603B E02E 401F; do
  for ((i=0; i<8; i++)); do
    cansend vcan0 "123#112233${data}000000" || exit 1
    sleep 0.2
  done
done

曲线应该先升到 1900 rpm,再降到 1000 rpm。用鼠标拖选可以放大一段,双击回到实时滚动。点 Record → ■ Stop,下载 CSV 或 ASC。在第二个设备终端做日志操作时,先把安装终端打印的 RUN 路径复制过来。

bash 复制代码
read -r -p '粘贴 RUN 的绝对路径: ' RUN

如果只想看当时的原始帧,打开 Replay 并上传刚才的 CSV。0.3.1 的内置回放直接把帧送到网页,DBC 解码流程不会重新执行。此时能看到报文,Plot 仍可能是空的。

想重建转速曲线,停止内置 Replay,保留 DBC,把 CSV 转成 canplayer 能读的格式,再送回 vcan0。下面的代码按文件名选取最近一次录制。转换适用于本例的经典 CAN 数据帧,并保留时间戳和扩展帧标志。

bash 复制代码
logs=("$RUN"/logs/canviz_*.csv)
CSV=${logs[-1]}
[[ -f "$CSV" ]] || { printf '%s\n' '先完成一轮录制' >&2; exit 1; }
REPLAY_LOG=$(mktemp -- "$RUN/tmp/replay.XXXXXXXX.log") || exit 1
python3 - "$CSV" "$REPLAY_LOG" <<'PY' || exit 1
import csv
import math
import sys

frames = []
with open(sys.argv[1], newline="", encoding="utf-8-sig") as src:
    for row in csv.DictReader(src):
        stamp = float(row["timestamp"])
        can_id = int(row["id"], 16)
        data = bytes.fromhex(row["data"])
        flag = row["is_extended_id"]
        if not math.isfinite(stamp) or flag not in ("0", "1"):
            raise SystemExit("CSV 时间戳或扩展帧标志无效")
        width, limit = (8, 0x1FFFFFFF) if flag == "1" else (3, 0x7FF)
        dlc = int(row["dlc"])
        if not 0 <= can_id <= limit or len(data) != dlc or dlc > 8:
            raise SystemExit("需要经典 CAN 数据帧")
        token = f"{can_id:0{width}X}#{data.hex().upper()}"
        frames.append(f"({stamp:.6f}) vcan0 {token}\n")
if not frames:
    raise SystemExit("CSV 中没有数据帧")
with open(sys.argv[2], "w", encoding="ascii") as dst:
    dst.writelines(frames)
PY
[[ -n $(ip -o -d link show dev vcan0 up type vcan) ]] || exit 1
canplayer -I "$REPLAY_LOG" vcan0=vcan0

新帧经 SocketCAN 到达,CANviz 会重新匹配 DBC。若刚清空过视图,收到第一帧后在 Plot 重新添加转速信号。这里复建的是按原间隔到达的信号变化,曲线横轴使用这次接收的时间。手里已有 candump -L 保存的同类日志时,可以直接用 canplayer,省去 CSV 转换。

8. J1939 与 FMS 数据怎么读

先从 29 位 ID 读出 PGN

看到 18F004E5#FFFF91603BFFFFFF 时,先拆 ID。优先级是 6,源地址 SA 是 0xE5,PGN 是 0xF004(61444),即 EEC1。PGN 标识参数组,SPN 标识组内的具体参数。EEC1 里的 SPN 190 是发动机转速,SPN 513 是实际扭矩百分比。

拆 PGN 时先看 PF 。PF 小于 0xF0 时是 PDU1,PS 是目的地址,算 PGN 时清零低字节。比如 0x18EA25E5 的目的地址是 0x25,PGN 是 0xEA00。PF 大于等于 0xF0 时是 PDU2,PS 参与 PGN 计算,EEC1 就属于这一类。

python 复制代码
for can_id in (0x18EA25E5, 0x18F004E5):
    pf = (can_id >> 16) & 0xFF
    ps = (can_id >> 8) & 0xFF
    pgn = (can_id >> 8) & 0x3FFFF
    if pf < 0xF0:
        pgn &= 0x3FF00
    print(f"PGN=0x{pgn:X}, PS=0x{ps:02X}, SA=0x{can_id & 0xFF:02X}")

在网页右侧打开 J1939 → Enable Decoder,可以查看收到的 PGN、节点和近期记录。扩展帧也可能承载其他协议,分析实车数据时结合接口说明和报文内容来判断。

用模拟 FMS V05 帧看转速、车速和油量

商用车的 CAN 型 FMS 接口通常用来读取一部分 J1939 数据,例如转速、车速和累计里程。手头有车厂给的 DBC 时,可以用它把载荷换算成工程值。下面是一份 FMS V05 教学 DBC,按完整 CAN ID 匹配。保存为 fms_demo.dbc,在网页里卸载前一个 DBC,再导入这份。

dbc 复制代码
VERSION "CANviz teaching fixture"

NS_ :

BS_:

BU_: Simulator Viewer

BO_ 2565866725 FMS_F004: 8 Simulator
 SG_ EngineSpeed : 24|16@1+ (0.125,0) [0|8031.875] "rpm" Viewer
 SG_ ActualTorque : 16|8@1+ (1,-125) [-125|125] "%" Viewer

BO_ 2566844901 FMS_FEF1: 8 Simulator
 SG_ WheelSpeed : 8|16@1+ (0.00390625,0) [0|250.99609375] "km/h" Viewer

BO_ 2566847717 FMS_FEFC: 8 Simulator
 SG_ FuelLevel1 : 8|8@1+ (0.4,0) [0|100] "%" Viewer

BO_ 2566841829 FMS_FEE5: 8 Simulator
 SG_ EngineHours : 0|32@1+ (0.05,0) [0|210554060.75] "h" Viewer

BO_ 2566832613 FMS_FEC1: 8 Simulator
 SG_ HighResDistance : 0|32@1+ (0.005,0) [0|21055406.075] "km" Viewer

BO_ 2566842853 FMS_FEE9: 8 Simulator
 SG_ TotalFuelUsed : 32|32@1+ (0.5,0) [0|2105540607.5] "L" Viewer

扩展帧在 DBC 的 BO_ ID 中置 bit 31,例如 0x18F004E5 写成 0x98F004E5,十进制为 2565866725。总线上仍发原始 ID。换一台车时,优先级或源地址变了,PGN 可能相同,DBC 匹配的完整 ID 则要重新核对。

保持 CANviz 连接 vcan0,清空旧帧后发下面六条。字节位置从 1 数,下面的数值字段按无符号小端读取。

bash 复制代码
[[ -n $(ip -o -d link show dev vcan0 up type vcan) ]] || exit 1
cansend vcan0 18F004E5#FFFF91603BFFFFFF
cansend vcan0 18FEF1E5#FF004BFFFFFFFFFF
cansend vcan0 18FEFCE5#FF7DFFFFFFFFFFFF
cansend vcan0 18FEE5E5#204E0000FFFFFFFF
cansend vcan0 18FEC1E5#00127A00FFFFFFFF
cansend vcan0 18FEE9E5#FFFFFFFF204E0000
PGN SPN 与字节 解码结果
F004 190,字节 4--5 15200 × 0.125 = 1900 rpm
F004 513,字节 3 145 − 125 = 20% 扭矩
FEF1 84,字节 2--3 19200 ÷ 256 = 75 km/h
FEFC 96,字节 2 125 × 0.4 = 50% 油量
FEE5 247,字节 1--4 20000 × 0.05 = 1000 h
FEC1 917,字节 1--4 8000000 × 0.005 = 40000 km
FEE9 250,字节 5--8 20000 × 0.5 = 10000 L

看转速时要识别状态值。例如 SPN 190 的 00 FE 对应原值 0xFE00,表示错误。FF FF 表示不可用。通用 DBC 解码可能照常换算,把它们画成 8128 和 8191.875 rpm。遇到类似尖峰,先看原始字节,统计时把这些状态点筛掉。接车时以车辆网关实际提供的数据和字段定义为准。

从 BAM 里拼出 VIN

VIN 对应 PGN 0xFEEC 和 SPN 237。这里用虚构标识 ZZZTEST0123456789*,末尾 * 是分隔符,总长 18 字节。经典 CAN 的一帧装不下,所以用 BAM 广播。TP.CM 先宣布目标 PGN、总长度和包数,再由三条 TP.DT 按序号送出数据。

bash 复制代码
[[ -n $(ip -o -d link show dev vcan0 up type vcan) ]] || exit 1
cansend vcan0 1CECFFE5#20120003FFECFE00 && sleep 0.1 &&
cansend vcan0 1CEBFFE5#015A5A5A54455354 && sleep 0.1 &&
cansend vcan0 1CEBFFE5#0230313233343536 && sleep 0.1 &&
cansend vcan0 1CEBFFE5#033738392AFFFFFF

首帧的数据是 20 12 00 03 FF EC FE 00。20 表示 BAM,12 00 是小端长度 18,03 是三包,最后三个字节 EC FE 00 给出目标 PGN。三条 DT 的首字节是 1、2、3,拼接其余字节后只取前 18 个,去掉填充的 FF,再按 ASCII 读取。

打开 J1939 → BAM Log 查看重组结果。需要完整数据时,在设备终端运行 curl -fsS http://127.0.0.1:8080/j1939/status,看 recent_bam 里的 PGN 65260、长度 18 和十六进制载荷。遇到缺包时,先在 candump 里按源地址、时间和包序号对齐同一次广播。

DM1 先看原帧,再核对故障码

DM1 的 PGN 是 0xFECA,用于报告活动故障。前两字节 04 FF 是灯状态和闪烁状态,中间 64 00 01 03 是一条 DTC,最后两字节是填充。

bash 复制代码
[[ -n $(ip -o -d link show dev vcan0 up type vcan) ]] || exit 1
cansend vcan0 18FECAE5#04FF64000103FFFF

对转换方法 CM=0,可用这几行从 DTC 提取 SPN、故障模式 FMI 和出现次数 OC。

python 复制代码
a, b, c, d = bytes.fromhex("64 00 01 03")
cm = d >> 7
spn = a | (b << 8) | ((c >> 5) << 16)
fmi = c & 0x1F
oc = d & 0x7F
print(spn, fmi, oc, cm)  # 100 1 3 0

0.3.1 的 DM1 面板对这条样例会解错。遇到该版本的结果,保留原帧并按字节独立核对 DTC。若原帧的 CM 为 1,查 ECU 的旧转换方法。现场判断还要核对报文来自哪个 ECU,以及厂商对 SPN 和 FMI 的说明。

9. 日常调试还能怎么用

  • 盯住一个 ID。 先用过滤缩小范围,在操作设备前后分别看 Data 和 Count。网页上看到某个字节在变时,把原始帧也留一份,后面可以逐条比较。
  • 把两个信号放到同一窗口。 比如把转速和扭矩一起画出来,再记下加负载的时刻。对比哪个信号先变、哪个恢复得慢,能更快找到需要回看的一段数据。
  • 从日志里算周期。 用 candump -L 的采集时间戳筛出同一 ID 和来源的帧,计算相邻间隔。连续少了几帧、突然扎堆或周期拉长,都比只看网页上的 Last Seen 更容易定位。

10. 三个实战应用

看一次转速升降

用第 7 节的五档数据练一遍。先记住 1900 rpm 对应的原始字节,打开 Plot 再录制,等转速降到 1000 rpm 后停止。回放时核对峰值和先后顺序。排查现场掉速也可以这么做,把异常时间记下来,再查当时的转速和相关工况信号。

从 FMS 接口读取车辆数据

接入车厂允许使用的 FMS 口后,先抓几帧确认源地址和 PGN,再导入车辆对应的 DBC。起步时可以同时看 EEC1 转速和车速,核对车辆从静止到移动时两个信号的变化。油量、小时数和里程适合抽样核对。碰到读数异常,先分清总线上没收到对应 PGN,还是字段本身给出了错误或不可用状态。

记录工业设备的一次通信异常

以 EC312 等具备 SocketCAN 的 Linux 边缘设备为例,让 CANviz 连接到已配置好只监听的接口,通过 SSH 转发在电脑上查看。平时用 ID 过滤盯住相关报文,故障发生前后保存一段日志,并记下时间和操作。把日志带回分析机后再回放和检查信号。示例里的发帧和注入回放都留在独立的 vcan0 上。

11. 常见问题与总结

  • 安装完启动失败。 确认调用的是 $RUN/.venv/bin/canviz,用 pip check 检查依赖,再看启动终端是否提示端口占用。
  • 网页打不开。 在运行服务的 Linux 上先请求 /status。远程设备能响应而电脑打不开时,检查 SSH 转发的端口和访问地址。
  • 网页没有帧。 先确认 candump 能收到,接着检查 SocketCAN 通道和过滤条件。
  • DBC 解码不对。 按原始帧核对完整 ID、帧格式、载荷长度和字节序。刚导入 DBC 时再收一条新帧。
  • 曲线空白。 展开帧确认已经有解码值,再检查 Plot 选中的信号、时间窗口和滚动状态。
  • Replay 有帧但没有信号。 内置回放用于查看原始帧。要重画曲线,按第 7 节回放到 vcan0。

调试收工前,把原始日志、用过的 DBC 和异常时间放在一起,再清理这轮 venv。下次遇到同样的问题,可以直接从那几秒的报文重建曲线,核对当时到底收到了什么。

相关推荐
2601_9679873744 分钟前
python判断变量名不要调用任何函数实现
python·函数·语法·判断·变量名
kloc1 小时前
聊天室项目入口网关子服务的实现
linux·c++
GeW1 小时前
深圳APEC峰会倒计时:数据库与Linux运维的"隐形考场"
linux
卷无止境1 小时前
SALib敏感性分析的python库学习笔记
后端·python
duansamve3 小时前
Python3+Flask+PostgreSQL18+Vue3项目生产环境用docker如何部署?
python·docker·flask
梦到啥说啥3 小时前
Linux网络编程:UDP网络协议
linux·网络协议·udp
特立独行的猫A3 小时前
少 3 个环节、少 1 次 ABI、少 3500 行代码:cj-tauri 与 Ionic/Capacitor 的鸿蒙桥接机制对比
java·后端·python
特立独行的猫A3 小时前
cj-tauri vs Rust/Tauri 同款播放器实测:编译 21 秒 vs 11 分钟,磁盘 12 MB vs 1.2 GB
java·后端·python
duansamve4 小时前
详解Flask中Session和Cookie
python·flask