admin管理员组

文章数量:1658461

软件背景

做汽车开发工作的应该大部分都知道 BLF (Binary Logging Format)文件。它是由Vector公司定义的用于记录总线数据的一种文件格式。在汽车开发的过程中,可能我们要录一段时间很长的数据,而CAN总线上有些数据是每隔10ms发送一次,有些是隔100ms,有些是隔1S发送一次的。而当我们要回放分析数据的时候,由于时间太长,导致电脑卡顿,导致需要很长的时间回放的数据。因此产生了一种想法:有时候我们并不需要特别精确的数据。而在分析数据的时候,如果把10ms一次的数据变为100ms一次。将会大的降低电脑的卡顿。因此希望能够使用软件处理一下BLF文件,在减少其中数据的同时,不会特别影响数据的内容。

这里我选用的编程语言为Python。选择它的理由很简单。因为它有丰富的库可以调用,因此我们并不需要特别深入的到底层。我的目的是能够快速的做出我达到功能需求的软件。

编程环境的搭建

首先给大家介绍一下我的编程环境:Win10+anaconda。安装anaconda后,可以使用jupyter在网页上进行编程。

在网上搜罗一番之后,我找到了能够满足我使用要求的库了:python-can。这个库的功能是驱动各种CAN盒,同时还带了解析记录文件的程序。安装指令为:

pip install -i https://pypi.tuna.tsinghua.edu/simple python-can

需要注意的是库安装的位置为:盘符:\Anaconda\Lib\site-packages 。意思是当我们import的时候,就是从这里找到的python库。在后续的编程的时候,也需要去这个文件夹中找到源码,看库的使用方法。

在安装好这个库之后,我们就可以使用python测试是否正常工作,在python中输入以下指令运行,如果没有报错表示安装成功:

import can

在完成以上的工作之后,就开始自己正式的工作了。编程很多时候都是参考网上的例子,然后按照自己的需求在其基础上进行修改。下面我介绍一下自己在做这个工具的小想法:其核心功能是按照用户的需求降低文件内的CAN报文的帧数。假设用户所需的采样频率为n。

采样的计算方法

最开始的方案是读取CAN报文上的所有帧数,然后在读取帧的过程中,将这个数对n进行取模。判断余数为1时,便记录该帧报文到新的文件中去。但是实际效果不是很好。因为有的帧发送比较频繁,有的帧发送的比较慢。导致有的帧在整个采样周期中都没有被采集到,造成数据丢失严重。以下是将n设置为10时的程序。使用CANoe可以观察到输入文件与输出文件对比。发送频率比较低的报文丢失很多数据。复制以下代码,修改路径后可转换文件

from can.io import BLFWriter, BLFReader
logfile = r"输入文件.blf"
filename_out = r"输出文件.blf"
logger=BLFWriter(filename_out)
i = 0
with can.BLFReader(logfile) as reader:
    for msg in reader:
        i += 1
        if i % 10 == 1:
                logger.on_message_received(msg)    
logger.stop()
print(i)

因此想到的新方法是首先要对CAN报文的数据进行分类。分类方法为按帧报文进行分类(在后续的开发中,发现还需按照channel进行分类)。然后统计每一类帧的出现的次数。然后用这个次数对n进行取模。判断余数为1后,将该帧记录到新的文件中。这样所有的报文都能按照n周期进行采样。也就不会丢失太多数据了。

from can.io import BLFWriter, BLFReader
logfile = r"输入文件.blf"
filename_out = r"输出文件.blf"
logger=BLFWriter(filename_out)
canid = {}
with can.BLFReader(logfile) as reader:
    for msg in reader:
        if msg.arbitration_id not in canid:
            canid[msg.arbitration_id] = 1
        else:
            canid[msg.arbitration_id] = canid[msg.arbitration_id] + 1
            if canid[msg.arbitration_id] % 10 == 1:
                logger.on_message_received(msg)
            
logger.stop()
print(canid)

上面的那个程序还不是很完美,因为它对所有的报文都进行了一个采样。而大家的需求是:对于发送周期很短的报文的采样能够松一点,对于发送周期比较长的报文采样能够密一点。也就是能够手动调节每帧的采样周期 。但是这个功能是需要与软件界面进行配合,因此在这里不提供代码了。

GUI的开发

我的电脑安装好了Python。自然可以很方便的运行上面的程序了。当然也不是特别方便,因为它的路径是要手动输入进去的,复制粘贴路径的功夫,也是非常麻烦的。因此接下来的目标是为该软件写一个界面。并且打包成exe软件,当我同事想用的时候,也能方便快捷的打开使用。在网上搜索一番后,我选择使用Tkinter作为这软件的GUI库。选择的理由也很简单。因为自己的软件十分简单,不希望用其他库后,开发打包后的软件太大了。

对于GUI开发,其实就是几个按钮罢了。在这里我的界面的主要难度在于Tkinter中,能够为每类帧设置其采样周期。这里我使用了Treeview这个控件。但是这个控件并没有提供格子修改功能。因此需要我们自己写一个函数对其格子进行修改。

from tkinter import *
from tkinter import ttk
from tkinter.simpledialog import askinteger
root= Tk() 

root.title("BLF")
frame= Frame(root)

frame.pack(padx=20,pady=20) #set area

def set_cell_value(event): # 双击进入编辑状态

    for item in tree.selection():

        #item = I001

        item_text = tree.item(item, "values")

        #print(item_text[0:2])  # 输出所选行的值

    column= tree.identify_column(event.x)# 列

    row = tree.identify_row(event.y)  # 行
    
    var_int = askinteger(title = "请输入一个整数",prompt = "整型变量x:")
    if int(var_int) > 0:
        tree.set(item, column=column, value=var_int)
    else:
        pass
    
# 定义列的名称
columns = ("CHANNEL","ID", "OLD_NUM", "MULT","NEW_NUM")   
tree = ttk.Treeview(frame, show = "headings", columns = columns, selectmode = BROWSE)
# 设置表格文字居中
tree.column("CHANNEL", anchor = "center")
tree.column("ID", anchor = "center")
tree.column("OLD_NUM", anchor = "center")
tree.column("MULT", anchor = "center")
tree.column("NEW_NUM", anchor = "center")
# 设置表格头部标题
tree.heading("CHANNEL", text = "通道数")
tree.heading("ID", text = "CAN ID")
tree.heading("OLD_NUM", text = "旧文件帧数")
tree.heading("MULT", text = "采样倍率")
tree.heading("NEW_NUM", text = "新文件帧数")
tree.grid(row=3,column=0,padx=35,pady=5,columnspan=2)
tree.bind('<Double-1>', set_cell_value) # 双击左键进入编辑

tree.insert('', 1, values = (0,0,0,0,0))
root.mainloop()

使用Pyintaller打包软件

界面的编写是一个很机械重复的过程。勉勉强强做出来比较合适的界面后。就要对软件进行打包了。这时候该如何打包程序呢。百度了一番打包程序之后,发现程序打包还是一门很深的水。这也是我写这篇文章的主要目的了。也是我对python理解最深的地方了。最开始我使用的是pyinstaller进行打包。安装的指令为:

pip install -i https://pypi.tuna.tsinghua.edu/simple pyinstaller

 使用以下指令就能打包出想要的软件,并且能够正常运行

pyinstaller -Fw main.py

但是有个问题就是,打包的软件由于导入了其他软件库。导致软件有十五六兆的大小。这是最方便的也是最慢的软件。因为它使用的是Cpython。运行效率太低了。打开软件就需要10秒左右(打包为单个文件,当打包为多个文件的时候,启动为2S)。然后解析一个20Mb的文件需要30秒左右。对于这种使用场景下已经够用了。因为对时间并没有太高的要求。但是当文件有200Mb的时候,可以有预见性的认为时间需要五分钟,意味着速度会变得特别的长。接下来无聊我便专门的研究了一下python的数据打包。

 

使用Nuitka打包软件

参考知乎上推荐的Nuitka打包。安装起来比较麻烦,需要安装C语言编译器等等。其原理应该是把python转化为C语言,然后通过C语言编译成EXE文件。其打包还是非常有意思的。因为用Nuitka打包出来的软件不是多个文件,因此其启动速度还是非常快的。大概一两秒就能加载好了。然后解析的时间也要比pyinstaller打包的快一点点。可以说是有所提升,但是二十秒的时间也是有点慢了。我的期望以为打包出来的文件是C编译出来的,按道理应该是非常快的。距离我的期望还是有点距离。

使用pypy运行软件

可能是自己的程序优化不到位,但是我也不想去修改自己的程序提高程序的运行速度。一方面是因为改程序太麻烦了,另一方面也是因为不知道从何开始下手。在搜索一番资料后,发现可以pypy这个解释器的运行速度非常的强大,而我的库其实都是纯py文件的,因此也不需要做太多的设置。我甚至都不用去给它配置c语言编译器就能运行我的程序。在把自己的程序和库导入到pypy中后,运行速度打开速度非常的快,而且解析blf文件也是非常的快的。当200Mb的文件也就在30秒内能够解析完成。完全能够满足大文件的需求。

 

本文标签: 小工具文件Pythonblf