# ULog 文件格式
ULog 是用于记录系统数据的文件格式。
格式是自描述的,即包含记录的格式和信息类型(注意 系统记录仪 允许 默认设置 从 SD 卡中替换记录的主题)。
它可用于记录设备输入(传感器等)、内部状态(CPU 负载、姿态等)以及 printf
日志信息。
该格式对所有二进制类型都使用 Little Endian。
# 数据类型
使用以下二进制类型。它们都与 C 语言中的类型相对应:
类型 | 大小(字节 |
---|---|
int8_t, uint8_t | 1 |
int16_t, uint16_t | 2 |
int32_t, uint32_t | 4 |
int64_t, uint64_t | 8 |
浮动 | 4 |
双人 | 8 |
bool, char | 1 |
此外,所有内容都可以用作数组,例如 浮点[5]
.一般来说,所有字符串 (字符[长度]
) 不包含 '\0'
结尾。字符串比较区分大小写。
# 文件结构
文件包括三个部分:
---------------------- | 标题 | ---------------------- | 定义 | ---------------------- | 数据 | ----------------------
# 页眉部分
标头是固定大小的部分,格式如下(16 字节):
---------------------------------------------------------------------- | 0x55 0x4c 0x6f 0x67 0x01 0x12 0x35 | 0x01 | uint64_t | | 文件魔法 (7B) | 版本 (1B) | 时间戳 (8B) | ----------------------------------------------------------------------
版本是文件格式的版本,目前为 1。 时间戳是一个 uint64_t
整数,表示以微秒为单位的记录开始时间。
# 定义部分
长度可变的部分,包含版本信息、格式定义和(初始)参数值。
定义和数据部分由一系列信息组成。每条信息都以这样的标题开头:
结构 消息标题 {
uint16_t msg_size;
uint8_t msg_type
};
msg_size
是不含报文头的报文大小(以字节为单位)。hdr_size
= 3 个字节)。
msg_type
定义了内容,是以下内容之一:
'B':标志位组信息。
struct ulog_message_flag_bits_s { struct message_header_s; uint8_t compat_flags[8]; uint8_t incompat_flags[8]; uint64_t appended_offsets[3]; ///< 如果设置了附加位,附加数据的文件偏移量 };
此信息 必须 是第一条信息,紧跟在标题部分之后,这样它就有了一个固定不变的偏移量。
compat_flags
:兼容标志位。compat_flags[0]
第 0 位、 默认参数:如果设置,日志中将包含参数默认值(信息 "Q")。
这些比特可用于 ULog 未来的变更,并与现有的解析器兼容。这意味着如果其中一个未知位被设置,解析器可以直接忽略这些位。
不兼容标记
不兼容标志位。如果日志包含附加数据,且至少有一个附加偏置
为非零。如果解析器发现其中一位被设置,则必须拒绝解析日志。这可以用来引入现有解析器无法处理的破坏性变化。附加偏置
:附加数据的文件偏移量(以 0 为基准)。如果不追加数据,则所有偏移量都必须为 0。这可用于可靠地为可能在信息中间停止的日志添加数据。附加数据的进程应该这样做:
- 设置相关的
不兼容标记
位、 - 设置第一个
附加偏置
等于日志文件的长度、 - 然后附加数据部分有效的任何类型的报文。
- 设置相关的
在未来的 ULog 规范中,可能会有更多字段附加在此报文的末尾。这意味着解析器不能假定此报文的长度是固定的。如果报文长度超出预期(目前为 40 字节),超出的字节必须直接忽略。
F":单个(复合)类型的格式定义,可作为嵌套类型记录或用于另一个定义中。
struct message_format_s { struct message_header_s header; char format[header.msg_size]; };
格式
:纯文本字符串,格式如下:message_name:field0;field1;
可以有任意数量的字段(至少 1 个),字段之间用;
.字段的格式为类型 字段名
或类型[数组长度] 字段名称
数组(仅支持固定大小的数组)。类型
是基本二进制类型之一或消息名称
的另一个格式定义(嵌套使用)。一种类型可以在定义之前使用。可以任意嵌套,但不能循环依赖。有些字段名称比较特殊:
时戳
:每条记录的信息 (添加已登录消息
) 必须包含一个时间戳字段(不必是第一个字段)。其类型可以是uint64_t
(目前唯一使用的)、uint32_t
,uint16_t
或uint8_t
.单位始终是微秒,除非在uint8_t
是毫秒。日志写入器必须确保足够频繁地记录报文,以便能够检测到环绕,而日志读取器必须处理环绕(并考虑到掉线)。时间戳必须始终单调递增,对于具有相同msg_id
.填充:以
_ 填充
不应显示,读取器必须忽略其数据。这些字段可由写入者插入,以确保正确对齐。如果填充字段是最后一个字段,则不会记录该字段,以避免写入不必要的数据。这意味着
message_data_s.data
的长度将缩短。不过,在嵌套定义中使用报文时,仍然需要填充。
I':信息提示。
结构 消息信息 { 结构 消息标题 页眉; uint8_t key_len; 烧焦 密钥[key_len]; 烧焦 价值[页眉.msg_size-1-key_len] };
密钥
是一个纯字符串,如格式信息中的字符串(也可以是自定义类型),但只包含一个字段,没有结尾;
例如float[3] myvalues
.价值
所描述的数据。密钥
.需要注意的是,带有某个键的信息在整个日志中最多只能出现一次。解析器可以字典形式存储信息报文。
预定义的信息报文有
密钥 | 说明 | 数值示例 |
---|---|---|
char[value_len] sys_name | 系统名称 | "PX4"; |
char[value_len] ver_hw | 硬件版本(板) | "PX4FMU_V4"; |
char[value_len] ver_hw_subtype | 执行局颠覆(变体) | "V2"; |
char[value_len] ver_sw | 软件版本(git 标签) | "7f65e01"; |
char[value_len] ver_sw_branch | git 分支 | "主人"; |
uint32_t ver_sw_release | 软件版本(见下文) | 0x010401ff |
char[value_len] sys_os_name | 操作系统名称 | "Linux"; |
char[value_len] sys_os_ver | 操作系统版本(git 标签) | "9f82919"; |
uint32_t ver_os_release | 操作系统版本(见下文) | 0x010401ff |
char[value_len] sys_toolchain | 工具链名称 | "GNU GCC"; |
char[value_len] sys_toolchain_ver | 工具链版本 | "6.2.1"; |
char[value_len] sys_mcu | 芯片名称和修订版 | STM32F42x,修订版 A"; |
char[value_len] sys_uuid | 载具的唯一标识符(如 MCU ID) | "392a93e32fa3"... |
char[value_len] log_type | 日志类型(未指定时为完整日志) | 使命"; |
char[value_len] 重放 | 如果在重放模式下,重放日志的文件名 | "log001.ulg"; |
int32_t time_ref_utc | 以秒为单位的 UTC 时间偏移 | -3600 |
的格式。 ver_sw_release
和 版本号
是:0xAABBCCTT,其中 AA 是主要部分,BB 是次要部分,CC 是补丁,TT 是类型。类型定义如下: >= 0
发展、 >= 64
:阿尔法版本、 >= 128
:测试版、 = 192
:RC 版本、 == 255
:发布版本。因此,举例来说,0x010402ff 相当于发布版本 v1.4.2。
该信息也可用于数据部分(但这是首选部分)。
M":多信息报文。
结构 ulog_message_info_multiple_header_s { 结构 消息标题 页眉; uint8_t is_continued; ///< 可用于数组 uint8_t key_len; 烧焦 密钥[key_len]; 烧焦 价值[页眉.msg_size-2-key_len] };
与信息报文相同,但可以有多个具有相同密钥的报文(解析器以列表形式存储)。信息
is_continued
可用于分割信息:如果设置为 1,则它是具有相同密钥的前一条信息的一部分。解析器可将所有多报文信息以二维列表的形式存储,其顺序与报文在日志中出现的顺序相同。P':参数信息。格式与
消息信息
.如果参数在运行时发生动态变化,也可在数据部分使用该信息。数据类型仅限于int32_t
,浮动
.Q":参数默认信息。
结构 ulog_message_parameter_default_header_s { 结构 消息标题 页眉; uint8_t 默认类型; uint8_t key_len; 烧焦 密钥[key_len]; 烧焦 价值[页眉.msg_size-2-key_len] };
默认类型
是一个比特字段,用于定义数值属于哪个(些)组。必须至少设置一位:1<<0
:全系统默认1<<1
:当前配置(例如机身)的默认值
日志可能不包含所有参数的默认值。在这种情况下,默认值等于参数值,而不同的默认类型会被独立处理。该信息也可用于数据部分。数据类型仅限于
int32_t
,浮动
.
本节在第一节开始前结束。 添加已登录消息
或 消息记录
信息,以先到者为准。
# 数据部分
以下信息属于本部分:
A": 用名称订阅一条信息,并给它一个在
消息数据
.这必须在第一个相应的消息数据
.结构 添加已登录消息 { 结构 消息标题 页眉; uint8_t multi_id; uint16_t msg_id; 烧焦 消息名称[页眉.msg_size-3]; };
multi_id
信息格式:同一信息格式可以有多个实例,例如系统有两个相同类型的传感器。默认情况下,第一个实例必须为 0。msg_id
:与之匹配的唯一 ID消息数据
数据。第一次使用时必须将其设置为 0,然后再增加。同样msg_id
不得在不同的订阅中使用两次,即使在取消订阅后也不行。消息名称
:要订阅的信息名称。必须与消息格式
定义。R':取消订阅信息,表示不再记录该信息(目前未使用)。
结构 删除已登录消息 { 结构 消息标题 页眉; uint16_t msg_id; };
D":包含记录的数据。
struct message_data_s { struct message_header_s header; uint16_t msg_id; uint8_t data[header.msg_size-2]; };
msg_id
:由添加已登录消息
留言数据
所定义的记录的二进制信息。消息格式
.有关填充字段的特殊处理,请参见上文。'L':记录字符串信息,即 printf 输出。
struct message_logging_s { struct message_header_s header; uint8_t log_level; uint64_t timestamp; char message[header.msg_size-9] };
时戳
单位:微秒、日志级别
:与 Linux 内核相同:
名称 | 水平值 | 意义 |
---|---|---|
应急 | '0' | 系统无法使用 |
警报 | '1' | 必须立即采取行动 |
CRIT | '2' | 关键条件 |
ERR | '3' | 错误条件 |
警告 | '4' | 警告条件 |
注意事项 | '5' | 正常但重要的条件 |
信息 | '6' | 信息 |
DEBUG | '7' | 调试级信息 |
'C':标记登录字符串信息
struct message_logging_tagged_s { struct message_header_s header; uint8_t log_level; uint16_t tag; uint64_t timestamp; char message[header.msg_size-9] };
标签
:代表日志信息字符串来源的 id。它可以代表进程、线程或类,具体取决于系统结构。例如,运行多个进程以控制不同有效载荷、外部磁盘、串行设备等的机载计算机的参考实现可以使用uint16_t 枚举
的标签属性中。标记的消息记录
结构如下enum class ulog_tag : uint16_t { unassigned, mavlink_handler, ppk_handler, camera_handler, ptp_handler, serial_handler, watchdog, io_service, cbuf, ulg };
时戳
单位:微秒日志级别
:与 Linux 内核相同:
名称 | 水平值 | 意义 |
---|---|---|
应急 | '0' | 系统无法使用 |
警报 | '1' | 必须立即采取行动 |
CRIT | '2' | 关键条件 |
ERR | '3' | 错误条件 |
警告 | '4' | 警告条件 |
注意事项 | '5' | 正常但重要的条件 |
信息 | '6' | 信息 |
DEBUG | '7' | 调试级信息 |
S":同步信息,这样阅读器就可以通过搜索下一个同步信息来恢复损坏的信息。
struct message_sync_s { struct message_header_s header; uint8_t sync_magic[8]; };
同步魔法
:[0x2F、0x73、0x13、0x20、0x25、0x0C、0xBB、0x12] (0x2F, 0x73, 0x13, 0x20, 0x25, 0x0C, 0xBB, 0x12)O":标记以毫秒为单位、持续时间一定的中断(记录信息丢失)。例如,如果设备速度不够快,就会出现丢失。
struct message_dropout_s { struct message_header_s header; uint16_t duration; };
I":信息提示。见上文。
M':多信息报文。见上文。
P':参数信息。参见上文。
Q":参数信息。见上文。
# 对解析器的要求
有效的 ULog 解析器必须满足以下要求:
必须忽略未知信息(但可以打印警告)。
还能解析未来/未知的文件格式版本(但会打印警告)。
必须拒绝解析包含未知不兼容位的日志 (
不兼容标记
的ulog_message_flag_bits_s
消息),这意味着日志中包含解析器无法处理的破坏性更改。解析器必须能够正确处理在信息中间突然结束的日志。未完成的信息应直接丢弃。
对于附加数据:解析器可以假设数据部分存在,即偏移量指向定义部分之后的位置。
附加数据必须被视为常规数据部分的一部分。
# 已知实施情况
- PX4-Autopilot:C++
- 记录仪模块 (打开新窗口)
- 重放模块 (打开新窗口)
- 硬故障日志模块 (打开新窗口):附加硬故障碰撞数据。
- pyulog (打开新窗口):带有 CLI 脚本的 python ULog 解析器库。
- 飞行图 (打开新窗口):Java, 日志绘图仪。
- pyFlightAnalysis (打开新窗口):基于 pyulog 的 Python 日志绘图仪和 3D 可视化工具。
- MAVLink (打开新窗口):通过 MAVLink 流式传输 ULog 的信息(注意不支持附加数据,至少不支持截断信息)。
- QGroundControl (打开新窗口):C++、通过 MAVLink 传输的 ULog 流以及用于 GeoTagging 的最低限度解析。
- mavlink-router (打开新窗口):C++、通过 MAVLink 的 ULog 流。
- MAVGA 分析 (打开新窗口):Java、通过 MAVLink 传输的 ULog 流以及用于绘图和分析的解析器。
- 情节魔术师 (打开新窗口):用于绘制日志和时间序列的 C++/Qt 应用程序。自 2.1.3 版起支持 ULog。
- ulogreader (打开新窗口):Javascript、ULog 阅读器和解析器以 JSON 对象格式输出日志。
# 文件格式版本历史
# 第 2 版中的更改
增加 ulog_message_info_multiple_header_s
和 ulog_message_flag_bits_s
信息和向日志添加数据的功能。该功能用于将崩溃数据添加到现有日志中。如果在日志中添加的数据在信息中间被剪切,则无法用版本 1 的分析程序进行分析。除此之外,如果解析器忽略未知信息,则可实现向前和向后兼容性。