# ULog 文件格式

ULog 是用于记录信息的文件格式。该格式具有自描述性,即包含格式和 uORB 记录的消息类型。本文档是 ULog 文件格式规范文档。它特别适用于有兴趣编写 ULog 分析器/序列器并需要对文件进行解码/编码的人员。

PX4 使用 ULog 将 uORB 主题记录为与以下来源相关(但不限于以下来源)的信息:

  • 设备输入: 传感器、RC 输入等
  • 内部状态: CPU 负载、姿态、EKF 状态等。
  • 字符串信息: printf 发言,包括 PX4_INFO()PX4_ERR().

格式使用 小二进制 (打开新窗口) 所有二进制类型的内存布局(数据类型的最小有效字节 (LSB) 位于最低内存地址)。

# 数据类型

以下二进制类型用于记录日志。它们都与 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].

字符串 (字符[长度]) 不包含终端 NULL 字符 '\0' 在最后。

备注

字符串比较是区分大小写的,在比较报文名称时应考虑到这一点。 添加订阅.

# ULog 文件结构

ULog 文件有以下三个部分:

---------------------- | 标题 | ---------------------- | 定义 | ---------------------- | 数据 | ----------------------

下文将对每个部分进行说明。

# 页眉部分

标头是固定大小的部分,格式如下(16 字节):

---------------------------------------------------------------------- | 0x55 0x4c 0x6f 0x67 0x01 0x12 0x35 | 0x01 | uint64_t | | 文件魔法 (7B) | 版本 (1B) | 时间戳 (8B) | ----------------------------------------------------------------------
  • 文件魔术(7 个字节): 文件类型指示符读作 "ULogXYZ,其中 XYZ 是神奇字节序列 0x01 0x12 0x35";
  • 版本(1 个字节): 文件格式版本(目前为 1)
  • 时间戳(8 字节): uint64_t 整数,表示记录开始的时间(微秒)。

# 定义 & 数据部分 报文头

定义和数据 各节包含许多 信息.每个报文前都有一个报文头:

结构 消息标题 {
  uint16_t msg_size;
  uint8_t msg_type;
};
  • msg_size 是不含报文头的报文大小(字节)。
  • msg_type 定义内容,是一个字符。

备注

下面的信息部分均以与其对应的字符作为前缀。 msg_type.

# 定义部分

定义部分包含软件版本、报文格式、初始参数值等基本信息。

本节中的报文类型有

  1. 标志位
  2. 格式定义
  3. 信息
  4. 多种信息
  5. 参数
  6. 默认参数

# 'B':标志位 信息

备注

该信息必须是 第一条信息 紧跟在文件头部分之后,因此它与文件开头的偏移量是固定不变的!

该信息为日志解析器提供日志是否可解析的信息。

结构 ulog_message_flag_bits_s {
  结构 消息标题 页眉; // msg_type = 'B'
  uint8_t compat_flags[8];
  uint8_t 不兼容标记[8];
  uint64_t 附加偏置[3]; 如果设置了追加位,则 // 追加数据的文件偏移量
};
  • compat_flags:兼容标志位

    • 这些标志表示日志文件中存在与任何 ULog 分析器兼容的特征。
    • compat_flags[0]: 默认参数 (位 0):如果设置,日志将包含 默认参数信息

    这些位可用于 ULog 未来与现有解析器兼容的变更。例如,可以通过在标准中定义一个新位来添加新的信息类型,而现有的解析器将忽略新的信息类型。这意味着如果其中一个未知位被设置,解析器就可以忽略该位。

  • 不兼容标记不兼容标志位。

    • incompat_flags[0]: DATA_APPENDED (位 0):如果设置,则日志包含附加数据,并且至少有一个 附加偏置 为非零。

    其余位目前未定义,必须设置为 0。这可用于引入现有解析器无法处理的破坏性变化。例如,当一个旧的 ULog 分析程序没有 DATA_APPENDED 读取较新的 ULog 时,它会停止解析日志,因为日志中会包含不符合规格的信息/概念。如果解析器发现任何未指定的位被设置,就必须拒绝解析日志。

  • 附加偏置:附加数据的文件偏移量(以 0 为基准)。如果不追加数据,则所有偏移量都必须为 0。这可用于可靠地为可能在信息中间停止的日志添加数据。例如,崩溃转储。

    附加数据的进程应该这样做:

    • 设置相关的 不兼容标记 位掩码
    • 设置第一个 附加偏置 为日志文件的长度,因为新数据将从这里开始。
    • 附加数据部分有效的任何类型的信息。

在未来的 ULog 规范中,可能会有更多字段附加在此报文的末尾。这意味着解析器不能假定此报文的长度是固定的。如果 msg_size 大于预期(目前为 40),任何额外的字节都必须被忽略/丢弃。

# 'F':格式信息

格式化信息以单个字符串定义单个信息名称及其内部字段。

结构 消息格式 {
  结构 消息标题 页眉; // msg_type = 'F'
  烧焦 格式[页眉.msg_size];
};
  • 格式 是一个纯文本字符串,格式如下: message_name:field0;field1;
    • 字段的数量可以任意设置(最少 1 个),字段之间用 ;.

A 领域 格式如下 类型 字段名或数组: 类型[数组长度] 字段名称 使用(只支持固定大小的数组)。

A 类型基本二进制类型消息名称 的格式定义(嵌套使用)。

  • 类型在定义之前就可以使用。
    • 例如:信息 信息A:信息B[2] msg_b 可以在 MessageB:uint_8[3] 数据
  • 可以任意嵌套,但 无循环依赖
    • 例如 信息A:信息B[2] msg_b 及样品; 信息B:信息A[4] msg_a

有些字段名称比较特殊:

  • 时戳:每个 订阅信息 必须包含一个时间戳字段
    • 其类型可以是 uint64_t (目前唯一使用的)、 uint32_t, uint16_tuint8_t.
    • 单位始终是微秒,除非在 uint8_t 其中单位为毫秒。
    • 时间戳必须始终是单调递增的。 msg_id.
    • 在使用小时间戳数据类型(如 uint8_t因此,日志编写者必须确保足够频繁地记录信息,以便能够检测到 包装 (当时间戳溢出数据类型并返回 0 时)
    • 在这种情况下,日志阅读器也必须处理环绕问题,并考虑掉线问题。
  • _padding{}:字段名称以 _ 填充 例如 _padding[3]) 不应显示,其数据必须被阅读器忽略。
    • 这些字段可由写入者插入,以确保正确对齐。
    • 如果填充字段是最后一个字段,则可以不记录该字段,以避免写入不必要的数据。
    • 这意味着 message_data_s.data 将因填充的大小而缩短。
    • 不过,在嵌套定义中使用报文时,仍然需要填充。

# 'I':信息留言

信息报文定义了字典类型定义 密钥 : 价值 对任何信息,包括但不限于硬件版本、软件版本、软件的构建工具链等。

结构 ulog_message_info_header_s {
  结构 消息标题 页眉; // msg_type = 'I'
  uint8_t key_len;
  烧焦 密钥[key_len];
  烧焦 价值[页眉.msg_size-1-key_len]
};
  • key_len:键值长度
  • 密钥:例如 char[value_len] sys_toolchain_ver
  • 价值:包含数据(长度为 value_len)对应的 密钥 例如 9.4.0.

备注

信息报文中定义的键值对应该是唯一的。也就是说,不应有多个具有相同键值的定义!

解析器可以字典形式存储信息。

预定义的信息报文有

密钥 说明 数值示例
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

备注

value_len 的数据大小。 价值.这在 密钥.

  • 的格式。 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 {
  结构 消息标题 页眉; // msg_type = 'M'
  uint8_t is_continued; // 可用于数组
  uint8_t key_len;
  烧焦 密钥[key_len];
  烧焦 价值[页眉.msg_size-2-key_len]
};
  • is_continued 可用于分割报文:如果设置为 1,则它是具有相同密钥的上一条报文的一部分。

解析器可将所有信息以二维列表的形式存储,其顺序与日志中出现的信息顺序相同。

# 'P':参数信息

中的参数信息 定义 部分定义了启动日志记录时载具的参数值。其格式与 信息留言.

结构 消息信息 {
  结构 消息标题 页眉; // msg_type = 'P'
  uint8_t key_len;
  烧焦 密钥[key_len];
  烧焦 价值[页眉.msg_size-1-key_len]
};

如果参数在运行时发生动态变化,该信息也可以是 用于数据部分 也是如此。

数据类型仅限于 int32_t浮动.

# 'Q':默认参数信息

默认参数信息定义了特定载具和设置的参数默认值。

结构 ulog_message_parameter_default_header_s {
  结构 消息标题 页眉; // msg_type = 'Q'
  uint8_t 默认类型;
  uint8_t key_len;
  烧焦 密钥[key_len];
  烧焦 价值[页眉.msg_size-2-key_len]
};
  • 默认类型 是一个位字段,用于定义该值属于哪个组。
    • 必须至少设置一位:
      • 1<<0:全系统默认
      • 1<<1:当前配置(例如机身)的默认值

日志可能不包含所有参数的默认值。在这种情况下,缺省值等于参数值,不同的缺省类型会被独立处理。

该信息也可用于数据部分,数据类型仅限于 int32_t浮动.

本节在第一节开始前结束。 订阅信息记录 信息,以先到者为准。

# 数据部分

中的信息类型 数据 部分是:

  1. 订阅
  2. 退订
  3. 记录数据
  4. 登录字符串
  5. 标签 已登录 字符串
  6. 同步
  7. 辍学标志
  8. 信息
  9. 多种信息
  10. 参数
  11. 默认参数

# A:订阅信息

通过名称订阅信息,并为其赋予一个在 记录的数据 信息.这必须在第一个相应的 记录的数据 信息.

结构 添加已登录消息 {
  结构 消息标题 页眉; // msg_type = 'A'
  uint8_t multi_id;
  uint16_t msg_id;
  烧焦 消息名称[页眉.msg_size-3];
};
  • multi_id信息格式:同一信息格式可以有多个实例,例如系统有两个相同类型的传感器。默认情况下,第一个实例必须为 0。
  • msg_id:与之匹配的唯一 ID 记录的数据 信息 数据。首次使用时必须将其设置为 0,然后再增加。
    • 相同 msg_id 不得重复用于不同的订阅。
  • 消息名称:要订阅的信息名称。必须与 信息格式 定义。

# R:退订信息

退订信息,标记不再记录该信息(目前未使用)。

结构 删除已登录消息 {
  结构 消息标题 页眉; // msg_type = 'R'
  uint16_t msg_id;
};

# 'D':记录的数据信息

结构 消息数据 {
  结构 消息标题 页眉; // msg_type = 'D'
  uint16_t msg_id;
  uint8_t 数据[页眉.msg_size-2];
};

有关填充字段的特殊处理,请参阅上文。

# 'L':记录的字符串信息

记录的字符串信息,即 printf() 输出。

结构 消息记录 {
  结构 消息标题 页眉; // msg_type = 'L'
  uint8_t 日志级别;
  uint64_t 时戳;
  烧焦 信息[页眉.msg_size-9]
};
  • 时戳单位:微秒
  • 日志级别:与 Linux 内核相同:
名称 水平值 意义
应急 '0' 系统无法使用
警报 '1' 必须立即采取行动
CRIT '2' 关键条件
ERR '3' 错误条件
警告 '4' 警告条件
注意事项 '5' 正常但重要的条件
信息 '6' 信息
DEBUG '7' 调试级信息

# 'C':标记的记录字符串信息

结构 标记的消息记录 {
  结构 消息标题 页眉; // msg_type = 'C'
  uint8_t 日志级别;
  uint16_t 标签;
  uint64_t 时戳;
  烧焦 信息[页眉.msg_size-9]
};
  • 标签:代表日志信息字符串来源的 id。它可以代表进程、线程或类,具体取决于系统结构。

    • 例如,针对运行多个进程以控制不同有效载荷、外部磁盘和串行设备等的机载计算机的参考实现,可以使用一个 uint16_t 枚举 进入 标签 属性如下
    枚举  ulog_tag : uint16_t {
      未分配,
      mavlink_handler,
      ppk_handler,
      相机处理程序,
      ptp_handler,
      串行处理程序,
      监察人,
      io_service,
      cbuf,
      ulg
    };
    
  • 时戳单位:微秒

  • 日志级别:与 Linux 内核相同:

名称 水平值 意义
应急 '0' 系统无法使用
警报 '1' 必须立即采取行动
CRIT '2' 关键条件
ERR '3' 错误条件
警告 '4' 警告条件
注意事项 '5' 正常但重要的条件
信息 '6' 信息
DEBUG '7' 调试级信息

# 'S':同步信息

同步信息,这样阅读器就可以通过搜索下一条同步信息来恢复已损坏的信息。

结构 消息同步 {
  结构 消息标题 页眉; // msg_type = 'S'
  uint8_t 同步魔法[8];
};
  • 同步魔法:[0x2F、0x73、0x13、0x20、0x25、0x0C、0xBB、0x12] (0x2F, 0x73, 0x13, 0x20, 0x25, 0x0C, 0xBB, 0x12)

# 'O':辍学信息

标记以毫秒为单位、持续时间为给定值的中断(丢失日志信息)。

例如,如果设备速度不够快,就会出现掉线现象。

结构 消息注销 {
  结构 消息标题 页眉; // msg_type = 'O'
  uint16_t 会期;
};

# 与定义科共享的信息

由于 "定义 "部分和 "数据 "部分使用相同的报文头格式,因此它们也共享下面列出的相同报文:

# 对解析器的要求

有效的 ULog 解析器必须满足以下要求:

  • 必须忽略未知信息(但可以打印警告信息)
  • 还能解析未来/未知的文件格式版本(但会打印警告)。
  • 必须拒绝解析包含未知不兼容位的日志 (不兼容标记标志位 信息) 表示日志中包含解析器无法处理的破坏性更改。
  • 解析器必须能够正确处理在信息中间突然结束的日志。未完成的信息应直接丢弃。
  • 对于附加数据:解析器可以假设数据部分存在,即偏移量指向定义部分之后的位置。
    • 附加数据必须被视为常规数据部分的一部分。

# 已知的解析器实现

# 文件格式版本历史

# 第 2 版中的更改

  • 增加 多种信息消息标志位 信息 以及向日志添加数据的功能。
    • 用于将碰撞数据添加到现有日志中。
    • 如果数据被添加到日志中,而日志在信息中间被剪切,则无法使用版本 1 解析器进行解析。
  • 除此之外,如果解析器忽略未知信息,则可实现向前和向后兼容性。