【NextPilot日志移植】ULog

发布于:2025-05-13 ⋅ 阅读:(13) ⋅ 点赞:(0)

📚 ULog 日志系统详解

关键词:结构化日志、飞行数据记录、自描述格式、嵌入式系统、PX4、NextPilot


🧠 一、ULog 是什么?

ULog(Universal Log) 是 PX4/NextPilot 飞控系统中使用的结构化日志格式,用于记录飞行过程中的传感器数据、参数、状态信息、错误日志等。

✅ 主要用途:

  • 飞行数据记录(如 IMU、GPS、电池状态)
  • 参数变化记录(PID 调整、RC 映射等)
  • 调试输出(如 PX4_INFO、PX4_WARN、PX4_ERR)
  • 系统状态分析(如 CPU 负载、内存使用)

🧱 二、ULog 的核心特性

特性 描述
结构化 每条日志都有固定格式,便于程序解析
自描述 日志文件本身包含数据结构定义,无需外部依赖
可扩展性强 支持多种消息类型,满足不同场景需求
高效性 使用缓冲机制减少频繁 I/O 操作
兼容性 支持版本控制,避免日志无法解析
跨平台 可在 PX4、ROS、Python、MATLAB 等平台解析

📦 三、ULog 文件结构

ULog 文件由多个**消息(Messages)**组成,每个消息都包含一个通用头部(ulog_message_header_s),后续数据根据消息类型不同而变化。

✅ 1. 文件头(ulog_file_header_s

struct ulog_file_header_s {
    uint8_t  magic[8];   // 文件魔数(标识文件类型)
    uint64_t timestamp;   // 日志起始时间戳(单位:微秒)
};
  • magic:固定值 0x16 0x2d 0x3a,ASCII 字符为 "LOGxxxxxx",用于识别文件类型。
  • timestamp:记录日志的起始时间,用于时间戳对齐。

✅ 2. 通用消息头(ulog_message_header_s

struct ulog_message_header_s {
    uint16_t msg_size; // 消息体大小(不含头部)
    uint8_t  msg_type; // 消息类型(如 'F'、'D'、'I'、'P' 等)
};
  • msg_size:消息体的字节数。
  • msg_type:消息类型,用于解析后续数据结构。

✅ 3. 消息类型(ULogMessageType

enum class ULogMessageType : uint8_t {
    FORMAT            = 'F',
    DATA              = 'D',
    INFO              = 'I',
    INFO_MULTIPLE     = 'M',
    PARAMETER         = 'P',
    PARAMETER_DEFAULT = 'Q',
    ADD_LOGGED_MSG    = 'A',
    REMOVE_LOGGED_MSG = 'R',
    SYNC              = 'S',
    DROPOUT           = 'O',
    LOGGING           = 'L',
    LOGGING_TAGGED    = 'C',
    FLAG_BITS         = 'B',
};
常用消息类型说明:
类型 字符 含义
'F' FORMAT 定义数据结构(如 sensor_combined:uint64_t timestamp;float x;float y;
'D' DATA 记录实际数据(如传感器数值)
'I' INFO 记录键值对信息(如 sys_toolchain_ver9.4.0
'P' PARAMETER 记录参数值(如 RC_MAP_THROTTLE=1100
'Q' PARAMETER_DEFAULT 记录默认参数值(用于对比是否被修改过)
'A' ADD_LOGGED_MSG 订阅某个日志主题(如 sensor_combined
'R' REMOVE_LOGGED_MSG 取消订阅某个日志主题
'S' SYNC 同步消息,用于日志文件的同步
'O' DROPOUT 记录数据丢失时间长度(如 duration=100ms
'L' LOGGING 记录调试日志(如 PX4_INFO("Debug message")
'B' FLAG_BITS 标记日志文件的兼容性标志

📝 四、ULog 数据结构详解

✅ 1. ulog_message_format_s —— 数据格式定义

struct ulog_message_format_s {
    uint16_t msg_size; ///< size of message - ULOG_MSG_HEADER_LEN
    uint8_t  msg_type = static_cast<uint8_t>(ULogMessageType::FORMAT);
    char     format[1500]; ///< 格式字符串,如 "sensor:uint64_t timestamp;float x;float y;"
};
  • 作用:描述一个日志主题的数据结构。
  • 示例
    sensor:uint64_t timestamp;float x;float y;float z;
    
    表示 sensor 主题包含时间戳和 x/y/z 浮点数值。

✅ 2. ulog_message_add_logged_s —— 添加日志主题

struct ulog_message_add_logged_s {
    uint16_t msg_size; ///< size of message - ULOG_MSG_HEADER_LEN
    uint8_t  msg_type = static_cast<uint8_t>(ULogMessageType::ADD_LOGGED_MSG);
    uint8_t  multi_id; ///< 多实例 ID(用于多传感器)
    uint16_t msg_id;   ///< 消息 ID(用于关联数据)
    char     message_name[255]; ///< uORB 主题名称(如 "sensor_combined")
};
  • 作用:告诉解析器,接下来要记录的主题名称和 ID。
  • 示例
    msg_id = 0x12
    message_name = "sensor_combined"
    

✅ 3. ulog_message_data_s —— 数据记录

struct ulog_message_data_s {
    uint16_t msg_size;
    uint8_t  msg_type = static_cast<uint8_t>(ULogMessageType::DATA);
    uint16_t msg_id;
};
  • 作用:记录实际数据(如传感器数值)。
  • 数据结构
    • msg_id:关联到 ADD_LOGGED_MSG 中的 msg_id
    • 后续数据:根据 FORMAT 定义的数据结构存储

✅ 4. ulog_message_info_sulog_message_info_multiple_s —— 信息记录

struct ulog_message_info_s {
    uint16_t msg_size;
    uint8_t  msg_type = static_cast<uint8_t>(ULogMessageType::INFO);
    uint8_t key_len;            ///< 键长度
    char    key_value_str[255]; ///< 键值对字符串
};
  • 作用:记录元信息(如参数、系统信息、错误日志等)。
  • 示例
    sys_toolchain_ver9.4.0
    sensor_gyro_rate=200 Hz
    

✅ 5. ulog_message_parameter_sulog_message_parameter_default_s —— 参数记录

struct ulog_message_parameter_s {
    uint16_t msg_size;
    uint8_t  msg_type = static_cast<uint8_t>(ULogMessageType::PARAMETER);
    uint8_t key_len;
    char    key_value_str[255]; ///< 键值对字符串
};
  • 作用:记录系统参数(如 PID 参数、RC 映射等)。
  • 区别
    • 'P':当前参数值。
    • 'Q':默认参数值(用于对比是否被修改过)。

✅ 6. ulog_message_dropout_s —— 丢包信息

struct ulog_message_dropout_s {
    uint16_t msg_size = sizeof(uint16_t); ///< size of message - ULOG_MSG_HEADER_LEN
    uint8_t  msg_type = static_cast<uint8_t>(ULogMessageType::DROPOUT);
    uint16_t duration; ///< in ms
};
  • 作用:记录数据丢失的时间长度。
  • 使用场景:当系统无法实时记录所有数据时,插入此消息表示“这段数据可能丢失”。

✅ 7. ulog_message_logging_sulog_message_logging_tagged_s —— 日志输出

struct ulog_message_logging_s {
    uint16_t msg_size;
    uint8_t  msg_type = static_cast<uint8_t>(ULogMessageType::LOGGING);
    uint8_t  log_level; ///< 日志级别(如 ERROR、WARN、INFO、DEBUG)
    uint64_t timestamp;
    char     message[128]; ///< 日志内容
};
  • 作用:记录调试信息(如 PX4_INFO("Debug message") 输出的内容)。
  • 使用场景:用于调试飞行控制逻辑,定位问题。

✅ 8. ulog_message_flag_bits_s —— 兼容性标志

struct ulog_message_flag_bits_s {
    uint16_t msg_size;
    uint8_t  msg_type = static_cast<uint8_t>(ULogMessageType::FLAG_BITS);
    uint8_t  compat_flags[8];   // 兼容性标志
    uint8_t  incompat_flags[8]; // 不兼容标志
    uint64_t appended_offsets[3]; // 附加数据偏移
};
  • 作用:用于标记日志文件的兼容性信息。
  • 使用场景:新版本飞控写入的文件,旧版本工具可以读取并判断是否兼容。

🔄 五、ULog 的工作流程

✅ 1. 初始化日志文件

ulog_file_header_s header;
header.magic = "LOGxxxxxx"; // 文件魔数
header.timestamp = hrt_absolute_time(); // 当前时间戳
write(fd, &header, sizeof(header)); // 写入文件头

✅ 2. 定义数据结构(FORMAT 消息)

ulog_message_format_s format_msg;
format_msg.msg_type = 'F';
strcpy(format_msg.format, "sensor:uint64_t timestamp;float x;float y;float z;");
write(fd, &format_msg, sizeof(format_msg) + strlen(format_msg.format));

✅ 3. 订阅日志主题(ADD_LOGGED_MSG)

ulog_message_add_logged_s add_msg;
add_msg.msg_type = 'A';
add_msg.multi_id = 0;
strcpy(add_msg.message_name, "sensor_combined");
write(fd, &add_msg, sizeof(add_msg) + strlen(add_msg.message_name));

✅ 4. 写入数据(DATA 消息)

ulog_message_data_s data_msg;
data_msg.msg_type = 'D';
data_msg.msg_id = 0x12; // 对应 ADD_LOGGED_MSG 的 msg_id

// 实际数据紧跟其后
sensor_combined_s data;
data.timestamp = hrt_absolute_time();
data.x = 1.2f;
data.y = 3.4f;
data.z = 5.6f;

write(fd, &data_msg, sizeof(data_msg));
write(fd, &data, sizeof(data));

✅ 5. 记录日志信息(INFO / PARAMETER)

ulog_message_info_s info_msg;
info_msg.msg_type = 'I';
strcpy(info_msg.key_value_str, "sys_toolchain_ver9.4.0");

write(fd, &info_msg, sizeof(info_msg) + strlen(info_msg.key_value_str));

✅ 6. 记录丢包信息(DROPOUT)

ulog_message_dropout_s dropout;
dropout.msg_type = 'O';
dropout.duration = 100; // 丢包时间长度(ms)

write(fd, &dropout, sizeof(dropout));

✅ 7. 文件尾部标志(可选)

某些日志系统会写入一个 EOF 标志,表示日志结束。


📊 六、ULog 的典型应用场景

场景 使用的消息类型
记录传感器数据 'D'(DATA)
记录参数设置 'P'(PARAMETER)
记录日志输出 'L'(LOGGING)
描述数据结构 'F'(FORMAT)
记录丢包信息 'O'(DROPOUT)
标记文件兼容性 'B'(FLAG_BITS)

🧩 七、ULog 的设计亮点

亮点 描述
模块化设计 4个日志主题独立管理,便于扩展
异步写入 使用后台线程处理 I/O,不影响主线程性能
线程安全 使用 mutex + condition variable 保证线程安全
高性能优化 使用缓冲机制减少频繁写磁盘操作
加密支持 可选编译项,增强安全性
性能统计 使用 perf_counter 记录写入和 fsync 时间

🧠 八、ULog 的优势总结

优势 描述
结构化存储 每条日志都有固定格式,便于解析
自描述性 每条数据都包含格式信息
兼容性设计 支持新旧版本兼容,避免日志无法解析
跨平台支持 可用 QGroundControl、Python、MATLAB 解析
加密功能 保护敏感飞行数据
调试友好 支持调试输出、参数变化记录

📌 九、ULog 文件示例(简化版)

[File Header]         // 文件魔数和起始时间
[F] sensor:uint64_t timestamp;float x;float y;float z;
[A] msg_id=0x12, message_name=sensor_combined
[D] msg_id=0x12, data=0x12345678 0x00000000 0x40000000 0x40800000
[I] sys_toolchain_ver9.4.0
[P] RC_MAP_THROTTLE=1100
[O] duration=100ms

📈 十、ULog 的文件格式示意图

[File Header]         // 文件魔数 + 时间戳
[FORMAT Messages]     // 描述各个日志主题的结构
[ADD_LOGGED_MSG]      // 定义哪些主题被记录
[DATA Messages]       // 实际记录的数据(传感器、参数等)
[INFO / PARAMETER Messages] // 元信息和参数记录
[DROPOUT Messages]   // 丢包信息
[FLAG_BITS Messages]  // 兼容性标志

🧱 十一、ULog 的典型使用流程

// 初始化日志文件
Logger logger(LogWriter::BackendFile, 1024 * 1024);
logger.start_log_file(LogType::Full, "/mnt/microsd/log001.ulg");

// 写入数据
logger.write_message(LogType::Full, data_ptr, data_size);

// 停止日志
logger.stop_log_file(LogType::Full);

📌 十二、移植建议(从 PX4/NextPilot 到其他系统)

✅ 保留核心功能

  • LogWriterFile:文件日志写入器
  • ULogMessageType:消息类型定义
  • LogFileBuffer:缓冲区管理
  • write_message():日志写入接口

✅ 可选功能

  • 加密支持(PX4_CRYPTO
  • 性能统计(perf_counter
  • 多线程支持(pthread