从dbc到can解析(二)

通过python或者是c++来实现对can信号的解析过程.

python实现

得益与python中封装的非常好的库,比如cantools,我们可以直接通过dbc文件解析对应的信号,这里分别从解析can消息和发送can消息两个层面来说明其用法。
首先要安装一些常用的库:

pip install python-can cantools
sudo apt install can-utils

创建虚拟can设备

can消息解析之前,我们先通过工具创建虚拟的can设备,运行这个脚本:

echo -e "${YELLOW}================ 开始创建虚拟CAN设备 ================${NC}"

# 安装can-utils(确保必备工具)
echo -e "${YELLOW}正在安装can-utils工具...${NC}"
sudo apt-get install -y can-utils
if [ $? -ne 0 ]; then
    echo -e "${RED}can-utils安装失败,请检查网络或手动安装${NC}"
    return
fi
echo -e "${GREEN}can-utils安装成功${NC}"

# 加载vcan内核模块
echo -e "${YELLOW}正在加载vcan内核模块...${NC}"
sudo modprobe vcan
if [ $? -ne 0 ]; then
    echo -e "${RED}vcan模块加载失败,请检查内核支持${NC}"
    return
fi
echo -e "${GREEN}vcan模块加载成功${NC}"

# 获取用户输入的设备名称
read -p "请输入要创建的CAN设备名称 (例如: can0): " can_device

# 检查设备是否已存在
if ip link show $can_device >/dev/null 2>&1; then
    echo -e "${YELLOW}设备 $can_device 已存在,是否启动它? (y/n): ${NC}"
    read -p "" choice
    if [[ $choice == "y" || $choice == "Y" ]]; then
        sudo ip link set $can_device up
        if [ $? -eq 0 ]; then
            echo -e "${GREEN}设备 $can_device 已启动${NC}"
        else
            echo -e "${RED}启动设备失败${NC}"
        fi
        return
    else
        echo -e "${YELLOW}已取消操作${NC}"
        return
    fi
fi

# 创建虚拟CAN设备
echo -e "${YELLOW}正在创建设备 $can_device...${NC}"
sudo ip link add dev $can_device type vcan
if [ $? -eq 0 ]; then
    # 启动设备
    sudo ip link set $can_device up
    if [ $? -eq 0 ]; then
        echo -e "${GREEN}虚拟CAN设备 $can_device 创建并启动成功${NC}"
        echo -e "${YELLOW}测试命令:${NC}"
        echo -e "  ${CYAN}cansend $can_device 123#11223344${NC}"
        echo -e "  ${CYAN}candump $can_device${NC}"
    else
        echo -e "${YELLOW}设备创建成功但启动失败,已创建未启动的设备${NC}"
        echo -e "${YELLOW}启动命令:${NC} ${CYAN}sudo ip link set $can_device up${NC}"
    fi
else
    echo -e "${RED}创建设备 $can_device 失败,请尝试其他名称${NC}"
fi

echo -e "${YELLOW}================ 虚拟CAN设备创建完成 ================${NC}"

在这里插入图片描述
只需要创建对应的虚拟can的名称,比如这里can0
然后可以通过ifconfig查询到对应的设备:
在这里插入图片描述
由于是虚拟设备,波特率可以是任意的,所以不需要设置。

发送can消息

运行send_can.py
python3 send_can.py

import cantools
import can
import time
import random
from typing import Dict, Any

def load_dbc(dbc_file: str) -> cantools.database.Database:
    """Load the DBC file."""
    try:
        return cantools.database.load_file(dbc_file)
    except Exception as e:
        print(f"Error loading DBC file: {e}")
        raise

def get_cycle_times(db: cantools.database.Database) -> Dict[int, float]:
    """Extract cycle times from DBC file attributes."""
    cycle_times = {}
    for message in db.messages:
        cycle_time = message.cycle_time / 1000.0 if message.cycle_time else 0.1  # Default to 100ms if not specified
        cycle_times[message.frame_id] = cycle_time
    return cycle_times

def assign_signal_values(db: cantools.database.Database) -> Dict[str, Dict[str, Any]]:
    """Define signal values for each message. Modify this to set desired values."""
    signal_values = {
        'Vehicle_Mileage2': {
            'Vehicle_Mileage2_MsgCntr': 0,  # Counter (0-15)
            'Vehicle_TRIP1': 1000,  # cm
            'Vehicle_Remote_Mileage1': 50.0  # km
        },
        'Vehicle_Mileage1': {
            'Vehicle_Mileage1_MsgCntr': 0,
            'Vchicle_AD_Mileage1': 100.0,
            'Vehicle_ODO1': 200.0
        },
        'BMS_2B1h': {
            'BMS_Battery_Capacity_Kwh': 50.0,
            'BMS_Battery_Capacity_Ah': 100,
            'BMS_HVBatLowestTemCellNum': 1,
            'BMS_HVBatLowestTem': 25,
            'BMS_HVBatHighestTemCellNum': 2,
            'BMS_HVBatHighestTem': 30
        },
        'BMS_2A1h': {
            'BMS_HVBatCellVolDiff': 10,
            'BMS_HVBatHighestVolCellNum': 1,
            'BMS_HVBatLowestVolCellNum': 2,
            'BMS_HVBatHighestCellVol': 4200,
            'BMS_HVBatLowestCellVol': 4100
        },
        'BMS_A0h': {
            'BMS_HVDisplaySOH': 95,
            'BMS_Sys_Flt': 0,
            'BMS_Charge_StsCc': 0,
            'BMS_Charge_StsCc2': 0,
            'BMS_Sys_Sts': 0,
            'BMS_HVBatSOC': 80.0,
            'BMS_HVBatCrnt': 10.0,
            'BMS_HVBatVol': 400.0
        },
        'VCU_Vehicle_Error_Status': {
            'VCU_CAN2_Fault': 0,
            'VCU_CAN1_Fault': 0,
            'VCU_CAN0_Fault': 0,
            'VCU_PwrCtrl_Fault': 0,
            'VCU_EPBActinWhenEBSMF': 0,
            'VCU_EEPROM_Fault': 0,
            'VCU_EBSActinWhenEPBMF': 0,
            'Error_Code': 0
        },
        'DBS_Status2': {
            'DBS_WarringCode': 0,
            'DBS_CheckSum2': 0,
            'DBS_Fault_Code': 0,
            'DBS_RollingCounter2': 0
        },
        'TPMS_Status': {
            'ID_Match_State': 0,
            'Hight_Temperature_Warning': 0,
            'Sensor_state': 0,
            'Sensor_Power_State': 0,
            'Pressure_Warning': 0,
            'Pressure_Unbalance': 0,
            'Tire_Temperature': 25,
            'Tire_Pressure': 250.0,
            'Tire_Num': 0,
            'Tire_Leak_State': 0
        },
        'DBS_Status': {
            'DBS_EstopFlag': 0,
            'DBS_PedaiFlag': 0,
            'BrakePressureReqACK': 0,
            'DBS_Work_Mode': 0,
            'DBS_RollingCounter': 0,
            'DBS_Ref_Iq': 0.0,
            'DBS_Park_Warning': 0,
            'DBS_PeadalOpening': 0,
            'DBS_CheckSum': 0,
            'DBS_HP_pressure': 0.0,
            'DBS_System_Status': 0
        },
        'VCU_DBS_Req': {
            'VCU_DBS_Work_Mode': 0,
            'VCU_ABS_Active': 0,
            'VCU_DBS_Pressure_Request': 0.0,
            'VCU_DBS_Request_Flag': 0
        },
        'Vehicle_Odometer_Status': {
            'Vehicle_Odometer_MsgCntr': 0,
            'Vehicle_AD_Mileage': 100.0,
            'Vehicle_Remote_Mileage': 50.0,
            'Vehicle_TRIP': 10.0,
            'Vehicle_ODO': 200.0
        },
        'VCU_Vehicle_HVBat_Status': {
            'Vehicle_Poweroff_Channel': 0,
            'Vehicle_Poweroff_Countdown_Time': 0.0,
            'Battery_Work_State': 0,
            'Vehicle_Soc': 80,
            'Vehicle_HVBat_MsgCntr': 0,
            'High_Voltage_Battery_Voltage': 400.0,
            'High_Voltage_Battery_MaxTem': 30,
            'High_Voltage_Battery_Current': 10.0
        },
        'VCU_RR_Wheel_Status': {
            'RR_Tire_Leak_State': 0,
            'RR_Wheel_Status_MsgCntr': 0,
            'RR_Tire_Temperature': 25,
            'RR_Tire_Pressure': 250.0,
            'RR_Sensor_state': 0,
            'RR_Pressure_Warning': 0,
            'RR_WhlSpd': 50.0,
            'RR_Reserved': 0
        },
        'VCU_FR_Wheel_Status': {
            'FR_Tire_Leak_State': 0,
            'FR_Pressure_Warning': 0,
            'FR_Wheel_Status_MsgCntr': 0,
            'FR_Tire_Temperature': 25,
            'FR_Tire_Pressure': 250.0,
            'FR_Sensor_state': 0,
            'FR_WhlSpd': 50.0,
            'FR_Reserved': 0
        },
        'VCU_RL_Wheel_Status': {
            'RL_Pressure_Warning': 0,
            'RL_Tire_Temperature': 25,
            'RL_Tire_Pressure': 250.0,
            'RL_Sensor_state': 0,
            'RL_Tire_Leak_State': 0,
            'RL_WhlSpd': 50.0,
            'RL_Reserved': 0,
            'RL_Wheel_Status_MsgCntr': 0
        },
        'VCU_FL_Wheel_Status': {
            'FL_Sensor_state': 0,
            'FL_Tire_Temperature': 25,
            'FL_Pressure_Warning': 0,
            'FL_Tire_Leak_State': 0,
            'FL_Tire_Pressure': 250.0,
            'FL_WhlSpd': 50.0,
            'FL_Reserved': 0,
            'FL_Wheel_Status_MsgCntr': 0
        },
        'VCU_Vehicle_Status_1': {
            'Vehicle_Range': 300,
            'Drive_Mode_State': 0,
            'EPB_Status': 0,
            'Accelerator_Pedal_Status': 0,
            'Brake_Pedal_Status': 0,
            'Vehicle_Status_1_MsgCntr': 0,
            'Vehicle_Gear': 0
        },
        'VCU_Vehicle_Status_2': {
            'Vehicle_Status_2_MsgCntr': 0,
            'Vehicle_Steering_Angle': 0.0,
            'Vehicle_Brake_Pressure': 0.0,
            'Vehicle_Speed': 50.0
        },
        'VCU_Vehicle_Diagnosis': {
            'Horn_2_State': 0,
            'VCU_VehRdy': 1,
            'ADS_Light_State': 0,
            'KERS_LimitedState': 0,
            'Oil_pot_State': 0,
            'Business_Relay_State': 0,
            'Relay_4G_State': 0,
            'Radar_Relay_State': 0,
            'Orin_Relay_State': 0,
            'Motor_Temp_State': 0,
            'Fog_Light_state': 0,
            'Power_Button_State': 0,
            'EPB_Button_State': 0,
            'Motor_Torque_Limit_State': 0,
            'B_Press_Switch_Collision_State': 0,
            'Remo_Touch_Switch_Disable_State': 0,
            'F_Press_Switch_Collision_State': 0,
            'B_Touch_Switch_Disable_State': 0,
            'L_Touch_Switch_Disable_State': 0,
            'R_Touch_Switch_Disable_State': 0,
            'F_Touch_Switch_Disable_State': 0,
            'R_Touch_Switch_Collision_State': 0,
            'L_Touch_Switch_Collision_State': 0,
            'AD_FaultCode': 0,
            'EPB_Diagnosis': 0,
            'Move_Switch': 0,
            'LowBeam_State': 0,
            'Reversing_Lights_State': 0,
            'Tire_Sensor_State': 0,
            'Brake_Light_State': 0,
            'Vehicle_Fault_Grade': 0,
            'EPS_State': 0,
            'Vehicle_Diagnosis_MsgCntr': 0,
            'Horn_1_State': 0,
            'HighBeam_State': 0,
            'Right_Turn_Light_State': 0,
            'Left_Turn_Light_State': 0,
            'B_Touch_Switch_Collision_State': 0,
            'F_Touch_Switch_Collision_State': 0,
            'BMS_State': 0,
            'Emergency_Button_State': 0,
            'DBS_State': 0,
            'AD_State': 0,
            'Remote_State': 0,
            'Motor_State': 0
        },
        'EPS_Status': {
            'EPS_Fault_Code': 0,
            'EPS_Current': 0.0,
            'EPS_Fault_Grade': 0,
            'EPS_Temperature': 25,
            'EPS_Angle_Spd': 50,
            'EPS_Calibration_Status': 0,
            'EPS_StrAngle_Act': 0.0,
            'EPS_Fault': 0,
            'EPS_Work_Mode_Status': 0
        },
        'VCU_EPS_Req': {
            'VCU_Request_EPS_Angle_Speed': 50,
            'VCU_Request_EPS_Angle_Calibrate': 0,
            'VCU_Req_EPS_Target_Angle': 0.0,
            'VCU_EPS_CtrlEnable': 0
        },
        'Remote_Control_Shake': {
            'Remote_X1': 0.0,
            'Remote_Y1': 0.0,
            'Remote_Y2': 0.0,
            'Remote_X2': 0.0
        },
        'Remote_Control_IO': {
            'Remote_A': 0,
            'Remote_D': 0,
            'Remote_C': 0,
            'Remote_B': 0,
            'Remote_F': 0,
            'Remote_E': 0
        },
        'EPB_Status': {
            'EPB_MANUAL_PARKING_KEY': 0,
            'EPB_FINAL_STATES_R': 0,
            'EPB_FINAL_STATES_L': 0,
            'EPB_FAULT': 0
        },
        'VCU_EPB_Req': {
            'VCU_EPB_Parking_Request_R': 0,
            'VCU_EPB_Parking_Request_L': 0,
            'VCU_EPB_Parking_Request_ALL': 0,
            'VCU_EPB_Parking_Flag_R': 0,
            'VCU_EPB_Parking_Flag_L': 0,
            'VCU_EPB_Parking_Flag_ALL': 0,
            'VCU_EPB_Clamping_force_R': 5000,
            'VCU_EPB_Clamping_force_L': 5000
        },
        'MCU_DrvMotSt': {
            'Motor_ActIdc': 0,
            'Motor_ActSpeed': 0.0,
            'Motor_ActGear': 0,
            'Motor_ActTorque': 0.0,
            'Motor_FalutCode': 0,
            'Motor_Temp': 25
        },
        'VCU_MCU_Req': {
            'VCU_SoftReset': 0,
            'VCU_ActiveHVReleaseReq': 0,
            'VCU_MotNegSpdLmt': -9000,
            'VCU_MotNegTrqLmt': -315,
            'VCU_ClampingBrakeReq': 0,
            'VCU_VehHVReady': 0,
            'VCU_ModeReq': 0,
            'VCU_TargetSpdReq': 0.0,
            'VCU_MotPosSpdLmt': 0,
            'VCU_MotPosTrqLmt': 0,
            'VCU_TargetTqReq': 0.0
        },
        'VCU_Version': {
            'Day': 16,
            'Month': 6,
            'Year': 2025,
            'Byte5': 0,
            'Byte4': 0,
            'Byte3': 0,
            'Byte2': 0,
            'Byte1': 0
        },
        'VCU_Vehicle_PwrCtrl_Status': {
            'VCU_LVMuteChargeSt': 0,
            'VCU_AbnPwrOff': 0,
            'VCU_PwrSt': 0,
            'VCU_PUM_FSM_Pointer': 0,
            'VCU_VehRdyDiagEnable': 0,
            'VCU_LowBattVolt': 12.0,
            'VCU_LVPwrActReq': 0,
            'VCU_HVPwrActReq': 0,
            'VCU_WeakUpSig': 0
        }
    }
    return signal_values

def main():
    # Configuration
    dbc_file = "Yokee_CAN2_VCU_500k.dbc"
    can_interface = "can0"
    
    # Load DBC file
    db = load_dbc(dbc_file)
    
    # Get cycle times from DBC
    cycle_times = get_cycle_times(db)
    
    # Initialize CAN bus
    try:
        bus = can.interface.Bus(channel=can_interface, bustype='socketcan')
    except Exception as e:
        print(f"Error initializing CAN bus: {e}")
        return
    
    # Assign signal values
    signal_values = assign_signal_values(db)
    
    # Track last send time for each message
    last_send_times = {msg.frame_id: 0 for msg in db.messages}
    
    print(f"Publishing CAN messages to {can_interface}...")
    
    try:
        while True:
            current_time = time.time()
            for message in db.messages:
                # Check if it's time to send this message
                if current_time - last_send_times[message.frame_id] >= cycle_times[message.frame_id]:
                    try:
                        # Get signal values for this message
                        data = signal_values.get(message.name, {})
                        
                        # Increment counter signals if present
                        for signal in message.signals:
                            if "MsgCntr" in signal.name or "RollingCounter" in signal.name:
                                data[signal.name] = (data.get(signal.name, 0) + 1) % (signal.maximum + 1)
                        
                        # Encode the message
                        encoded_data = message.encode(data)
                        
                        # Create CAN message
                        can_msg = can.Message(
                            arbitration_id=message.frame_id,
                            data=encoded_data,
                            is_extended_id=False
                        )
                        
                        # Send the message
                        bus.send(can_msg)
                        last_send_times[message.frame_id] = current_time
                        print(f"Sent {message.name} (ID: {message.frame_id})")
                    except Exception as e:
                        print(f"Error encoding/sending {message.name}: {e}")
            
            # Sleep briefly to avoid CPU overload
            time.sleep(0.02)
            
    except KeyboardInterrupt:
        print("\nStopped by user")
    finally:
        bus.shutdown()

if __name__ == "__main__":
    main()

只需要注意一点,那就是我们的整个消息是通过一个json格式进行封装的,以message为单位封装了signal.
文件和设备定义在main中:

    dbc_file = "Yokee_CAN2_VCU_500k.dbc"
    can_interface = "can0"
encoded_data = message.encode(data)

通过encode把这些数据都写入data中.同样的,这里其实会有一些问题,对于那些默认不需要发送的信号也会同步进行发送.但是后来在使用cpp时沿用了这一设定,导致很多默认信号我都直接初始化为0了.所以需要注意一下那些默认的信号,在python中需要额外进行初始化.cpp不使用这种json初始化形式,所以需要额外判断,只有需要发送的信号才会发送(按照人的需求来发送can信号,而不是dbc中所有的信号)
运行之后,通过candump can0查看当前的信号发送情况:
在这里插入图片描述

解析can消息

解析can消息也是通过dbc文件实现,并且比起发送需要提前定义信号,解析完全不需要这个过程,读取can设备然后根据和dbc文件的对应,将每个信号的含义都会解出对应的物理值.

import cantools
import can
import sys

def main():
    # if len(sys.argv) != 2:
    #     print(f"用法: {sys.argv[0]} <dbc_file>")
    #     sys.exit(1)

    # dbc_file = sys.argv[1]
    dbc_file = "Yokee_CAN2_VCU_500k.dbc"
    
    try:
        # 加载DBC文件
        db = cantools.database.load_file(dbc_file)
        print(f"成功加载DBC文件: {dbc_file}")
    except Exception as e:
        print(f"加载DBC文件失败: {e}")
        sys.exit(1)

    try:
        # 创建CAN总线接口,连接到can0
        bus = can.interface.Bus(
            bustype='socketcan',
            channel='can0',
            bitrate=500000  # 比特率,根据实际情况调整
        )
        print("成功连接到CAN0接口")
        print("开始接收和解析CAN消息...")
        print("-" * 60)
    except Exception as e:
        print(f"连接CAN0接口失败: {e}")
        sys.exit(1)

    try:
        while True:
            # 接收CAN消息
            message = bus.recv(1.0)  # 超时时间1if message is not None:
                try:
                    # 解析CAN消息
                    can_id = message.arbitration_id
                    data = message.data
                    
                    # 根据CAN ID查找消息定义
                    msg_def = db.get_message_by_frame_id(can_id)
                    
                    # 解析信号值
                    signals = msg_def.decode(data)
                    
                    # 打印解析结果
                    print(f"ID: 0x{can_id:03X} ({msg_def.name})")
                    for signal_name, value in signals.items():
                        print(f"  {signal_name}: {value}")
                    print("-" * 60)
                    
                except KeyError:
                    # 未在DBC中定义的CAN ID
                    print(f"未定义的CAN ID: 0x{can_id:03X}")
                    print("-" * 60)
                except Exception as e:
                    print(f"解析错误: {e}")
                    print("-" * 60)
                    
    except KeyboardInterrupt:
        print("\n程序已停止")
    finally:
        # 关闭CAN总线连接
        bus.shutdown()
        print("已断开CAN0连接")

if __name__ == "__main__":
    main()

在这里插入图片描述
也是只需要can设备和对应的dbc文件位置即可

运行:

python parse.py

打印出了对应的message和signal及其对应的值,可以和send_can的消息进行对比,应该是完全一致的.
在这里插入图片描述

解析can消息的cpp实现

发送can消息

cpp实现版本最开始我是直接通过位操作实现的,过于粗糙并且不好复用(主要是没有找到合适的解析库),最后直接自己实现了一个解析库的函数:
那么最重要的就是解析dbc文件的函数:

    void parseDBC(const std::string& filename) {
        std::ifstream file(filename);
        if (!file.is_open()) {
            std::cerr << "Error: Could not open DBC file: " << filename << std::endl;
            return;
        }

        std::string line;
        Message currentMessage;
        bool inMessage = false;
        int lineNumber = 0;

        while (std::getline(file, line)) {
            lineNumber++;
            line = trim(line);
            if (line.empty()) continue;

            try {
                if (line.rfind("BO_ ", 0) == 0) {
                    if (inMessage && !currentMessage.signals.empty()) {
                        messages[currentMessage.id] = currentMessage;
                    }
                    currentMessage = Message();
                    inMessage = true;

                    auto tokens = split(line, ' ');
                    if (tokens.size() < 4) {
                        std::cerr << "Error at line " << lineNumber << ": Invalid BO_ format" << std::endl;
                        continue;
                    }

                    currentMessage.id = std::stoul(tokens[1]);
                    currentMessage.name = tokens[2].substr(0, tokens[2].size() - 1);
                    currentMessage.dlc = std::stoi(tokens[3]);
                }
                else if (line.rfind("SG_ ", 0) == 0 && inMessage) {
                    Signal signal;
                    auto tokens = split(line, ' ');
                    if (tokens.size() < 7) {
                        std::cerr << "Error at line " << lineNumber << ": Invalid SG_ format" << std::endl;
                        continue;
                    }

                    signal.name = tokens[1];
                    auto bitInfo = split(tokens[3], '|');
                    signal.startBit = std::stoi(bitInfo[0]);
                    auto lengthInfo = split(bitInfo[1], '@');
                    signal.length = std::stoi(lengthInfo[0]);
                    signal.isMotorola = (lengthInfo[1].find('0') != std::string::npos);
                    signal.isSigned = (lengthInfo[1].find('+') == std::string::npos);

                    auto scale = split(tokens[4].substr(1, tokens[4].size() - 2), ',');
                    signal.factor = std::stod(scale[0]);
                    signal.offset = std::stod(scale[1]);
                    signal.unit = tokens[6].substr(1, tokens[6].size() - 2);
                    currentMessage.signals.push_back(signal);
                }
                else if (line.rfind("VAL_ ", 0) == 0) {
                    auto tokens = split(line, ' ');
                    if (tokens.size() < 4) {
                        std::cerr << "Error at line " << lineNumber << ": Invalid VAL_ format" << std::endl;
                        continue;
                    }

                    uint32_t messageId = std::stoul(tokens[2]);
                    std::string signalName = tokens[3];
                    std::map<int, std::string> valueTable;
                    for (size_t i = 4; i < tokens.size() - 1; i += 2) {
                        int value = std::stoi(tokens[i]);
                        std::string desc = tokens[i + 1];
                        if (desc.front() == '"' && desc.back() == '"') {
                            desc = desc.substr(1, desc.size() - 2);
                        }
                        valueTable[value] = desc;
                    }

                    if (messages.find(messageId) != messages.end()) {
                        for (auto& signal : messages[messageId].signals) {
                            if (signal.name == signalName) {
                                signal.valueTable = valueTable;
                                break;
                            }
                        }
                    }
                }
            } catch (const std::exception& e) {
                std::cerr << "Exception at line " << lineNumber << ": " << e.what() << std::endl;
            }
        }
        if (inMessage && !currentMessage.signals.empty()) {
            messages[currentMessage.id] = currentMessage;
        }
        file.close();
    }

最初的版本中解析dbc文件没有考虑intel和Motorola的情况,第二版就把它也考虑在内了。定义的signal的结构体为:

struct Signal {
    std::string name;
    int startBit;
    int length;
    bool isSigned;
    bool isMotorola;
    double factor;
    double offset;
    std::string unit;
    std::map<int, std::string> valueTable;
};

message的结构体为:

struct Message {
    uint32_t id;
    std::string name;
    int dlc;
    std::vector<Signal> signals;
};

当然,在实际使用时最后是把结构体在定义时就进行初始化!
DBC解析的类为:

class DBCParser {
private:
    std::map<uint32_t, Message> messages;

    std::vector<std::string> split(const std::string& s, char delim) {
        std::vector<std::string> tokens;
        std::string token;
        std::istringstream tokenStream(s);
        while (std::getline(tokenStream, token, delim)) {
            tokens.push_back(token);
        }
        return tokens;
    }

    std::string trim(const std::string& str) {
        size_t first = str.find_first_not_of(" \t");
        size_t last = str.find_last_not_of(" \t");
        if (first == std::string::npos) return "";
        return str.substr(first, last - first + 1);
    }

public:
    void parseDBC(const std::string& filename) {
        std::ifstream file(filename);
        if (!file.is_open()) {
            std::cerr << "Error: Could not open DBC file: " << filename << std::endl;
            return;
        }

        std::string line;
        Message currentMessage;
        bool inMessage = false;
        int lineNumber = 0;

        while (std::getline(file, line)) {
            lineNumber++;
            line = trim(line);
            if (line.empty()) continue;

            try {
                if (line.rfind("BO_ ", 0) == 0) {
                    if (inMessage && !currentMessage.signals.empty()) {
                        messages[currentMessage.id] = currentMessage;
                    }
                    currentMessage = Message();
                    inMessage = true;

                    auto tokens = split(line, ' ');
                    if (tokens.size() < 4) {
                        std::cerr << "Error at line " << lineNumber << ": Invalid BO_ format" << std::endl;
                        continue;
                    }

                    currentMessage.id = std::stoul(tokens[1]);
                    currentMessage.name = tokens[2].substr(0, tokens[2].size() - 1);
                    currentMessage.dlc = std::stoi(tokens[3]);
                }
                else if (line.rfind("SG_ ", 0) == 0 && inMessage) {
                    Signal signal;
                    auto tokens = split(line, ' ');
                    if (tokens.size() < 7) {
                        std::cerr << "Error at line " << lineNumber << ": Invalid SG_ format" << std::endl;
                        continue;
                    }

                    signal.name = tokens[1];
                    auto bitInfo = split(tokens[3], '|');
                    signal.startBit = std::stoi(bitInfo[0]);
                    auto lengthInfo = split(bitInfo[1], '@');
                    signal.length = std::stoi(lengthInfo[0]);
                    signal.isMotorola = (lengthInfo[1].find('0') != std::string::npos);
                    signal.isSigned = (lengthInfo[1].find('+') == std::string::npos);

                    auto scale = split(tokens[4].substr(1, tokens[4].size() - 2), ',');
                    signal.factor = std::stod(scale[0]);
                    signal.offset = std::stod(scale[1]);
                    signal.unit = tokens[6].substr(1, tokens[6].size() - 2);
                    currentMessage.signals.push_back(signal);
                }
                else if (line.rfind("VAL_ ", 0) == 0) {
                    auto tokens = split(line, ' ');
                    if (tokens.size() < 4) {
                        std::cerr << "Error at line " << lineNumber << ": Invalid VAL_ format" << std::endl;
                        continue;
                    }

                    uint32_t messageId = std::stoul(tokens[2]);
                    std::string signalName = tokens[3];
                    std::map<int, std::string> valueTable;
                    for (size_t i = 4; i < tokens.size() - 1; i += 2) {
                        int value = std::stoi(tokens[i]);
                        std::string desc = tokens[i + 1];
                        if (desc.front() == '"' && desc.back() == '"') {
                            desc = desc.substr(1, desc.size() - 2);
                        }
                        valueTable[value] = desc;
                    }

                    if (messages.find(messageId) != messages.end()) {
                        for (auto& signal : messages[messageId].signals) {
                            if (signal.name == signalName) {
                                signal.valueTable = valueTable;
                                break;
                            }
                        }
                    }
                }
            } catch (const std::exception& e) {
                std::cerr << "Exception at line " << lineNumber << ": " << e.what() << std::endl;
            }
        }
        if (inMessage && !currentMessage.signals.empty()) {
            messages[currentMessage.id] = currentMessage;
        }
        file.close();
    }

    const std::map<uint32_t, Message>& getMessages() const {
        return messages;
    }
};

其余的思路就是类似了,根据encode把信号进行编码,然后通过匹配的方式来发送can消息,注意我这里直接把所有的信号都初始化为0了

class CANPublisher {
private:
    int socketFd;
    DBCParser parser;

    void encodeSignal(can_frame& frame, const Signal& signal, double value) {
        double rawValue = (value - signal.offset) / signal.factor;
        uint64_t raw = static_cast<uint64_t>(std::round(rawValue));

        uint64_t maxValue = (1ULL << signal.length) - 1;
        if (signal.isSigned) {
            int64_t signedRaw = static_cast<int64_t>(raw);
            int64_t minSigned = -(1LL << (signal.length - 1));
            int64_t maxSigned = (1LL << (signal.length - 1)) - 1;
            if (signedRaw < minSigned) raw = static_cast<uint64_t>(minSigned);
            if (signedRaw > maxSigned) raw = static_cast<uint64_t>(maxSigned);
        } else {
            if (raw > maxValue) raw = maxValue;
        }

        int startByte = signal.startBit / 8;
        int startBitInByte = signal.startBit % 8;
        int bitsRemaining = signal.length;

        if (signal.isMotorola) {
            int bitPosition = signal.length - 1;
            for (int i = startByte; i < frame.can_dlc && bitsRemaining > 0; i++) {
                int bitsInThisByte = std::min(8 - startBitInByte, bitsRemaining);
                for (int j = 0; j < bitsInThisByte; j++) {
                    int bit = (raw >> (bitPosition - j)) & 1;
                    frame.data[i] &= ~(1 << (7 - (startBitInByte + j)));
                    frame.data[i] |= (bit << (7 - (startBitInByte + j)));
                }
                bitsRemaining -= bitsInThisByte;
                bitPosition -= bitsInThisByte;
                startBitInByte = 0;
            }
        } else {
            int bitPosition = 0;
            for (int i = startByte; i < frame.can_dlc && bitsRemaining > 0; i++) {
                int bitsInThisByte = std::min(8 - startBitInByte, bitsRemaining);
                uint64_t mask = (1ULL << bitsInThisByte) - 1;
                uint64_t shiftedValue = (raw >> bitPosition) & mask;
                frame.data[i] &= ~(mask << startBitInByte);
                frame.data[i] |= (shiftedValue << startBitInByte);
                bitPosition += bitsInThisByte;
                bitsRemaining -= bitsInThisByte;
                startBitInByte = 0;
            }
        }
    }

public:
    CANPublisher(const std::string& dbcFile) {
        parser.parseDBC(dbcFile);
        socketFd = -1;
    }

    bool initSocket(const std::string& interface) {
        socketFd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
        if (socketFd < 0) {
            std::cerr << "Error creating socket: " << strerror(errno) << std::endl;
            return false;
        }

        struct ifreq ifr;
        strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1);
        ifr.ifr_name[IFNAMSIZ - 1] = '\0';
        if (ioctl(socketFd, SIOCGIFINDEX, &ifr) < 0) {
            std::cerr << "Error getting interface index: " << strerror(errno) << std::endl;
            close(socketFd);
            return false;
        }

        struct sockaddr_can addr;
        addr.can_family = AF_CAN;
        addr.can_ifindex = ifr.ifr_ifindex;
        if (bind(socketFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
            std::cerr << "Error binding socket: " << strerror(errno) << std::endl;
            close(socketFd);
            return false;
        }

        return true;
    }

    bool publish(const ControlCommand& cmd) {
        if (!cmd.isValid()) {
            std::cerr << "Invalid ControlCommand" << std::endl;
            return false;
        }

        std::map<uint32_t, can_frame> frames;
        for (const auto& msgPair : parser.getMessages()) {
            frames[msgPair.first] = can_frame{};
            frames[msgPair.first].can_id = msgPair.first;
            frames[msgPair.first].can_dlc = msgPair.second.dlc;
            std::memset(frames[msgPair.first].data, 0, 8);
        }

        for (const auto& msgPair : parser.getMessages()) {
            uint32_t msgId = msgPair.first;
            const Message& msg = msgPair.second;

            for (const auto& signal : msg.signals) {
                // 通过信号名称匹配,不再依赖硬编码的msgId
                if (signal.name == "AD_Accelerate_Pedal") {
                    encodeSignal(frames[msgId], signal, cmd.accelerator);
                } else if (signal.name == "AD_Speed_Req") {
                    encodeSignal(frames[msgId], signal, cmd.target_velocity * 3.6);
                } else if (signal.name == "AD_Accelerate_Gear") {
                    encodeSignal(frames[msgId], signal, cmd.target_gear);
                } else if (signal.name == "AD_Accelerate_Valid") {
                    encodeSignal(frames[msgId], signal, 1);
                } else if (signal.name == "AD_BrakePressure_Req") {
                    encodeSignal(frames[msgId], signal, cmd.brake);
                } else if (signal.name == "AD_DBS_Valid") {
                    encodeSignal(frames[msgId], signal, 1);
                } else if (signal.name == "AD_Steering_Angle_Cmd") {
                    encodeSignal(frames[msgId], signal, cmd.target_steering_angle * 57.2958);
                } else if (signal.name == "AD_Steering_Valid") {
                    encodeSignal(frames[msgId], signal, 1);
                } else if (signal.name == "AD_Left_Turn_Light") {
                    encodeSignal(frames[msgId], signal, cmd.turn_lights == 1 ? 1 : 0);
                } else if (signal.name == "AD_Right_Turn_Light") {
                    encodeSignal(frames[msgId], signal, cmd.turn_lights == 2 ? 1 : 0);
                } else if (signal.name == "AD_Double_Flash_Light") {
                    encodeSignal(frames[msgId], signal, cmd.turn_lights == 3 ? 1 : 0);
                } else if (signal.name == "AD_Low_Beam") {
                    encodeSignal(frames[msgId], signal, cmd.head_lights == 1 ? 1 : 0);
                } else if (signal.name == "AD_High_Beam") {
                    encodeSignal(frames[msgId], signal, cmd.head_lights == 2 ? 1 : 0);
                } else if (signal.name == "AD_Horn_1_Control" || signal.name == "AD_Horn_2_Control") {
                    encodeSignal(frames[msgId], signal, cmd.horn_chassis);
                } else if (signal.name == "AD_Fof_Light") {
                    encodeSignal(frames[msgId], signal, cmd.fog_lamp_front || cmd.fog_lamp_rear);
                } else if (signal.name == "AD_Body_Valid") {
                    encodeSignal(frames[msgId], signal, 1);
                }
            }
        }

        for (const auto& framePair : frames) {
            if (write(socketFd, &framePair.second, sizeof(framePair.second)) != sizeof(framePair.second)) {
                std::cerr << "Error sending CAN frame ID " << framePair.first << ": " << strerror(errno) << std::endl;
                return false;
            }
        }

        return true;
    }

    ~CANPublisher() {
        if (socketFd >= 0) {
            close(socketFd);
        }
    }
};

在实际使用过程中,也进行了一定的修改,将所有信号都初始化和匹配改为了只发送指定的信号(提前定义)。这样就不会发送那些不需要发送的默认信号了。
并且发送的信号我通过一个control_command结构体进行封装了一下
也就是说发送的can消息是从ControlCommand中赋值的:

else if (signal.name == "AD_Speed_Req") {
                    encodeSignal(frames[msgId], signal, cmd.target_velocity * 3.6);

使用

编译对应的源码:

g++ -o can_pub can_pub.cpp

运行

./can_pub ./Yokee_CAN2_VCU_500k.dbc

再使用candump查看:

candump can0

在这里插入图片描述
可以通过python和cpp的结合来验证发送的是否正常,具体操作就是cpp这边发送can消息,然后python来解析对应的can消息.

解析can消息

这里也是一样的思路,全面考虑信号是intel还是Motorola类型.也是可以通过dbc中的某些关键字判断的.
对应的signal和message定义如下:

struct Signal {
    std::string name;
    int startBit;
    int length;
    bool isSigned;
    bool isMotorola; // True for Motorola (@0), false for Intel (@1)
    double factor;
    double offset;
    std::string unit;
    std::map<int, std::string> valueTable; // For enumerated values
};

struct Message {
    uint32_t id;
    std::string name;
    int dlc;
    std::vector<Signal> signals;
};

DBCParser类都是类似的,同样的解析逻辑即可.不同的是解析can是通过reader类实现的:

class CANReader {
private:
    int socketFd;
    DBCParser parser;

    // Decode a signal from CAN frame data
    double decodeSignal(const can_frame& frame, const Signal& signal) {
        uint64_t raw = 0;
        int startByte = signal.startBit / 8;
        int startBitInByte = signal.startBit % 8;
        int bitsRemaining = signal.length;

        if (signal.isMotorola) {
            // Motorola (big-endian) decoding
            // Place MSB at startBit, lower bits at higher positions
            int bitPosition = signal.length - 1; // Start with MSB
            for (int i = startByte; i < frame.can_dlc && bitsRemaining > 0; i++) {
                uint8_t byte = frame.data[i];
                int bitsInThisByte = std::min(8 - startBitInByte, bitsRemaining);
                for (int j = 0; j < bitsInThisByte; j++) {
                    // Extract bits from MSB to LSB within the byte
                    int bit = (byte >> (7 - (startBitInByte + j))) & 1;
                    raw |= (uint64_t(bit) << bitPosition);
                    bitPosition--;
                }
                bitsRemaining -= bitsInThisByte;
                startBitInByte = 0; // Reset for next byte
            }
        } else {
            // Intel (little-endian) decoding
            // Place LSB at startBit, higher bits at higher positions
            int bitPosition = 0; // Start with LSB
            for (int i = startByte; i < frame.can_dlc && bitsRemaining > 0; i++) {
                uint8_t byte = frame.data[i];
                int bitsInThisByte = std::min(8 - startBitInByte, bitsRemaining);
                uint64_t mask = (1ULL << bitsInThisByte) - 1;
                uint64_t value = (byte >> startBitInByte) & mask;
                raw |= (value << bitPosition);
                bitPosition += bitsInThisByte;
                bitsRemaining -= bitsInThisByte;
                startBitInByte = 0; // Reset for next byte
            }
        }

        // Handle signed values
        if (signal.isSigned && (raw & (1ULL << (signal.length - 1)))) {
            uint64_t signMask = ~((1ULL << signal.length) - 1);
            raw |= signMask;
        }

        // Apply factor and offset
        return raw * signal.factor + signal.offset;
    }

public:
    CANReader(const std::string& dbcFile) {
        parser.parseDBC(dbcFile);
    }

    bool initSocket(const std::string& interface) {
        socketFd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
        if (socketFd < 0) {
            std::cerr << "Error creating socket: " << strerror(errno) << std::endl;
            return false;
        }

        struct ifreq ifr;
        strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1);
        ifr.ifr_name[IFNAMSIZ - 1] = '\0';
        if (ioctl(socketFd, SIOCGIFINDEX, &ifr) < 0) {
            std::cerr << "Error getting interface index: " << strerror(errno) << std::endl;
            close(socketFd);
            return false;
        }

        struct sockaddr_can addr;
        addr.can_family = AF_CAN;
        addr.can_ifindex = ifr.ifr_ifindex;
        if (bind(socketFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
            std::cerr << "Error binding socket: " << strerror(errno) << std::endl;
            close(socketFd);
            return false;
        }

        return true;
    }

    void readAndParse() {
        struct can_frame frame;
        while (true) {
            int nbytes = read(socketFd, &frame, sizeof(frame));
            if (nbytes < 0) {
                std::cerr << "Error reading CAN frame: " << strerror(errno) << std::endl;
                break;
            }
            std::cout << "nbytes: " << nbytes << std::endl;

            std::cout << "Received CAN frame with ID: " << frame.can_id << std::endl;

            if (nbytes == sizeof(frame)) {
                auto it = parser.getMessages().find(frame.can_id);
                if (it != parser.getMessages().end()) {
                    const Message& msg = it->second;
                    std::cout << "Message: " << msg.name << " (ID: " << frame.can_id << ")\n";
                    for (const auto& signal : msg.signals) {
                        double value = decodeSignal(frame, signal);
                        std::cout << "  Signal: " << signal.name << " = ";

                        // Check if signal has a value table (for enumerated signals)
                        if (!signal.valueTable.empty()) {
                            int intValue = static_cast<int>(value);
                            auto valIt = signal.valueTable.find(intValue);
                            if (valIt != signal.valueTable.end()) {
                                std::cout << valIt->second;
                            } else {
                                std::cout << value;
                            }
                        } else {
                            std::cout << value;
                        }
                        std::cout << " " << signal.unit << "\n";
                    }
                    std::cout << std::endl; // Add newline for readability
                }
            }
        }
    }

    ~CANReader() {
        if (socketFd >= 0) {
            close(socketFd);
        }
    }
};

最后在main中定义对应的设备和dbc文件

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <path_to_dbc_file>" << std::endl;
        return 1;
    }

    std::string dbcFile = argv[1];
    CANReader reader(dbcFile);

    if (!reader.initSocket("can0")) {
        return 1;
    }

    std::cout << "Reading CAN data from vcan0 interface...\n";
    reader.readAndParse();

    return 0;
}

使用

编译源码

g++ -o can_parser_v1 can_parse_v1.cpp

运行

./can_parser_v1 ./Yokee_CAN2_VCU_500k.dbc

通过python或者cpp版本发送can消息
在这里插入图片描述
同样的,对照的查看信号是否是解析对了.
以上的源码放在了:
https://github.com/chan-yuu/can_parse

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白云千载尽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值