Plotjuggler是一个强大的画图工具,绘图极其方便,对于分析log绝对是个神器。
在其主页上,看到它支持ulog模式,而ulog格式又是一个飞行器开源库PX4里所定义的,官方文档写的不是很好,主要是只讲了定义,没有实际的例子,看了几遍都没看懂。并且PX4的源码里对Ulog封装的也不好,根本看不出是怎么写的,没法从中把它给抽取出来。
网上搜了个遍,没看到有如何写ulog格式的教程或代码,只能啃官方代码和文档。
记得某个晚上,看了两个小时的PX4源码和格式,一行代码都没写出来,当晚心情特别沉重,这玩意儿真的就这么难吗?
第二天找到了Pyulog,是一个Python解析ulog的库,令人惊喜的是它里面竟然有写ulog的函数。
单步调试看源码,看了大概一天半,才终于看明白了原理,恍然大悟。
然后自己重新建了C++工程,自己造些数据写成ulog,并且下载PlotJuggler源码调试自己写的ulog格式,一步一步用Plotjuggler画出来。等框架写的差不多了,再移植到我们的代码框架里。
以下是Ulog格式分析,其中的类别定义可取Plotjuggler和PX4源码里找到。如下,分为三个部分,其中Header和Definitions是初始化时预定义的。
----------------------
| Header |
----------------------
| Definitions |
----------------------
| Data |
----------------------
1 Header
固定16个字节,没什么好说的。
----------------------------------------------------------------------
| 0x55 0x4c 0x6f 0x67 0x01 0x12 0x35 | 0x01 | uint64_t |
| File magic (7B) | Version (1B) | Timestamp (8B) |
----------------------------------------------------------------------
2 Definitions
Format
struct message_format_s {
struct message_header_s header;
char format[header.msg_size];
};
format[header.msg_size]的格式如下:':‘前面是格式(可理解为结构体)名称,’:‘后面是类型,以’;'个该,PlotJugger会按照这个格式解析format。如
"pose:uint64_t timestamp;double x;double y;double z;"
variable
struct message_add_logged_s {
struct message_header_s header;
uint8_t multi_id;
uint16_t msg_id;
char message_name[header.msg_size-3];
};
message_name:即上面注册的Format “pose”
msg_id:每个message_name都要有一个独一无二的msg_id,作为后续记录的key
multi_id: 每个message_name可能会有多个类型,比如同一个数据类型有多个sensor,此时msg_id相同,而multi_id不同
3 Data
struct message_data_s {
struct message_header_s header;
uint16_t msg_id;
uint8_t data[header.msg_size-2];
};
msg_id: 就是注册变量的id,对应格式为msg_id:{data},注意timestamp单位是毫秒,且必须单调递增。
整体log文本格式如下(实际是二进制存储)
--------------------------//header
"ULog"
--------------------------// format
"pose:uint64_t timestamp;double x;double y;double z;"
"radar:uint64_t timestamp;double x;double y;"
//------------------------variable format:msg_id
"pose":0
"radar":1
//------------------------data msg_id:timestamp,data
0:100000000,{Pose}
1:100000050,{Radar}
0:100001000,{Pose}
1:100001050,{Radar}
0:100002000,{Pose}
1:100002050,{Radar}
...................
Pose和Radar对应的结构体如下:
struct Pose {
uint64_t timestamp = 0;
double x = 0;
double y = 0;
double z = 0;
}
struct Radar {
uint64_t timestamp = 0;
double x = 0;
double y = 0;
}
后记
格式搞明白花了时间跨度为两周,开发时间跨度为一周。果然还是会者不难,难者不会。
回过头看,卡脖子的几个点,Ulog格式是怎样的,sprintf怎么用,如何写二进制,如何调用ulog,记录如下:
1 定义结构体时,pack push 1,没有pop,导致程序挂了,而且挂的还是别的地方,耗时2小时
#pragma pack(push, 1)
#pragma pack(pop)
2 ULog类名的L写成了小写Ulog没注意,编译提示找不到定义,一直以为是CMAKELIST自己没有添加对,耗时2小时
3 debug调试问题,clion debug没有,bin下面没有lib,vscode怎么调试C++,怎么设置debug模式?
4 CMakeList.txt 编译,全局变量多变量重复定义。
5 sprintf怎么用?
int format_len = snprintf(msg.format, sizeof(msg.format), "%s:", format_name.c_str());
format_len += snprintf(msg.format + format_len, sizeof(msg.format) - format_len,
"%s", format[i].c_str());
6 二进制文件怎么写?
std::ofstream file(”test.ulg“, std::ios::out|std::ios::binary);
file.write((char *) &msg, msg_size);
7 vector怎么使用字符串初始化? 使用({})
std::vector<std::string> format_names({"format1","format2"});
8 怎么调用ulog?
使用单例模式,这样可以在全局都使用ulog,只需要初始化一个对象,不必在每个类中都定义一个ulog的对象,而且可以把写log到一个文件内。