python处理车辆can通信 3 can数据采集记录

背景

结合智能驾驶,can通信,线控调试。会想到一些业务需求:can数据采集记录,can数据离线可视化分析,can数据切割,can和udp数据格式互转,车辆控制调试界面,车辆线控性能分析,dbc和xlsx互转,dbc生成c代码等等(更多需求可提到评论区里)。

需求分析

can数据采集记录

windows中canoe中Measurement Setup的Logging模块可实现asc和blf格式的can数据记录。linux中使用can卡(kvaser,pcan或者nvidia orin的can口),实现一样的功能。

需求实现

1. can卡驱动安装

kvaser驱动下载 Downloads - Kvaser Drivers, Documentation, Software, more...

pcan驱动下载 Linux PCAN Driver: Overview

解压安装包之后看里面的README文档进行安装。要区分是否为socketcan方式安装

nvidia orin上是socketcan, socketcan启动方式是

# 虚拟can通道
sudo modprobe vcan
sudo ip link add dev can0 type vcan
sudo ip link set up can0

# ip a可以查看是否生效


# 打开真实can通道
# 普通can例子
sudo ip link set can0 up type can bitrate 500000
# canfd例子
sudo ip link set can0 up type can bitrate 500000 sample-point 0.75 dbitrate 2000000 dsample-point 0.8 fd on


# 用can-utils验证
sudo apt-get install can-utils
#接受can信息
candump can0
#发送can信息
cansend can0 123#DEADBEEF

2. python-can实现数据记录基本功能

     py库安装 pip3 install python-can

import can
# 初始化can通道,要注意can通道名称,波特率,是否为canfd
bus_pcan = can.Bus(channel="PCAN_USBBUS1", interface='pcan', bitrate=500000)
# bus_socketcan = can.interface.Bus(channel='can0', bustype='socketcan')

# can.Logger是python-can的内置方法,通过识别文件名后缀来记录成对应的文件格式
asc_filepath = "./xxx.asc"
asc_logger = can.Logger(path)

# buses是个list,需要监听哪路,就把哪路append进来
buses = [bus_pcan]

# listeners是个list,用来存放回调函数,这些函数的输入参数是can message
# def print_msg(msg: can.Message):
#     print(msg)
# listeners = [asc_logger, print_msg]
listeners = [asc_logger]

# can.Notifier是让buses内的can通道,在收到can msg后就会调用listeners里的函数
notifier = can.Notifier(buses, listeners)

# notifier是异步运行的,所以需要阻塞主线程,要不然程序会不等待就退出
while True:
    cmd = input()
    if cmd == "quit"
        notifier.stop()
        bus_pcan.shutdown()
        break

3. 数据记录方式自定义

真实的业务场景需要考虑更多,下面摘取一些python片段来简述某些业务场景的实现:

1. 记录数据前需要检测can线是否已连接,

车辆行驶中的抖动容易让DB9接头松脱,此时数据记录设备检测到的负载率就很低。

如果是不同波特率的两路can线掉落了,线上也没有标签,人为再接回去就有可能插反了。如果插反了,总线就会有很多错误帧出现。

如果两路波特率是一样的,那可以通过线上特有的can报文ID来区分。

import threading

#检查can连接状态是定时检测,需要跑一个多线程来异步进行
check_bus_status_thread = threading.Thread(target=self.check_bus_status, args=())
check_bus_stats_run = True
def check_bus_stats():
    while check_bus_stats_run:
        ...

2. 每次启动记录都能产生新的文件夹,
import os
def mkdir_log_folder(folder):
    is_exists = os.path.exists(folder)
    if not is_exists:
        os.makedirs(folder)
3. log文件名能反映当时的时间戳,
import os
import time
def get_time_stamp(ct):
    local_time = time.localtime(ct)
    date_head = time.strftime("%Y_%m_%d_%H_%M_%S", local_time)
    date_secs = (ct - int(ct)) * 1000
    time_stamp = "%s_%03d" % (date_head, date_secs)
    return time_stamp
log_folder = "./"
file_name = os.path.join(log_folder, ('log_' + get_time_stamp(time.time()) + ".asc"))
4. 记录的文件大小不能太大,记录的文件时长是指定周期的,
import can
import os
import psutil
import time
from apscheduler.schedulers.background import BackgroundScheduler

# 这是后台运行的一个线程,也是需要阻塞主线程来保证它不退出
def logger_a_b():
    sched = BackgroundScheduler()

    @sched.scheduled_job('interval', id='can_logger_a_b', seconds=120)
    def change_logger():
        log_run_folder = "./"
        path = os.path.join(log_run_folder, ('log_' + get_time_stamp(time.time()) + ".asc"))
        if asc_logger_a in notifier.listeners:
            asc_logger_b = can.Logger(path)
            notifier.add_listener(asc_logger_b)
            notifier.remove_listener(asc_logger_a)
            time.sleep(2)
            asc_logger_a.stop()
        elif asc_logger_b in notifier.listeners:
            asc_logger_a = can.Logger(path)
            notifier.add_listener(asc_logger_A)
            notifier.remove_listener(asc_logger_B)
            time.sleep(2)
            asc_logger_b.stop()
        if psutil.disk_usage(log_run_folder).percent > 90:
            print("disk_usage more than 90%, please stop logger")
    return sched

logger_a_b_thread = logger_a_b
5. 实时截取车辆出现问题时前后几分钟的那一段数据并另存为,
import can
import time

issue_asc_path = "./xxx.asc"
issue_point_time = time.time()
issue_asc = can.ASCWriter(issue_asc_path)
for asc_file in asc_files:
    with can.ASCWriter(asc_file) as file:
        for msg in file:
            # 取issue_point_time的前30s和后10s内的数据
            if msg.timestamp >= issue_point_time - 30:
                issue_asc.on_message_received(msg)
                if msg.timestamp >= issue_point_time + 10:
                    break
6. 退出记录时python脚本能保存最后一份文件并关闭can通道等等。
import signal


def on_kill(signum, frame):
    # 关闭回调函数集
    notifier.stop()
    # 关闭上面提到的按时长滚动记录后台线程
    logger_a_b_thread.shutdown()
    # 关闭所有can通道
    for bus in bues:
        bus.shutdown()
    print("can logger shutdown")

# 程序被ctrl C或正常退出时会执行on_kill函数
signal.signal(signal.SIGTERM, on_kill)
signal.signal(signal.SIGINT, on_kill)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值