DBC python解析

由于设计自定义开发,使用系统库经常遇到格式/占位问题,一气之下结合AI自定义一个类,够用即可,不够续加代码解析。
测试结果:
常规解析结果跟系统库一致的,但是由于懒得判断,统一使用float来处理,结果显示多个0其实无碍

2025-07-25 修改:eval接口尽量不用,否则解码速度很低,最好约定标准格式

 

Intel格式

Intel格式即小端,MSB存放在高字节单元,反映到矩阵图中就是以起始位为原点,自上而下填充。Intel格式,msb在lsb下面。

比如46这个数字,换为二进制为:101110,长度为6个bit

如果起始位为20的话,那么格式如下图:对应CAN数据为:00 00 E0 02 00 00 00

 

Motorola格式

Motorola格式即大端,MSB存放在低字节单元,反映到矩阵图中就是以起始位为原点,自下而上填充。Motorola格式,msb在lsb上面。

Motorola_LSB排列格式

比如46这个数字,换为二进制为:101110,长度为6个bit

如果起始位为20的话,那么格式如下图:对应CAN数据为:00 02 E0 00 00 00 00

Motorola_MSB排列格式

比如46这个数字,换为二进制为:101110,长度为6个bit

如果起始位为20的话,那么格式如下图:对应CAN数据为:00 00 17 00 00 00 00

描述取自:Intel格式与Motorola格式的区别 - 哔哩哔哩 (bilibili.com)

import re
from typing import  Optional, Tuple


class DBCParser:
    """解析DBC文件并存储其中的报文和信号信息"""

    def __init__(self):
        self.messages = {}  # 存储报文信息:message_id -> message_info
        self.signal_formulas = {}  # 存储信号的物理值计算公式:(message_id, signal_name) -> formula_info

    def parse(self, file_path: str) -> None:
        """
        解析DBC文件

        Args:
            file_path: DBC文件路径
        """

        import chardet
        with open(file_path, "rb") as f:
            result = chardet.detect(f.read(10000))  # 检测前10KB内容

        with open(file_path, 'r', encoding = result['encoding']) as file:
            lines = file.readlines()

        i = 0
        while i < len(lines):
            line = lines[i].strip()

            # 解析报文定义
            if line.startswith("BO_ "):
                message_id = int(line.split()[1])
                message_name = line.split()[2].rstrip(':')
                message_size = int(line.split()[3])

                # 收集报文中的信号行
                signals = []
                i += 1
                while i < len(lines) and lines[i].strip().startswith("SG_ "):
                    signals.append(lines[i].strip())
                    i += 1

                # 解析信号
                parsed_signals = {}
                for signal_line in signals:
                    signal_info = self._parse_signal(signal_line)
                    parsed_signals[signal_info["name"]] = signal_info

                # 存储报文信息
                self.messages[message_id] = {
                    "name"   : message_name,
                    "size"   : message_size,
                    "signals": parsed_signals
                }

            # 解析信号值描述(可选)
            # 解析信号值描述(可选)
            elif line.startswith("VAL_ "):
                # 这里可以添加对信号值描述的解析
                val_pattern = re.compile(r'VAL_\s+(\d+)\s+(\w+)\s+(.*?)\s*;', re.DOTALL)
                val_entries = val_pattern.findall(line)

                for msg_id, sig_name, val_def in val_entries:   # 取决于自定义格式
                    if int(msg_id) not in self.messages.keys():
                        continue  # 不符合原来的属性

                    dsig = self.messages[int(msg_id)]['signals']
                    if sig_name not in dsig.keys():
                        continue  # 不符合原来的属性

                    # 解析数值与描述的映射
                    dsig[sig_name]['val_map'] = {}
                    parts = re.findall(r'(\d+)\s+"([^"]*)"', val_def)
                    for num_val, text_desc in parts:
                        dsig[sig_name]['val_map'][float(num_val)] = text_desc

                i += 1

            # 解析信号计算公式
            elif line.startswith("BA_DEF_ SG_ "):
                i += 1

            # 解析信号CM_
            elif line.startswith("CM_"):
                # 解析信号注释
                sg_comments = re.findall(r'^\s*CM_\s.+?\s(.+?)\s(.+?)\s"([^"]*)"', line)
                for msg_id, sig_name, comment in sg_comments:
                    if int(msg_id) not in self.messages.keys():
                        continue  # 不符合原来的属性

                    dsig = self.messages[int(msg_id)]['signals']
                    if sig_name not in dsig.keys():
                        continue  # 不符合原来的属性

                    # 解析数值与描述的映射 长命
                    dsig[sig_name]['lname'] = comment

                i += 1

            else:
                i += 1

    def _parse_signal(self, line: str) -> dict:
        """解析信号定义行"""
        def get_val_typ(ssval: str, ityp=0):
            sval = ssval.replace(' ', '')
            if ityp == 0:
                return int(sval)
            else:
                return float(sval)

        lre = re.compile(r'^\s*SG_\s(.+?):\s(.+?)\|(.+?)@(.+?)([+-]).?\((.+?),(.+?)\).?\[(.+?)\|(.+?)\].?"([^"]*)"(.*)', re.IGNORECASE)
        fre = lre.findall(line, endpos = len(line))[0]
        if len(fre):
            start_bit = get_val_typ(fre[1])
            bit_length = get_val_typ(fre[2])

            # 提前预解析
            isbig_msb = True
            if '0' in fre[3]: # @0 为大端
                # big_msb 不是顺序bit  big_lsb 是反向解析
                cal_start_bit = (start_bit // 8) * 8 + (7 - start_bit % 8)
                cal_end_bit = cal_start_bit + bit_length
            else:
                cal_start_bit = start_bit
                cal_end_bit = start_bit + bit_length

            return {
                "name"      : str(fre[0]).strip(),
                "cal_s_bit" : cal_start_bit,
                "cal_e_bit":  cal_end_bit,
                "isbig_or": '0' in fre[3],  # @0 为大端
                "isigned" : '-' in fre[4],
                "isbig_msb" : isbig_msb,
                "sign_mask": 1 << (bit_length - 1),
                "sign_val": 1 << bit_length,
                "factor"    : get_val_typ(fre[5], 1),
                "offset"    : get_val_typ(fre[6], 1),
                "min"       : get_val_typ(fre[7], 1),
                "max"       : get_val_typ(fre[8], 1),
                "unit"      : fre[9]
            }
        return {}

    def _parse_formula_definition(self, line: str) -> Optional[dict]:
        """解析公式定义"""
        if "GenSigFunc" not in line:
            return None

        # 提取公式名称
        formula_name_match = re.search(r'"([^"]*)"', line)
        if not formula_name_match:
            return None

        formula_name = formula_name_match.group(1)

        # 提取参数
        param_pattern = r'$([^)]+)$'
        param_match = re.search(param_pattern, line)
        if not param_match:
            return None

        params = [p.strip() for p in param_match.group(1).split(",")]

        return {
            "name"  : formula_name,
            "params": params
        }

    def _parse_signal_formula(self, line: str, formula_info: dict) -> Tuple[int, str]:
        """解析信号的公式应用"""
        # 提取消息ID和信号名称
        pattern = r'BA_\s+"{}"\s+SG_\s+(\d+)\s+(\w+);'.format(formula_info["name"])
        match = re.search(pattern, line)

        if not match:
            return None

        message_id = int(match.group(1))
        signal_name = match.group(2)

        # 将公式与特定信号关联
        self.signal_formulas[(message_id, signal_name)] = {
            "formula_name": formula_info["name"],
            "params"      : formula_info["params"]
        }

        return (message_id, signal_name)

    def get_message_info(self, message_id: int) -> dict:
        """获取报文信息"""
        return self.messages.get(message_id, None)

    def get_all_messages(self) -> dict:
        """获取所有报文信息"""
        return self.messages

    def parse_data(self, message_id: int, recv_data: bytes) -> dict:
        """
        解析原始CAN数据帧,返回各信号的物理值

        Args:
            message_id: 报文ID
            recv_data: 原始CAN数据 (bytes, 长度为1~8)

        Returns:
            dict: {信号名: 物理值}
        """
        if message_id not in self.messages.keys():
            # raise ValueError(f"未找到报文ID {hex(message_id)} 的定义")
            return {}

        signals = self.messages[message_id].get("signals", {})
        result = {}

        int_allbits = ''
        big_allbits = ''
        for i in recv_data:
            bits = f"{i:08b}"
            big_allbits += bits
            int_allbits += bits[::-1]

        for signal_name, signal_info in signals.items():
            if signal_info["isbig_or"] :
                cal_value = big_allbits[signal_info['cal_s_bit']: signal_info['cal_e_bit']]
                if not signal_info['isbig_msb']:
                    cal_value = cal_value[::-1]
            else:
                cal_value = int_allbits[signal_info['cal_s_bit']: signal_info['cal_e_bit']][::-1]

            cal_value = int(cal_value, 2)  # 2为底的

            # 6. 处理有符号数(补码)
            if signal_info["isigned"] and cal_value & signal_info["sign_mask"]:
                    cal_value -= signal_info["sign_val"]

            # 7. 应用公式计算物理值
            end_val = signal_info['factor'] * cal_value + signal_info['offset']
            if end_val > signal_info["max"]:
                end_val = signal_info["max"]
            elif end_val < signal_info["min"]:
                end_val = signal_info["min"]

            if signal_info.get('val_map', False):
                end_val = signal_info['val_map'].get(end_val, end_val)

            signal_name = signal_info.get('lname', signal_name)
            result[signal_name] = (end_val, str(signal_info['unit']).replace('N/A', ''))

        return result
# 使用示例
if __name__ == "__main__":
    data = bytearray([33] * 8)
    msg_id = 100

    parser = DBCParser()
    parser.parse("tets.dbc")  # 替换为实际的DBC文件路径

    parsed_values = parser.parse_data(msg_id, data)
    for signal, value in parsed_values.items():
        print(f"解码 {signal}: {value}")

  


  

VERSION "1.0"

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_
    SG_MUL_VAL_

BS_:

BU_: Engine ECU Transmission

BO_ 100 EngineSpeedMsg: 8 Engine
    SG_ Signal1 : 0|8@0+ (1,0) [0|255000000] "Unit1" ECU2
    SG_ Signal2 : 20|16@0+ (1,0) [0|25500000] "Unit1" ECU2

  

 

 

import re
from typing import Optional, Tuple


class DBCParser:
"""解析DBC文件并存储其中的报文和信号信息"""

def __init__(self):
self.messages = {} # 存储报文信息:message_id -> message_info
self.signal_formulas = {} # 存储信号的物理值计算公式:(message_id, signal_name) -> formula_info

def parse(self, file_path: str) -> None:
"""
解析DBC文件

Args:
file_path: DBC文件路径
"""

import chardet
with open(file_path, "rb") as f:
result = chardet.detect(f.read(10000)) # 检测前10KB内容

with open(file_path, 'r', encoding = result['encoding']) as file:
lines = file.readlines()

i = 0
while i < len(lines):
line = lines[i].strip()

# 解析报文定义
if line.startswith("BO_ "):
message_id = int(line.split()[1])
message_name = line.split()[2].rstrip(':')
message_size = int(line.split()[3])

# 收集报文中的信号行
signals = []
i += 1
while i < len(lines) and lines[i].strip().startswith("SG_ "):
signals.append(lines[i].strip())
i += 1

# 解析信号
parsed_signals = {}
for signal_line in signals:
signal_info = self._parse_signal(signal_line)
parsed_signals[signal_info["name"]] = signal_info

# 存储报文信息
self.messages[message_id] = {
"name" : message_name,
"size" : message_size,
"signals": parsed_signals
}

# 解析信号值描述(可选)
# 解析信号值描述(可选)
elif line.startswith("VAL_ "):
# 这里可以添加对信号值描述的解析
val_pattern = re.compile(r'VAL_\s+(\d+)\s+(\w+)\s+(.*?)\s*;', re.DOTALL)
val_entries = val_pattern.findall(line)

for msg_id, sig_name, val_def in val_entries: # 取决于自定义格式
if int(msg_id) not in self.messages.keys():
continue # 不符合原来的属性

dsig = self.messages[int(msg_id)]['signals']
if sig_name not in dsig.keys():
continue # 不符合原来的属性

# 解析数值与描述的映射
dsig[sig_name]['val_map'] = {}
parts = re.findall(r'(\d+)\s+"([^"]*)"', val_def)
for num_val, text_desc in parts:
dsig[sig_name]['val_map'][float(num_val)] = text_desc

i += 1

# 解析信号计算公式
elif line.startswith("BA_DEF_ SG_ "):
i += 1

# 解析信号CM_
elif line.startswith("CM_"):
# 解析信号注释
sg_comments = re.findall(r'^\s*CM_\s.+?\s(.+?)\s(.+?)\s"([^"]*)"', line)
for msg_id, sig_name, comment in sg_comments:
if int(msg_id) not in self.messages.keys():
continue # 不符合原来的属性

dsig = self.messages[int(msg_id)]['signals']
if sig_name not in dsig.keys():
continue # 不符合原来的属性

# 解析数值与描述的映射 长命
dsig[sig_name]['lname'] = comment

i += 1

else:
i += 1

def _parse_signal(self, line: str) -> dict:
"""解析信号定义行"""
def get_val_typ(ssval: str, ityp=0):
sval = ssval.replace(' ', '')
if ityp == 0:
return int(sval)
else:
return float(sval)

lre = re.compile(r'^\s*SG_\s(.+?):\s(.+?)\|(.+?)@(.+?)([+-]).?\((.+?),(.+?)\).?\[(.+?)\|(.+?)\].?"([^"]*)"(.*)', re.IGNORECASE)
fre = lre.findall(line, endpos = len(line))[0]
if len(fre):
start_bit = get_val_typ(fre[1])
bit_length = get_val_typ(fre[2])

# 提前预解析
isbig_msb = True
if '0' in fre[3]: # @0 为大端
# big_msb 不是顺序bit big_lsb 是反向解析
cal_start_bit = (start_bit // 8) * 8 + (7 - start_bit % 8)
cal_end_bit = cal_start_bit + bit_length
else:
cal_start_bit = start_bit
cal_end_bit = start_bit + bit_length

return {
"name" : str(fre[0]).strip(),
"cal_s_bit" : cal_start_bit,
"cal_e_bit": cal_end_bit,
"isbig_or": '0' in fre[3], # @0 为大端
"isigned" : '-' in fre[4],
"isbig_msb" : isbig_msb,
"sign_mask": 1 << (bit_length - 1),
"sign_val": 1 << bit_length,
"factor" : get_val_typ(fre[5], 1),
"offset" : get_val_typ(fre[6], 1),
"min" : get_val_typ(fre[7], 1),
"max" : get_val_typ(fre[8], 1),
"unit" : fre[9]
}
return {}

def _parse_formula_definition(self, line: str) -> Optional[dict]:
"""解析公式定义"""
if "GenSigFunc" not in line:
return None

# 提取公式名称
formula_name_match = re.search(r'"([^"]*)"', line)
if not formula_name_match:
return None

formula_name = formula_name_match.group(1)

# 提取参数
param_pattern = r'$([^)]+)$'
param_match = re.search(param_pattern, line)
if not param_match:
return None

params = [p.strip() for p in param_match.group(1).split(",")]

return {
"name" : formula_name,
"params": params
}

def _parse_signal_formula(self, line: str, formula_info: dict) -> Tuple[int, str]:
"""解析信号的公式应用"""
# 提取消息ID和信号名称
pattern = r'BA_\s+"{}"\s+SG_\s+(\d+)\s+(\w+);'.format(formula_info["name"])
match = re.search(pattern, line)

if not match:
return None

message_id = int(match.group(1))
signal_name = match.group(2)

# 将公式与特定信号关联
self.signal_formulas[(message_id, signal_name)] = {
"formula_name": formula_info["name"],
"params" : formula_info["params"]
}

return (message_id, signal_name)

def get_message_info(self, message_id: int) -> dict:
"""获取报文信息"""
return self.messages.get(message_id, None)

def get_all_messages(self) -> dict:
"""获取所有报文信息"""
return self.messages

def parse_data(self, message_id: int, recv_data: bytes) -> dict:
"""
解析原始CAN数据帧,返回各信号的物理值

Args:
message_id: 报文ID
recv_data: 原始CAN数据 (bytes, 长度为1~8)

Returns:
dict: {信号名: 物理值}
"""
if message_id not in self.messages.keys():
# raise ValueError(f"未找到报文ID {hex(message_id)} 的定义")
return {}

signals = self.messages[message_id].get("signals", {})
result = {}

int_allbits = ''
big_allbits = ''
for i in recv_data:
bits = f"{i:08b}"
big_allbits += bits
int_allbits += bits[::-1]

for signal_name, signal_info in signals.items():
if signal_info["isbig_or"] :
cal_value = big_allbits[signal_info['cal_s_bit']: signal_info['cal_e_bit']]
if not signal_info['isbig_msb']:
cal_value = cal_value[::-1]
else:
cal_value = int_allbits[signal_info['cal_s_bit']: signal_info['cal_e_bit']][::-1]

cal_value = int(cal_value, 2) # 2为底的

# 6. 处理有符号数(补码)
if signal_info["isigned"] and cal_value & signal_info["sign_mask"]:
cal_value -= signal_info["sign_val"]

# 7. 应用公式计算物理值
end_val = signal_info['factor'] * cal_value + signal_info['offset']
if end_val > signal_info["max"]:
end_val = signal_info["max"]
elif end_val < signal_info["min"]:
end_val = signal_info["min"]

if signal_info.get('val_map', False):
end_val = signal_info['val_map'].get(end_val, end_val)

signal_name = signal_info.get('lname', signal_name)
result[signal_name] = (end_val, str(signal_info['unit']).replace('N/A', ''))

return result
posted @ 2025-07-10 10:28  默*为  阅读(55)  评论(0)    收藏  举报