目的将录制的rosbag文件内的can消息提取出来并转换为常规的can日志文件格式保存(如: asc文件),方便使用can分析仪分析数据
1. rosbag文件解析
由于可能需要在window系统下使用,此处选择rosbags库实现,无需安装ros系统。
pip install rosbags
参考rosbags官方文档示例,修改过滤条件为" x.msgtype == ‘can_msgs/msg/Frame’ "即可遍历出目标rosbag文件内的所有can消息话题
目标rosbag文件录制的can消息类型为can_msgs::Frame
from pathlib import Path
from rosbags.highlevel import AnyReader
from rosbags.typesys import Stores, get_typestore
bagpath = Path('/home/ros/rosbag_2020_03_24')
# Create a type store to use if the bag has no message definitions.
typestore = get_typestore(Stores.ROS2_FOXY)
# Create reader instance and open for reading.
with AnyReader([bagpath], default_typestore=typestore) as reader:
connections = [x for x in reader.connections if x.msgtype == 'can_msgs/msg/Frame']
for connection, timestamp, rawdata in reader.messages(connections=connections):
msg = reader.deserialize(rawdata, connection.msgtype)
print(hex(msg.id).upper() + "\t\t" + " ".join(msg.data))
2. CAN日志文件生成
参考vector官方文档 CAN_LOG_TRIGGER_ASC_Format.pdf format v7.2生成实现
2.1 文件头生成
def GenerateHeaderInfo(self)->str:
header_info_template = '''// created by jluoyy-tool-can_format
date {WeekDay} {Month} {Day} {FullTime} {Year}
base {data_format} timestamps {time_format}
internal events logged
// version 7.2.0
Begin Triggerblock {WeekDay} {Month} {Day} {FullTime} {Year}
'''
current_local_time = time.localtime(time.time())
WeekDay = time.strftime("%a", current_local_time)
Month = time.strftime("%b", current_local_time)
Day = time.strftime("%d", current_local_time)
FullTime = time.strftime("%H:%M:%S.000 %p", current_local_time)
Year = time.strftime("%Y", current_local_time)
header_info = header_info_template.format(
WeekDay = WeekDay, Month = Month, Day = Day, FullTime = FullTime, Year = Year,
data_format = self._data_format_, time_format = self._time_format_
)
self._frame_cnt_ = 0
return header_info
2.2 CANMessage
def GenerateCANMessage(self, timestamp_seconds: float, channel: int, msg_dir: str, can_id: int, byte_array: List[int])->str:
self.SetBeginTimeStamp(timestamp_seconds)
can_message_line_template = "{Time}\t{Channel}\t{ID}\t\t\t{Dir} d {DLC} {Bytes} {MessageFlags}"
ID = "%X"%(can_id)
Channel = str(channel)
Bytes = " ".join(["%02X"%(x) for x in byte_array])
DLC = len(byte_array)
MessageFlags = ""
Time = "%.8f"%(timestamp_seconds - self._begin_timestamp_)
if msg_dir.lower().startswith("tx"):
Dir = "Tx"
else:
Dir = "Rx"
can_message_data = can_message_line_template.format(
Time = Time, Channel = Channel, ID = ID, Dir = Dir, DLC = DLC, Bytes = Bytes, MessageFlags = MessageFlags
)
return can_message_data
2.3 CANExtendedMessage
def GenerateCANExtendedMessage(self, timestamp_seconds: float, channel: int, msg_dir: str, can_id: int, byte_array: List[int])->str:
self.SetBeginTimeStamp(timestamp_seconds)
can_message_line_template = "{Time}\t{Channel}\t{ID}{Symbol_01}{Dir} d {DLC} {Bytes} {MessageFlags}"
ID = "%Xx"%(can_id)
Channel = str(channel)
Bytes = " ".join(["%02X"%(x) for x in byte_array])
DLC = len(byte_array)
MessageFlags = ""
Time = "%.8f"%(timestamp_seconds - self._begin_timestamp_)
Symbol_01 = "\t" * (3 - int(len(ID)/4))
if msg_dir.lower().startswith("tx"):
Dir = "Tx"
else:
Dir = "Rx"
can_message_data = can_message_line_template.format(
Time = Time, Channel = Channel, ID = ID, Dir = Dir, DLC = DLC, Bytes = Bytes, MessageFlags = MessageFlags,
Symbol_01 = Symbol_01
)
return can_message_data
2.4 CANRemoteFrame
def GenerateCANRemoteFrame(self, timestamp_seconds: float, channel: int, msg_dir: str, can_id: int)->str:
self.SetBeginTimeStamp(timestamp_seconds)
can_message_line_template = "{Time}\t{Channel}\t{ID}\t\t\t{Dir}\tr"
ID = "%X"%(can_id)
Channel = str(channel)
Time = "%.8f"%(timestamp_seconds - self._begin_timestamp_)
if msg_dir.lower().startswith("tx"):
Dir = "Tx"
else:
Dir = "Rx"
can_message_data = can_message_line_template.format(
Time = Time, Channel = Channel, ID = ID, Dir = Dir
)
return can_message_data
2.5 CANErrorFrame
def GenerateCANErrorFrame(self, timestamp_seconds: float, channel: int, msg_dir: str)->str:
self.SetBeginTimeStamp(timestamp_seconds)
can_message_line_template = "{Time}\t{Channel} ErrorFrame"
Channel = str(channel)
Time = "%.8f"%(timestamp_seconds - self._begin_timestamp_)
can_message_data = can_message_line_template.format(
Time = Time, Channel = Channel
)
return can_message_data
3. 完整代码
该代码仅为简单示例实现,channel、dir等参数暂未携带在包内,代码内直接设置为默认参数,能满足个人当前需求。若需要按实际通道或收发方向分类,需要按各自需求自行调整。
# -*- coding: utf-8 -*-
# ********************************************************** #
# Cause I know you are my polaris. #
# ********************************************************** #
import os,sys,time
import tkinter as tk
from typing import List
from pathlib import Path
from tkinter import filedialog
try:
import rosbags, rospkg
from rosbags.highlevel import AnyReader
from rosbags.typesys import Stores, get_typestore
except:
os.system("pip install rosbags rospkg")
os.system("python -m pip install rosbags rospkg")
import rosbags, rospkg
from rosbags.highlevel import AnyReader
from rosbags.typesys import Stores, get_typestore
class CANASC:
def __init__(self, file_path:str="") -> None:
self._data_format_ = "hex"
self._time_format_ = "absolute"
self._begin_timestamp_ = 0.0
self._frame_cnt_ = 0
if False is file_path.endswith(".asc"):
self._file_path_ = ""
self._file_stream_ = None
raise Exception("文件类型错误, 应当为asc文件类型")
else:
self._file_path_ = file_path
self._file_stream_ = open(file_path, 'w', encoding="utf-8")
pass
def __del__(self):
self._file_stream_.write("End Triggerblock")
self._file_stream_.close()
pass
def GenerateHeaderInfo(self)->str:
header_info_template = '''// created by jluoyy-tool-can_format
date {WeekDay} {Month} {Day} {FullTime} {Year}
base {data_format} timestamps {time_format}
internal events logged
// version 7.2.0
Begin Triggerblock {WeekDay} {Month} {Day} {FullTime} {Year}
'''
current_local_time = time.localtime(time.time())
WeekDay = time.strftime("%a", current_local_time)
Month = time.strftime("%b", current_local_time)
Day = time.strftime("%d", current_local_time)
FullTime = time.strftime("%H:%M:%S.000 %p", current_local_time)
Year = time.strftime("%Y", current_local_time)
header_info = header_info_template.format(
WeekDay = WeekDay, Month = Month, Day = Day, FullTime = FullTime, Year = Year,
data_format = self._data_format_, time_format = self._time_format_
)
self._frame_cnt_ = 0
self._file_stream_.write(header_info)
return header_info
def SetBeginTimeStamp(self, timestamp_seconds: float):
if 0 == self._frame_cnt_:
self._begin_timestamp_ = timestamp_seconds
self._frame_cnt_ += 1
self._file_stream_.write("\t0.00000000 Start of measurement\n")
else:
self._frame_cnt_ += 1
def GenerateCANMessage(self, timestamp_seconds: float, channel: int, msg_dir: str, can_id: int, byte_array: List[int])->str:
self.SetBeginTimeStamp(timestamp_seconds)
can_message_line_template = "{Time}\t{Channel}\t{ID}\t\t\t{Dir} d {DLC} {Bytes} {MessageFlags}"
ID = "%X"%(can_id)
Channel = str(channel)
Bytes = " ".join(["%02X"%(x) for x in byte_array])
DLC = len(byte_array)
MessageFlags = ""
Time = "%.8f"%(timestamp_seconds - self._begin_timestamp_)
if msg_dir.lower().startswith("tx"):
Dir = "Tx"
else:
Dir = "Rx"
can_message_data = can_message_line_template.format(
Time = Time, Channel = Channel, ID = ID, Dir = Dir, DLC = DLC, Bytes = Bytes, MessageFlags = MessageFlags
)
self._file_stream_.write("\t" + can_message_data + "\n")
return can_message_data
def GenerateCANExtendedMessage(self, timestamp_seconds: float, channel: int, msg_dir: str, can_id: int, byte_array: List[int])->str:
self.SetBeginTimeStamp(timestamp_seconds)
can_message_line_template = "{Time}\t{Channel}\t{ID}{Symbol_01}{Dir} d {DLC} {Bytes} {MessageFlags}"
ID = "%Xx"%(can_id)
Channel = str(channel)
Bytes = " ".join(["%02X"%(x) for x in byte_array])
DLC = len(byte_array)
MessageFlags = ""
Time = "%.8f"%(timestamp_seconds - self._begin_timestamp_)
Symbol_01 = "\t" * (3 - int(len(ID)/4))
if msg_dir.lower().startswith("tx"):
Dir = "Tx"
else:
Dir = "Rx"
can_message_data = can_message_line_template.format(
Time = Time, Channel = Channel, ID = ID, Dir = Dir, DLC = DLC, Bytes = Bytes, MessageFlags = MessageFlags,
Symbol_01 = Symbol_01
)
self._file_stream_.write("\t" + can_message_data + "\n")
return can_message_data
def GenerateCANRemoteFrame(self, timestamp_seconds: float, channel: int, msg_dir: str, can_id: int)->str:
self.SetBeginTimeStamp(timestamp_seconds)
can_message_line_template = "{Time}\t{Channel}\t{ID}\t\t\t{Dir}\tr"
ID = "%X"%(can_id)
Channel = str(channel)
Time = "%.8f"%(timestamp_seconds - self._begin_timestamp_)
if msg_dir.lower().startswith("tx"):
Dir = "Tx"
else:
Dir = "Rx"
can_message_data = can_message_line_template.format(
Time = Time, Channel = Channel, ID = ID, Dir = Dir
)
self._file_stream_.write("\t" + can_message_data + "\n")
return can_message_data
def GenerateCANErrorFrame(self, timestamp_seconds: float, channel: int, msg_dir: str)->str:
self.SetBeginTimeStamp(timestamp_seconds)
can_message_line_template = "{Time}\t{Channel} ErrorFrame"
Channel = str(channel)
Time = "%.8f"%(timestamp_seconds - self._begin_timestamp_)
can_message_data = can_message_line_template.format(
Time = Time, Channel = Channel
)
self._file_stream_.write("\t" + can_message_data + "\n")
return can_message_data
# class CANASC:
# ----------------------------------------------- #
def main():
input_path = ""
root = tk.Tk()
while "" == input_path:
input_path = filedialog.askopenfilename(title="选择rosbag文件", filetypes={("roabag文件", "*.bag"), ("ALL", "*.*")}, defaultextension=".bag")
if 0 == len(input_path):
input_path = ""
if "" == input_path or not os.path.exists(input_path):
print("选择的文件路径错误: " + input_path)
sel = input("是否重新选择(Y/N):")
if 'Y' == sel.upper():
input_path = ""
else:
return
root.destroy()
root.mainloop()
output_path = input_path.replace(".bag", "_bag") + ".asc"
can_msg = CANASC(output_path)
can_msg.GenerateHeaderInfo()
rosbag_path = Path(input_path)
typestore = get_typestore(Stores.ROS2_FOXY)
with AnyReader([rosbag_path], default_typestore=typestore) as reader:
connections = [x for x in reader.connections if x.msgtype == 'can_msgs/msg/Frame']
is_stored_begintime = False
for connection, timestamp, rawdata in reader.messages(connections=connections):
if False is is_stored_begintime:
can_msg.SetBeginTimeStamp(timestamp/1e9)
is_stored_begintime = True
msg = reader.deserialize(rawdata, connection.msgtype)
line_text = ""
if True is msg.is_error:
line_text = can_msg.GenerateCANErrorFrame(timestamp/1e9, 1, "RX")
elif True is msg.is_rtr:
line_text = can_msg.GenerateCANRemoteFrame(timestamp/1e9, 1, "RX", msg.id)
elif True is msg.is_extended:
line_text = can_msg.GenerateCANExtendedMessage(timestamp/1e9, 1, "RX", msg.id, msg.data)
else:
line_text = can_msg.GenerateCANMessage(timestamp/1e9, 1, "RX", msg.id, msg.data)
print(line_text.replace("\t", " ") + "\t/* from " + connection.topic + " */", flush=True)
pass
pass
del can_msg
return
# ************************************* #
if __name__ == "__main__":
main()