汽车行业DBC文件解析 | Python 解析dbc文件

官网文档下载

CSDN下载 : DBC-File-Format-Documentation.pdf

网络拓扑

理解Network,Node,Message,Signal
在这里插入图片描述

文件格式解读

说明:dbc文件以空格符拆分数据信息,类似于csv文件以",“拆分数据的意思,” “属于分隔符。还有其他分割信息的符号,如 “|”,”,“,”()“,”[]"等

Keyword Object Type Comment
VERSOIN 版本信息,为空,也可以自定义
BU_ Network Node 网络节点
BO_ Message 消息、报文
SG_ Signal 信号
EV_ Environment Variable 环境变量
CM_ 注解部分
BA_DEF_ 特征名称类型定义
BA_DEF_DEF_ 特征默认值定义
BA_ 特征项目设置值定义
NS_: new symbol 后面紧跟着一堆ns,一般是创建dbc时自动生成,不用太关心
VAL_ 数值表部分
Symbol Meaning
= A name on the left of the = is defined using the syntax on the right (syntax rule).
; The semicolon terminates a definition.
| The vertical bar indicates an alternative.
[ … ] The definitions within brackets are optional (zero or one occurrence).
{ … } The definitions within braces repeated (zero or multiple occurrences)
( … ) Parentheses define grouped elements.
’ … ’ Text in hyphens has to appear as defined.
(* … *) Comment.

文件格式

VERSION ""
     NS_ :
         NS_DESC_
         CM_
         BA_DEF_
         BA_
         VAL_
         CAT_DEF_
         CAT_
         FILTER
         BA_DEF_DEF_
         EV_DATA_
		 ENVVAR_DATA_
		 SGTYPE_
		 SGTYPE_VAL_
		 BA_DEF_SGTYPE_
		 BA_SGTYPE_
		 SIG_TYPE_REF_
		 VAL_TABLE_
		 SIG_GROUP_
		 SIG_VALTYPE_
		 SIGTYPE_VALTYPE_
		 BO_TX_BU_
		 BA_DEF_REL_
		 BA_REL_
		 BA_DEF_DEF_REL_
		 BU_SG_REL_
		 BU_EV_REL_
		 BU_BO_REL_
BS_:
BU_: Engine Gateway

BO_ 100 EngineData: 8 Engine
	SG_ PetrolLevel : 24|8@1+ (1,0) [0|255] "l" Gateway 
	SG_ EngPower : 48|16@1+ (0.01,0) [0|150] "kW" Gateway 
	SG_ EngForce : 32|16@1+ (1,0) [0|0] "N" Gateway
	SG_ IdleRunning : 23|1@1+ (1,0) [0|0] "" Gateway
	SG_ EngTemp : 16|7@1+ (2,-50) [-50|150] "degC" Gateway 
	SG_ EngSpeed : 0|16@1+ (1,0) [0|8000] "rpm" Gateway

CM_ "CAN communication matrix for power train electronics
VAL_ 100 IdleRunning 0 "Running" 1 "Idle" ;

BS_:[baudrate:BTR1,BTR2];
//波特率设置,BS_为关键字,其中[]中为可选部分
BU_:Nodename1 Nodename2 Nodename3 ……
//网络节点定义,其中Nodename为网络节点名字,用户自定义,名字需要唯一性

1、报文消息数据格式解读 BO_

格式:
BO_ MessageId MessageName: MessageSize Transmitter
			
		MessageId 			为10进制表示的报文ID,类型为longlogn型,即CAN ID
		MessageName 		报文的名字,与C语言命令规范相同
		MessageSize 		报文数据段字节数
		Transmitter 		该报文的网络节点,如果该报文没有指定发送节点,则该值需设置为”Vector__XXX”

举例:
以下是DBC中代表一条消息的描述信息
BO_ 1015 IPK_ODO_Consump: 8 Vector__XXX 


解释
BO_								代表一条消息的起始标识
1015							消息ID的十进制形式,=0x3f7
IPK_ODO_Consump					消息名
:								分割符号
8								消息报文长度,帧字节数
Vector__XXX						发出该消息的网络节点,标识为Vector__XXX时未指明具体节点

2、信号信息数据格式解读 SG_

格式
SG_ SignalName (SigTypeDefinition) : StartBit|SignalSize@ByteOrder ValueType (Factor,Offset) [Min|Max] Unit Receiver

		SignalName (SigTypeDefinition) 	表示该信号的名字 和 多路选择信号的定义
		SigTypeDefinition	是可选项,有3种格式:
				a> 空
				b> M 表示多路选择器信号
				c> m50 表示被多路选择器选择的信号,50表示当‘M’定义的信号的值等于50的时候,该报文使用此通路
		StartBit|SignalSize 表示该信号的起始位及信号长度
		ByteOrder 	表示信号的字节顺序:0代表Motorola格式,1代表Inter格式
		ValueType 	表示该信号的数值类型:+表示无符号数,-表示有符号数
		Factor,Offset 	表示因子,偏移量;这两个值用于信号的原始值与物理值之间的转换。 转换公式:物理值=原始值*因子+偏移量
		Min|Max 	表示该信号的最小值和最大值,即指定了该信号值的范围;这两个值为double类型
		Unit 	表示该信号的物理单位,为字符串类型
		Receiver 表示该信号的接收节点(可以是多个节点);若该信号没有指定的接收节点,则必须设置为” Vector__XXX”

举例:
每条报文消息里面有多个报文信号,报文信号的信息的起始标识为"SG_", 
它以一个"BO_"开始至下一"BO_"之间的内容止,详细报文消息以缩进1或2个空格符形式类似树图子节点的方式呈现。
一条消息下的一个信号的信息,此处缩进一个空格
BO_ 100 EngineData: 8 Engine
	SG_ IPK_EVDTEodometer : 7|12@0+ (1,0) [0|999] "km"  TBOX

解释:
SG_							代表一个信号信息的起始标识
IPK_EVDTEodometer			信号名,分长名与短名,此处是短名。长名非必须存在,可以不定义
:							分割符号
7							信号起始bit
|							分割符号
12							信号总长度
@0+							@0表示是Motorola格式(Intel格式是1),+表示是无符号数据
(1,0)						(精度值,偏移值)
[0|999]						[最小值|最大值], 物理意义的最小与最大,现实世界的有物理意义的值,比如此处仪表续航里程最大999KM
"km"						"单位"
TBOX						接收处理此信号的节点,同样可以不指明,写为Vector__XXX

2.1 Motorola格式与Intel格式

填格子的方式分为Intel和Motorola两种:
Intel
	格式也即小端,MSB存放在高字节单元,反映到矩阵图中就是以起始位为原点,自上而下填充。
	
Motorola
	格式也即大端,MSB存放在低字节单元,反映到矩阵图中就是以起始位为原点,自下而上填充
	
这个决定了信号起始bit,  生成报文计算信号值时的大小端算法。

2.2 精度值与偏移量

物理值与信号值的关系公式: 
					信号值*精度值 + 偏移量 = 物理值
总线上报文消息中传递的是信号值,当此信号传递到ECU时,需转换为物理意义的值在输出接口显示。

举例:
SG_ TCU_TransOilTemp : 7|8@0+ (1,-40) [-40|214] "°C"  TBOX

若传感器显示16度,则: 
	信号值=(物理值-偏移量)/精度值 = (16 - (-40))/1 = 56(dec) = 0x38
	报文呈现为“38,00,00,00,00,00,00,00”  (此处00表示未设置信号,","分割字节,以上同)

2.3 消息与信号的详细描述 CM_

CM_ Object MessageId/NodeName “Comment”  

		Object 		表示注解的对象类型,可以是节点“BU_”、报文“BO_”、消息”SG_”
		MessageId/NodeName 
					表示进行注解的对象,若前面的对象类型是信号或者报文,则这里的值应为报文的ID(10进制数表示);
					若前面的对象类型为节点,则这里的值应为节点的名字
		Comment 	表示进行注解的文本信息

举例:
CM_ BO_ 1015 "Transmitted by IPK, power consumption and fuel consumption";
CM_ SG_ 1015 IPK_EVDTEodometer "The EV DTE odometer ";

解释:
CM_    起始标识,我猜测CM_为comment缩写

2.4 消息发送周期 BA_

举例:
BA_ "GenMsgCycleTime" BO_ 1015 1000;

解释:
BA_  起始标识,描述消息与信号更详尽的信息。 以上语句描述了消息的周期,单位ms

2.5 信号默认值,起始值 BA_

举例:
BA_ "GenSigStartValue" SG_ 1015 IPK_EVDTEodometer 4095;

解释:
BA_   起始标识,描述消息与信号更详尽的信息。 以上语句描述了消息中具体信号的初始值,十进制表示

2.6 值枚举或特殊值列举或取值范围描述 VAL_

格式:
VAL_ MessageId SignalName N "DefineN" …… 0 "Define0";		

			MessageId					表示该信号所属的报文ID(10进制数表示)
			SignalName					表示信号名
			N "DefineN" …… 0 "Define0"	表示定义的数值表内容,即该信号的有效值分别用什么符号表示
			
举例:
VAL_ 1015 IPK_EVDTEodometer 4095 "Invalid" ;

解释:
VAL_  起始标识符,对信号值的描述

3、特征(属性)BA_DEF_ , BA_DEF_DEF, BA_

格式:
BA_DEF_ Object AttributeName ValueType Min Max;		

			Object 特征类型,可以是:
					BU_(节点特征定义)
					BO_(报文特征定义)
					SG_(信号特征定义)
					空格(项目特征定义)
			AttributeName 
					特征名称(C语言变量格式)
			ValueType
					特征值类型(只能是十进制、十六进制、浮点数、枚举、字符5种类型)
			Min Max 
					数值类型这里出现范围,枚举类型这里是枚举值,字符类型,这里是空
															
BA_DEF_DEF_ AttributeName DefaultValue;				

			AttributeName 	特征名称
			DefaultValue 	该特征的默认设置值
															
BA_ AttributeName projectValue;						

			AttributeName 	特征名称
			projectValue 	该特征的设置值

Python 解析 DBC

下面推荐一篇从开源社区看到的一篇:

说一下下述文章的读后感吧,毕竟我也从头看到尾,而且很多细节需要自己改

  1. 下述文章总体思路是没有问题
  2. dbc文件每个人的都不同,所以下述脚本由一些细节地方需要手改
  3. 报错是肯定会的,关键在于改
  4. 最关键的是吸收思路
#! python3

"""
    File:       dbcParser.py
    Created:    03/29/2017

    This Python script contains classes for describing the contents of a CAN
    database file. Given a .dbc file, it will parse it into objects in memory
    and generate a C header file for use by the Network Manager embedded
    firmware application for packaging and unpackaging data in CAN data frames.

    Before editing, please read the EVT Wiki page describing the objects in a
    CAN Database, located here:
        https://wiki.rit.edu/display/EVT/CAN+Database
"""

import re

__regex_pattern__ = re.compile(r""" SG_ (?P<name>.*) : (?P<start_bit>[0-9]{1,2})\|(?P<length>[0-9]{1,2})@(?P<format>[0-1])(?P<type>[+-]) \((?P<factor>.*),(?P<offset>.*)\) \[(?P<min>.*)\|(?P<max>.*)\] "(?P<unit>.*)"(\s{1,2})(?P<rx_nodes>.*)""")


class CANDatabase:
    """
    Object to hold all CAN messages in a network as defined by the DBC file.
    """

    # Private Properties
    _name = ""
    _dbcPath = ""
    _comment = ""
    _messages = list()
    _txNodes = list()
    _extended = False
    _attributes = list()
    _iter_index = 0

    def __init__(self, dbc_path):
        """
        Constructor for the CAN Database.

        Arguments:
         - dbcPath: The file path to .dbc file.
        """
        self._dbcPath = dbc_path

    def __iter__(self):
        """
        Defined to make the object iterable.
        """
        return self

    def __next__(self):
        """
        Get the next iterable in the CANMessage list.
        """
        if self._iter_index == len(self._messages):
            self._iter_index = 0
            raise StopIteration
        self._iter_index += 1
        return self._messages[self._iter_index-1]

    def Load(self):
        """
        Opens the DBC file and parses its contents.
        """
        try:
            file = open(self._dbcPath)
        except OSError:
            print("Invalid file path specified.")
            print(self._dbcPath)
            return

        building_message = False
        can_msg = None

        line_number = 0
        for line in file:
            line = line.rstrip('\n')
            line_number += 1  # keep track of the line number for error reporting

            if line.startswith("BU_:"):
                self._parseTransmittingNodes(line)

            elif line.startswith("BO_"):
                can_msg = self._parseMessageHeader(line)
                building_message = True

            elif line.startswith(" SG_") and building_message:
                can_msg.AddSignal(self._parseSignalEntry(line))

            elif line == "":
                if building_message:
                    building_message = False
                    self._messages += [can_msg]
                    can_msg.UpdateSubscribers()
                    can_msg = None

            elif line.startswith("VAL_"):
                val_components = valueLineSplit(line)
                new_value_name = val_components[2]
                new_value_canID = int(val_components[1], 16)
                # Tuple: (Name, CAN ID, Item Pairs)
                new_value = (new_value_name, new_value_canID, list())

                pairs = val_components[3:]
                for i in range(0, len(pairs), 2):
                    try:
                        # add item value/name pairs to list in new_value tuple
                        item_value = int(pairs[i])
                        item_name = pairs[i+1]
                        new_value[2].append((item_value, item_name))
                    except IndexError:
                        print("Invalid value: " + new_value_name + 
                              ". Found on line " + str(line_number) + '.')
                        return None

                for message in self:
                    if message.CANID() == new_value[1]:
                        message.AddValue(new_value)
                        break

            # parse attributes
            elif line.startswith("BA_DEF_ BO_"):
                components = line.split(' ')
                # warning: indices are one higher than they appear to be because of double space in line
                attr_name = components[3].strip('"')
                attr_type = components[4]
                attr_min = components[5]
                attr_max = components[6].rstrip(';')

                new_attr = (attr_name, attr_type, attr_min, attr_max)
                self._attributes.append(new_attr)

            elif line.startswith("BA_ "):
                components = line.split(' ')
                attr_name = components[1].strip('"')
                attr_msgID = int(components[3])
                attr_val = components[4].rstrip(';')

                new_attr = (attr_name, attr_val)

                for message in self:
                    if message.CANID() == attr_msgID:
                        message.AddAttribute(new_attr)
                        break

            elif line.startswith("CM_"):
                components = line.split(' ')
                if len(components) <= 2:
                    break
                for message in self:
                    if message.CANID() == int(components[2]):
                        for signal in message:
                            if signal.Name() == components[3]:
                                comment_str = ''
                                for each in components[4:]:
                                    comment_str += each
                                signal.AddComment(comment_str)
                                break
                    break

        self._iter_index = 0

        return self

    def Name(self):
        """
        Gets the CAN Database's name.
        """
        return self._name

    def Messages(self):
        """
        Gets the list of CANMessage objects.
        """
        return self._messages

    def _parseTransmittingNodes(self, line):
        """
        Takes a string and parses the name of transmitting nodes in the CAN bus
        from it.
        """
        items = line.split(' ')
        for each in items:
            if each == "BU_:":
                pass
            else:
                self._txNodes += [each]

        return

    def _parseMessageHeader(self, line):
        """
        Creates a signal-less CANMessage object from the header line.
        """
        items = line.split(' ')
        msg_id = int(items[1])
        msg_name = items[2].rstrip(':')
        msg_dlc = int(items[3])
        msg_tx = items[4].rstrip('\n')

        return CANMessage(msg_id, msg_name, msg_dlc, msg_tx)

    def _parseSignalEntry(self, line):
        """
        Creates a CANSignal object from DBC file information.

        The Regex used is compiled once in order to save time for the numerous
        signals being parsed.
        """
        result = __regex_pattern__.match(line)

        name = result.group('name')
        start_bit = int(result.group('start_bit'))
        length = int(result.group('length'))
        sig_format = int(result.group('format'))
        sig_type = result.group('type')
        factor = int(result.group('factor'))
        offset = int(result.group('offset'))
        minimum = int(result.group('min'))
        maximum = int(result.group('max'))
        unit = result.group('unit')
        rx_nodes = result.group('rx_nodes').split(',')

        return CANSignal(name, sig_type, sig_format, start_bit, length, offset,
                         factor, minimum, maximum, unit, rx_nodes)


class CANMessage:
    """
    Contains information on a message's ID, length in bytes, transmitting node,
    and the signals it contains.
    """

    def __init__(self, msg_id, msg_name, msg_dlc, msg_tx):
        """
        Constructor.
        """
        self._canID = msg_id
        self._name = msg_name
        self._dlc = msg_dlc
        self._txNode = msg_tx
        self._idType = None
        self._comment = ""
        self._signals = list()
        self._attributes = list()
        self._iter_index = 0
        self._subscribers = list()

    def __iter__(self):
        """
        Defined to make the object iterable.
        """
        self._iter_index = 0
        return self

    def __next__(self):
        """
        Defines the next CANSignal object to be returned in an iteration.
        """
        if self._iter_index == len(self._signals):
            self._iter_index = 0
            raise StopIteration
        self._iter_index += 1
        return self._signals[self._iter_index-1]

    def AddSignal(self, signal):
        """
        Takes a CANSignal object and adds it to the list of signals.
        """
        self._signals += [signal]
        return self

    def Signals(self):
        """
        Gets the signals in a CANMessage object.
        """
        return self._signals

    def SetComment(self, comment_str):
        """
        Sets the Comment property for the CANMessage.
        """
        self._comment = comment_str

        return self

    def CANID(self):
        """
        Gets the message's CAN ID.
        """
        return self._canID

    def AddValue(self, value_tuple):
        """
        Adds a enumerated value mapping to the appropriate signal.
        """
        for signal in self:
            if signal.Name() == value_tuple[0]:
                signal.SetValues(value_tuple[2])
                break
        return self

    def AddAttribute(self, attr_tuple):
        """
        Adds an attribute to the message.
        """
        self._attributes.append(attr_tuple)
        return self

    def Attributes(self):
        return self._attributes

    def Name(self):
        return self._name

    def TransmittingNode(self):
        return self._txNode

    def DLC(self):
        return self._dlc

    def UpdateSubscribers(self):
        """
        Iterates through signals in the message to note all of the receiving
        nodes subscribed to the message.
        """
        for signal in self:
            nodes = signal.RxNodes()
            for each in nodes:
                if each not in self._subscribers:
                    self._subscribers += [each]

        return self


class CANSignal:
    """
    Contains information describing a signal in a CAN message.
    """

    def __init__(self, name, sigtype, sigformat, startbit, length, offset, factor,
                 minVal, maxVal, unit, rx_nodes):
        """
        Constructor.
        """
        self._name = name
        self._type = sigtype
        self._format = sigformat
        self._startbit = startbit
        self._length = length
        self._offset = offset
        self._factor = factor
        self._minVal = minVal
        self._maxVal = maxVal
        self._units = unit
        self._values = list()
        self._comment = ""
        self._rx_nodes = rx_nodes

    def __lt__(self, other):
        return self._startbit < other._startbit

    def Name(self):
        """
        Gets the name of the CANSignal.
        """
        return self._name

    def SetValues(self, values_lst):
        """
        Sets the enumerated value map for the signal's data.
        """
        self._values = values_lst
        return self

    def Length(self):
        return self._length

    def SignType(self):
        return self._type

    def AddComment(self, cm_str):
        self._comment = cm_str

    def ReadComment(self):
        return self._comment

    def RxNodes(self):
        return self._rx_nodes


def valueLineSplit(line):
    """
    Custom split function for splitting up the components of a value line.

    Could not use normal String.split(' ') due to spaces in some of the value
    name strings.
    """
    components = list()
    part = ""
    in_quotes = False
    for ch in line:
        if ch == ' ' and not in_quotes:
            components.append(part)
            part = ""
        elif ch == '"' and not in_quotes:
            in_quotes = True
        elif ch == '"' and in_quotes:
            in_quotes = False
        else:
            part += ch
    return components


def main():
    """
    Opens a DBC file and parses it into a CANDatabase object and uses the
    information to generate a C header file for the Network Manager
    application.
    """
    file = "EVT_CAN.dbc"
    candb = CANDatabase(file)
    candb.Load()
    input()

if __name__ == "__main__":
    main()

解析了DBC能做什么

我有一些想法:

  1. 通过解析dbc自动生成CAPL Simulation代码
  2. 汇总dbc信息,自动生成测试脚本
  3. 或者大家有什么想法请在下方评论留言

猜你喜欢

转载自blog.csdn.net/qq_33704787/article/details/127463082