# 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_tuint8_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 消息),这意味着日志中包含解析器无法处理的破坏性更改。

  • 解析器必须能够正确处理在信息中间突然结束的日志。未完成的信息应直接丢弃。

  • 对于附加数据:解析器可以假设数据部分存在,即偏移量指向定义部分之后的位置。

    附加数据必须被视为常规数据部分的一部分。

# 已知实施情况

# 文件格式版本历史

# 第 2 版中的更改

增加 ulog_message_info_multiple_header_sulog_message_flag_bits_s 信息和向日志添加数据的功能。该功能用于将崩溃数据添加到现有日志中。如果在日志中添加的数据在信息中间被剪切,则无法用版本 1 的分析程序进行分析。除此之外,如果解析器忽略未知信息,则可实现向前和向后兼容性。