python-can 虚拟车速通信

1、okr

  • O:做一个不用 CAN 硬件的虚拟车速表 demo
  • KR1:python-can 虚拟环境单机跑
  • KR2:发送端每 200ms 稳定发车速报文
  • KR3::接收端实时显示车速,有终端界
  • KR4:改发送值,界面 1 秒内同步变

2、准备方案分两种

方案A:因为我们没车也没CAN卡,所以只能用虚拟方案

环境

  • python-can 的 VirtualBus(纯软件,无需硬件)
  • 界面:简单 GUI(tkinter/PyQt)或终端打印

步骤,见上述 kr

方案B:真实的实车测试场景

环境

  • 硬件:CAN 接口卡(PCAN/Kvaser)+ 连接车辆 CAN 总线
  • 软件:python-can 库 + 设备驱动
  • 协议:获取车速信号的 CAN ID、字节位置、比例因子(DBC 或协议文档)

步骤

  1. 配置 CAN 通道和波特率,建立连接
  2. 编码 车速值(60 km/h 按协议换算成原始值)
  3. 发送 CAN 报文
  4. 验证 在仪表或诊断工具上查看车速变化

3、核心思路

用 python-can 的 udp_multicast 虚拟总线 在同一台机器上模拟 CAN 通信,发送方编码车速报文,接收方解析并可视化显示。

┌─────────────┐ UDP Multicast ┌─────────────┐

│ 发送方脚本 │ ═════════════════► │ 接收方脚本 │

│ (模拟ECU) │ 虚拟CAN总线(channel) │ (仪表/DBS) │

│ 车速=60km/h │ │ 解析+展示 │

└─────────────┘ └─────────────┘

3.1 python-can

python-can 就是 Python 的"CAN 总线翻译官"

  • CAN 总线本身是一套硬件通信协议(像说德语),你的 Python 代码说英语,python-can 就是中间的翻译,让你用简单的 Python 代码就能收发 CAN 报文,不用管底层硬件细节。
  • 它封装了各种 CAN 接口卡(PCAN、Kvaser、Vector)的驱动,也提供虚拟总线(virtual/udp_multicast),没有硬件也能开发和测试。

安装

bash 复制代码
pip install python-can

验证

python 复制代码
 import can
 print(can.__version__)  # 输出版本即安装成功

3.2 udp_multicast

虚拟总线像微信群,在群里发了公告,线上的人都知道。比如发了车速加到了60km/h,那仪表盘、发动机、ABS都知道了。

注意不是广播,而是多播,只有特定频段的可以收到消息,换言之,只有进了群的才能收到。

(备注:文件传输协议UDP和TCP是两大寡头,UDP类似广播,发了不管你收不收得到,但速度快;TCP类似打电话,一对一的,按顺序通信比较稳)

4、开始编码

4.1 发送方 - sender_only.py

python 复制代码
import can
import time
import sys

print("=== 启动调试发送端 ===")

try:
    # 1. 尝试连接虚拟总线
    print("[1] 正在连接虚拟总线 'virtual_bus' ...")
    # bus = can.interface.Bus(interface='virtual', channel='virtual_bus', bitrate=500000)
    # 修改后 👇👇
    bus = can.interface.Bus(
        interface='udp_multicast',
        channel='239.255.0.1',  # 必须和接收端的 IP 一模一样!
        bitrate=500000
    )
    # 1.1 bitrate在虚拟总线里是干嘛的?
    # bitrate = 500000  # 500 kbps,真实CAN总线速度
    # 在 udp_multicast里实际不起作用(UDP没有固定波特率),但保留是为了:
    # - 接口统一(和真实CAN卡代码兼容)
    # - 某些虚拟接口会模拟时序
    print("[OK] 连接成功!")

    # 2. 准备数据
    # 2.1 帧id是什么?
    # arbitration_id------帧id,就像门牌号,两个作用:
    # a、识别报文类型:告诉接收者这数据是什么类型的给谁的,0x1A0 表示"这是车速报文",0x1B0可能是转速报文
    # b、决定优先级:ID越小,总线竞争时优先级越高(0x100比0x500优先发送)
    # 2.2 data是什么?
    # data = [60, 0, 0, 0, 0, 0, 0, 0]  # 8字节,每字节0-255
    # CAN报文固定最多8字节。这里的写法是直接把60塞进第一个字节。
    # 但注意:实际车上通常按DBC规则编码,比如:车速 = 第一个字节 × 0.5km/h
    # 那么60km/h = 120 = 0x78
    # data = [0x78, 0, 0, 0, 0, 0, 0, 0]
    msg = can.Message(arbitration_id=0x1A0, data=[60, 0, 0, 0, 0, 0, 0, 0])
    print(f"[2] 准备发送报文 ID: 0x{msg.arbitration_id:X}, 数据:{msg.data}")

    # 3. 开始循环发送
    count = 0
    print("[3] 进入发送循环 (按 Ctrl+C 停止)...")

    while True:
        bus.send(msg)
        count += 1
        # 打印进度,让你知道它活着
        if count % 5 == 0:
            print(f"   -> 已发送 {count} 帧,车速 60 km/h")

        time.sleep(0.2)  # 200ms 周期

except Exception as e: # ← 捕获所有异常(Exception是基类)
    print(f"\n[ERROR] 发生严重错误,程序退出:{e}")
    print(f"错误类型:{type(e).__name__}")
    import traceback

    traceback.print_exc()  # 打印详细堆栈信息
finally:
    # 不管成功还是失败,最后都会执行(清理工作)
    print("\n[END] 发送端已关闭")
    # locals()返回当前作用域的所有变量字典:
    # 'bus' in locals()  # 检查变量bus是否已定义(避免未赋值就shutdown报错)
    if 'bus' in locals():
        bus.shutdown()

4.2 接收方 - dashboard.py

python 复制代码
import can
import time
import os
import sys

# 配置:必须和发送端一致
BUS_CONFIG = {
    'interface': 'udp_multicast',
    'channel': '239.255.0.1',
    'bitrate': 500000
}
CAN_ID_SPEED = 0x1A0


def clear_screen():
    """清屏命令,Mac/Linux 用 clear,Windows 用 cls"""
    os.system('clear' if os.name != 'nt' else 'cls')


def main():
    print("🚀 正在连接 CAN 总线...")
    try:
        bus = can.interface.Bus(**BUS_CONFIG)
        bus.set_filters([{"can_id": CAN_ID_SPEED, "can_mask": 0x7FF}])
        print("✅ 连接成功!监听中...\n")
        time.sleep(1)
    except Exception as e:
        print(f"❌ 连接失败:{e}")
        return

    current_speed = "--"
    frame_count = 0

    # 隐藏光标,让刷新更流畅
    sys.stdout.write("\033[?25l")

    try:
        while True:
            msg = bus.recv(timeout=0.1)  # 稍微等一点点数据

            if msg and msg.arbitration_id == CAN_ID_SPEED:
                current_speed = msg.data[0]
                frame_count += 1

                # --- 绘制终端仪表盘 ---
                clear_screen()
                print("=" * 40)
                print("       🚗 虚拟车速仪表盘 (Terminal) 🚗")
                print("=" * 40)
                print()

                # 根据速度改变颜色逻辑 (如果终端支持 ANSI 颜色)
                speed_str = str(current_speed)
                color_code = "\033[92m"  # 绿色
                if current_speed > 80:
                    color_code = "\033[91m"  # 红色
                elif current_speed > 40:
                    color_code = "\033[93m"  # 黄色

                reset_code = "\033[0m"

                # 打印大字
                print(f"          当前车速")
                print(f"      {color_code}┌─────────────┐{reset_code}")
                print(f"      {color_code}│             │{reset_code}")
                print(f"      {color_code}│   {speed_str:>3} km/h   │{reset_code}")
                print(f"      {color_code}│             │{reset_code}")
                print(f"      {color_code}└─────────────┘{reset_code}")
                print()
                print(f"   📡 已接收帧数:{frame_count}")
                print(f"   🆔 报文 ID: 0x{msg.arbitration_id:X}")
                print("=" * 40)
                print("按 Ctrl+C 退出")

            else:
                # 如果没收到新数据,至少显示初始状态,证明程序活着
                if frame_count == 0:
                    clear_screen()
                    print("⏳ 等待第一帧数据... (请确保发送端已运行)")
                    print(f"   通道:{BUS_CONFIG['channel']}")

    except KeyboardInterrupt:
        print("\n👋 退出程序")
    finally:
        # 恢复光标
        sys.stdout.write("\033[?25h")
        bus.shutdown()


if __name__ == "__main__":
    main()

5、运行

打开两个终端,左边先启动接收方,右边再启动发送方

bash 复制代码
python dashboard.py
bash 复制代码
python sender_only.py

6、效果

如下:右边发送数据,左边接收到数据并显示

7、练一练

之前速度是写死的,掌握上述内容后,可以试着把速度改"活"

写了两种"活"的模式,你可以切换试试:

|---------|--------------------------|--------------|
| 模式 | 代码 | 效果 |
| 正弦波(默认) | 60 + 60*sin(count*0.1) | 平滑加速减速,像真实驾驶 |
| 线性突变 | (count * 5)% 240 | 测试边界值、回绕、跳变 |

把之前sender_only.py的while里的代码替换即可,代码如下:

python 复制代码
    while True:
        # ========== 动态计算车速 ==========
        # 用正弦波生成 0~120 之间的车速
        # count * 0.1 控制变化速度,数字越大变化越快
        speed = int(60 + 60 * math.sin(count * 0.1))

        # 也可以改成线性突变模式(取消下面注释):
        # speed = (count * 5) % 240  # 0,5,10...235,0,5... 测试突变和回绕

        # 组装报文:车速放入第1字节
        msg = can.Message(
            arbitration_id=0x1A0,
            data=[speed, 0, 0, 0, 0, 0, 0, 0]
        )

        # 发送
        bus.send(msg)
        count += 1

        # 打印状态(每5帧打印一次,避免刷屏)
        if count % 5 == 0:
            print(f"   -> 帧[{count:4d}] 车速: {speed:3d} km/h")

        time.sleep(0.2)  # 200ms周期
相关推荐
学习3人组2 小时前
成品批次信息及全链路溯源汇报材料(大客户专用)
网络·erp·mes
想唱rap2 小时前
UDP套接字编程
服务器·网络·c++·网络协议·ubuntu·udp
AC赳赳老秦2 小时前
OpenClaw多平台部署:Windows+Linux跨系统协同,实现全场景覆盖
linux·服务器·前端·网络·windows·deepseek·openclaw
rannn_1113 小时前
【Redis|原理篇2】Redis网络模型、通信协议、内存回收
java·网络·redis·后端·缓存
VOOHU_20183 小时前
VOOHU沃虎:音频变压器的主要作用是什么?什么情况下必须使用?
网络·物联网·音视频·电子元器件
遇见你的雩风3 小时前
网络原理(一)
java·网络
Johnstons3 小时前
网络诊断工具怎么选:从看到异常到真正定位根因的实战方法
网络·wireshark·抓包分析
埃伊蟹黄面4 小时前
数据链路层
服务器·网络
Tel199253080045 小时前
ENDAT2.2 协议信号转 SSI /BISS-C转换卡 ENDAT2.2 协议信号转DMC多摩川高速协议转换器 互转卡
c语言·开发语言·网络