通过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) # 超时时间1秒
if 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