Формат файлу ULog
ULog - це формат файлу, що використовується для логування повідомлень. Формат самоописуючий, тобто містить формат та типи повідомлень uORB, які реєструються. Цей документ призначений для документації специфікації формату файлу ULog. Це призначено особливо для тих, хто зацікавлений у написанні розбірника / серіалізатора ULog та потребує розкодувати / закодувати файли.
PX4 використовує ULog для ведення журналу тем uORB як повідомлення, пов'язані з (але не обмежені) наступними джерелами:
- Пристрій входу: датчики, RC, вхід тощо.
- Внутрішні стани: Завантаження ЦП, ставлення, стан 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 |
float | 4 |
double | 8 |
bool, char | 1 |
Додатково типи можуть бути використані як масив фіксованого розміру: наприклад, float[5]
.
Рядки (char[length]
) не містять завершуючий нульовий символ '\0'
в кінці.
Порівняння рядків чутливе до регістру, що слід враховувати при порівнянні імен повідомлень під час додавання підписок.
Структура файлу ULog
Файли ULog мають наступні три розділи:
----------------------
| Header |
----------------------
| Definitions |
----------------------
| Data |
----------------------
Опис кожного розділу наведено нижче.
Розділ заголовка
Заголовок є розділом фіксованого розміру та має наступний формат (16 байт):
plain
----------------------------------------------------------------------
| 0x55 0x4c 0x6f 0x67 0x01 0x12 0x35 | 0x01 | uint64_t |
| File magic (7B) | Version (1B) | Timestamp (8B) |
----------------------------------------------------------------------
- Файлова магія (7 байт): Індикатор типу файлу, який читає "ULogXYZ де XYZ - це послідовність магічних байтів
0x01 0x12 0x35
" - Версія (1 байт): Версія формату файлу (наразі 1)
- Мітка часу (8 байтів):
uint64_t
ціле число, що вказує на початок реєстрації в мікросекундах.
Заголовок повідомлення розділу визначення та даних
Секції Визначення та Дані містять кілька повідомлень. Кожне повідомлення передує цим заголовком:
c
struct message_header_s {
uint16_t msg_size;
uint8_t msg_type;
};
msg_size
є розміром повідомлення в байтах без заголовка.msg_type
визначає вміст і є одним байтом.
Розділи повідомлення нижче передуються символом, який відповідає його msg_type
.
Розділ визначень
Розділ визначень містить основну інформацію, таку як версія програмного забезпечення, формат повідомлення, початкові значення параметрів тощо.
Основні типи повідомлень в цьому розділі є:
'B': Повідомлення флагів бітів
Це повідомлення повинно бути першим повідомленням одразу після розділу заголовка, щоб мати постійний фіксований зсув від початку файлу!
Це повідомлення надає інформацію лог-аналізатору про те, чи можна аналізувати журнал.
c
struct ulog_message_flag_bits_s {
struct message_header_s header; // msg_type = 'B'
uint8_t compat_flags[8];
uint8_t incompat_flags[8];
uint64_t appended_offsets[3]; // file offset(s) for appended data if appending bit is set
};
compat_flags
: біти сумісності прапорців- Ці прапорці вказують на наявність функцій у файлі журналу, які сумісні з будь-яким парсером ULog.
compat_flags[0]
: DEFAULT_PARAMETERS (Біт 0): якщо встановлено, журнал містить повідомлення про типові параметри
Решта бітів наразі не визначені і повинні бути встановлені на 0. Ці біти можуть бути використані для майбутніх змін ULog, які сумісні з існуючими парсерами. Наприклад, додавання нового типу повідомлення може бути вказане шляхом визначення нового біта у стандарті, а існуючі парсери ігноруватимуть новий тип повідомлення. Це означає, що парсери можуть просто ігнорувати біти, якщо один з невідомих бітів встановлений.
incompat_flags
: несумісні біти прапорців.incompat_flags[0]
: DATA_APPENDED (Біт 0): якщо встановлено, журнал містить додані дані та щонайменше один зappended_offsets
не є нульовим.
Решта бітів наразі не визначені і повинні бути встановлені на 0. Це може бути використано для введення руйнівних змін, які існуючі парсери не можуть обробити. Наприклад, коли старий парсер ULog, який не мав поняття DATA_APPENDED, читає новий ULog, він припинить розбір журналу, оскільки журнал міститиме повідомлення / концепції, які не відповідають специфікації. Якщо парсер виявляє, що будь-який з цих бітів встановлений, що не вказано, він повинен відмовитися від аналізу журналу.
appended_offsets
: Зсув файлу (0-основний) для додаваних даних. Якщо даних не додається, всі зміщення повинні бути нульовими. Це може бути використано для надійного додавання даних до журналів, які можуть припинитись посеред повідомлення. Наприклад, дампи збоїв.Процес додавання даних повинен виконати:
- встановити відповідний біт
incompat_flags
- встановіть перший
appended_offsets
, який наразі дорівнює 0, до довжини файлу журналу без доданих даних, оскільки саме там почнуться нові дані - додайте будь-який тип повідомлень, які є дійсними для розділу Дані.
- встановити відповідний біт
Можливо, що у майбутніх специфікаціях ULog буде додано ще декілька полів в кінці цього повідомлення. Це означає, що парсер не повинен припускати фіксовану довжину цього повідомлення. Якщо розмір msg_size
більший, ніж очікувалося (зараз 40), будь-які додаткові байти повинні бути проігноровані / відкинуті.
'F': Формат повідомлення
Формат повідомлення визначає одне ім'я повідомлення та його внутрішні поля в одному рядку.
c
struct message_format_s {
struct message_header_s header; // msg_type = 'F'
char format[header.msg_size];
};
format
- це рядок звичайного тексту із наступним форматом:message_name:field0;field1;
- Може бути довільна кількість полів (мінімум 1), розділених за допомогою
;
. message_name
: довільний непорожній рядок з цими допустимими символами:a-zA-Z0-9_-/
(і відмінний від будь-якого з основних типів).
- Може бути довільна кількість полів (мінімум 1), розділених за допомогою
Поле має формат: тип назва_поля
, або для масиву: тип[довжина_масиву] назва_поля
використовується (підтримуються лише масиви фіксованого розміру). field_name
повинен складатися з символів у наборі a-zA-Z0-9_
.
Тип
- один із основних бінарних типів або message_name
іншого визначення формату (вкладене використання).
- Тип може бути використаний до того, як він буде визначений.
- наприклад, Повідомлення
MessageA:MessageB[2] msg_b
може прийти доMessageB:uint_8[3] data
- наприклад, Повідомлення
- Може бути довільне вкладення, але без циклічних залежностей
- e.g.
MessageA:MessageB[2] msg_b
&MessageB:MessageA[4] msg_a
- e.g.
Деякі назви полів є спеціальними:
відмітка часу
: кожен формат повідомлення з Повідомленням про підписку повинен містити поле відмітки часу (наприклад, формат повідомлення, який використовується лише як частина вкладеного визначення іншим форматом, може не містити поля відмітки часу)- Його тип повинен бути
uint64_t
. - Одиниця вимірювання - мікросекунди.
- Часовий позначення завжди повинно бути монотонно зростаючим для серії повідомлень з тим самим
msg_id
(тією самою підпискою).
- Його тип повинен бути
_padding{}
: імена полів, які починаються з_padding
(наприклад,_padding[3]
), не повинні відображатися, і їх дані повинні бути ігноровані читачем.- Ці поля можуть бути вставлені письменником для забезпечення правильного вирівнювання.
- Якщо поле відступу є останнім полем, тоді це поле може не бути зареєстроване, щоб уникнути запису непотрібних даних.
- Це означає, що
message_data_s.data
буде коротшим на розмір відступу. - Однак відступ все ще потрібен, коли повідомлення використовується во вкладеному визначенні.
- Загалом, поля повідомлень не обов'язково вирівняні (тобто зсув поля всередині повідомлення не обов'язково є кратним його розміру даних), тому читач завжди повинен використовувати відповідні методи копіювання пам'яті для доступу до окремих полів.
'I': Інформаційне повідомлення
Повідомлення про інформацію визначає типове визначення словника key
: value
пари для будь-якої інформації, включаючи, але не обмежуючись, версію апаратного забезпечення, версію програмного забезпечення, засіб збірки для програмного забезпечення тощо.
c
struct ulog_message_info_header_s {
struct message_header_s header; // msg_type = 'I'
uint8_t key_len;
char key[key_len];
char value[header.msg_size-1-key_len]
};
key_len
: Довжина значення ключаkey
: Містить рядок ключа у форміtype name
, наприклад,char[value_len] sys_toolchain_ver
. Допустимі символи для імені:a-zA-Z0-9_-/
. Тип може бути одним з основних типів, включаючи масиви.значення
: Містить дані (з довжиноюvalue_len
), що відповідають ключуkey
, наприклад9.4.0
.
INFO
Ключ, визначений у повідомленні Інформації, повинен бути унікальним. Означає, що не повинно бути більше одного визначення з таким самим ключовим значенням.
Парсери можуть зберігати інформаційні повідомлення у вигляді словника.
Попередньо визначені інформаційні повідомлення:
key | Опис | Приклад для значення |
---|---|---|
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 tag) | "7f65e01" |
char[value_len] ver_sw_branch | git branch | "master" |
uint32_t ver_sw_release | Версія програмного забезпечення (див. нижче) | 0x010401ff |
char[value_len] sys_os_name | Назва операційної системи | "Linux" |
char[value_len] sys_os_ve r | Версія ОС (git tag) | "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, rev A" |
char[value_len] sys_uuid | Унікальний ідентифікатор для транспортного засобу (наприклад, ідентифікатор MCU) | "392a93e32fa3"... |
char[value_len] log_type | Тип журналу (повний журнал, якщо не вказано) | "mission" |
char[value_len] replay | Ім'я файлу відтвореного журналу, якщо в режимі відтворення | "log001.ulg" |
int32_t time_ref_utc | Зсув часу UTC в секундах | -3600 |
value_
відображає розмір даних значення
. Це описано у key
.
- Формат
ver_sw_release
таver_os_release
становить: 0xAABBCCTT, де AA - major, BB - ** minor **, CC - патч, а TT - type.- Тип визначається наступним чином:
>= 0
: розробка,>= 64
: альфа-версія,>= 128
: бета-версія,>= 192
: RC-версія,== 255
: версія релізу. - Наприклад,
0x010402FF
перекладається у випуск версії v1.4.2.
- Тип визначається наступним чином:
Це повідомлення також може бути використано в розділі Дані (це, однак, переважний розділ).
'M': Багатоінформаційне повідомлення
Повідомлення з багатою інформацією слугує такій же меті, як і повідомлення інформації, але для довгих повідомлень або кількох повідомлень з одним і тим же ключем.
c
struct ulog_message_info_multiple_header_s {
struct message_header_s header; // msg_type = 'M'
uint8_t is_continued; // can be used for arrays
uint8_t key_len;
char key[key_len];
char value[header.msg_size-2-key_len]
};
is_continued
може бути використаний для розділення повідомлень: якщо встановлено 1, це частина попереднього повідомлення з тим самим ключем.
Парсери можуть зберігати всю інформацію про багато повідомлень у вигляді 2D-списку, використовуючи той самий порядок, що й повідомлення в лог-файлі.
Дійсні імена та типи такі ж, як для повідомлення Інформація.
'P': Повідомлення параметра
Повідомлення параметра в розділі Визначення визначає значення параметрів транспортного засобу при початку реєстрації. Він використовує той самий формат, що і Повідомлення інформації.
c
struct message_info_s {
struct message_header_s header; // msg_type = 'P'
uint8_t key_len;
char key[key_len];
char value[header.msg_size-1-key_len]
};
Якщо параметр динамічно змінюється під час виконання, це повідомлення також може бути використано в розділі Дані також.
Тип даних обмежений int32_t
та float
. Допустимі символи для імені: a-zA-Z0-9_-/
.
'Q': Параметр повідомлення за замовчуванням
Повідомлення параметра за замовчуванням визначає значення параметра для вказаного транспортного засобу та налаштувань.
c
struct ulog_message_parameter_default_header_s {
struct message_header_s header; // msg_type = 'Q'
uint8_t default_types;
uint8_t key_len;
char key[key_len];
char value[header.msg_size-2-key_len]
};
default_types
є бітовим полем і визначає, до якої (яких) групи(ів) належить значення.- Принаймні один біт повинен бути встановлений:
1<<0
: системний стандарт за замовчуванням1<<1
: за замовчуванням для поточної конфігурації (наприклад, конструкція повітряного корпусу)
- Принаймні один біт повинен бути встановлений:
Журнал не може містити значень за замовчуванням для всіх параметрів. У цих випадках значення за замовчуванням дорівнює значенню параметра, а різні типи за замовчуванням розглядаються незалежно один від одного.
Це повідомлення також може бути використане в розділі Дані, і застосовується той самий тип даних та найменування, як і для повідомлення Параметр.
Цей розділ закінчується до початку першого Повідомлення про підписку або повідомлення Логування, яке спочатку.
Розділ даних
Основні типи повідомлень в розділі Дані є:
- Підписка
- Відписка
- Зареєстровані дані
- Зареєстрована рядок
- Позначений Зареєстрована рядок
- Синхронізація
- Відмова відмови
- Інформація
- Multi Information
- Параметр
- Параметр за замовчуванням
A
: Повідомлення про підписку
Підписати повідомлення за назвою та надати йому ідентифікатор, який використовується в Зареєстрованому повідомленні Даних. Це повинно бути перед першим відповідним Повідомленням про зареєстровані дані.
c
struct message_add_logged_s {
struct message_header_s header; // msg_type = 'A'
uint8_t multi_id;
uint16_t msg_id;
char message_name[header.msg_size-3];
};
multi_id
: той самий формат повідомлення може мати кілька екземплярів, наприклад, якщо система має два датчики одного типу. Стандартне та перше значення повинно бути 0.msg_id
: унікальний ідентифікатор для відповідності даним Повідомлення про зареєстровані дані. Перше використання повинно встановити це на 0, а потім збільшувати його.- Той же
msg_id
не повинен використовуватися двічі для різних підписок.
- Той же
message_name
: назва повідомлення для підписки. Повинно відповідати одному з визначень Форматованого повідомлення.
R
: Повідомлення про відписку
Відмовитися від повідомлення, щоб позначити, що воно більше не буде реєструватися (зараз не використовується).
c
struct message_remove_logged_s {
struct message_header_s header; // msg_type = 'R'
uint16_t msg_id;
};
'D': Повідомлення про зареєстровані дані
c
struct message_data_s {
struct message_header_s header; // msg_type = 'D'
uint16_t msg_id;
uint8_t data[header.msg_size-2];
};
msg_id
: як визначено у Повідомленні підпискидані
містять зареєстроване бінарне повідомлення, визначене за допомогою Форматування повідомлення
Див. вище для спеціальної обробки полів відступу.
'L': Повідомлення про зареєстрований рядок
Зареєстрований рядковий повідомлення, тобто вихід printf()
.
c
struct message_logging_s {
struct message_header_s header; // msg_type = 'L'
uint8_t log_level;
uint64_t timestamp;
char message[header.msg_size-9]
};
часовий_відміток
: у мікросекундахlog_level
: те саме, що і в ядрі Linux:
Назва | Значення рівня | Значення |
---|---|---|
EMERG | '0' | Система непридатна до використання |
ALERT | '1' | Дії повинні бути вжиті негайно |
CRIT | '2' | Критичні умови |
ERR | '3' | Умови помилки |
WARNING | '4' | Умови попередження |
NOTICE | '5' | Нормальний, але значущий стан |
INFO | '6' | Інформаційний |
DEBUG | '7' | Повідомлення рівня налагодження |
'C': позначене зареєстроване рядкове повідомлення
c
struct message_logging_tagged_s {
struct message_header_s header; // msg_type = 'C'
uint8_t log_level;
uint16_t tag;
uint64_t timestamp;
char message[header.msg_size-11]
};
tag
: id представляє джерело записаного рядка повідомлення. Це може представляти процес, потік або клас в залежності від архітектури системи.- Наприклад, референсна реалізація для бортового комп'ютера, що виконує кілька процесів для керування різними навантаженнями, зовнішніми дисками, послідовними пристроями тощо, може кодувати ідентифікатори цих процесів, використовуючи
uint16_t enum
у атрибутіtag
структури наступним чином:
cenum class ulog_tag : uint16_t { unassigned, mavlink_handler, ppk_handler, camera_handler, ptp_handler, serial_handler, watchdog, io_service, cbuf, ulg };
- Наприклад, референсна реалізація для бортового комп'ютера, що виконує кілька процесів для керування різними навантаженнями, зовнішніми дисками, послідовними пристроями тощо, може кодувати ідентифікатори цих процесів, використовуючи
часовий_відміток
: у мікросекундахlog_level
: те саме, що і в ядрі Linux:
Назва | Значення рівня | Значення |
---|---|---|
EMERG | '0' | Система непридатна до використання |
ALERT | '1' | Дії повинні бути вжиті негайно |
CRIT | '2' | Критичні умови |
ERR | '3' | Умови помилки |
WARNING | '4' | Умови попередження |
NOTICE | '5' | Нормальний, але значущий стан |
INFO | '6' | Інформаційне |
DEBUG | '7' | Повідомлення рівня налагодження |
'S': Повідомлення синхронізації
Повідомлення синхронізації, щоб читач міг відновитися від пошкодженого повідомлення, шукаючи наступне повідомлення синхронізації.
c
struct message_sync_s {
struct message_header_s header; // msg_type = 'S'
uint8_t sync_magic[8];
};
sync_magic
: [0x2F, 0x73, 0x13, 0x20, 0x25, 0x0C, 0xBB, 0x12]
'O': Повідомлення про відключення
Позначте відсутність (втрачені повідомлення журналювання) заданої тривалості в мс.
Відключення можуть виникати, наприклад, якщо пристрій не є достатньо швидким.
c
struct message_dropout_s {
struct message_header_s header; // msg_type = 'O'
uint16_t duration;
};
Повідомлення, розділені розділом визначень
Оскільки Розділи Визначень та Дані використовують той же формат заголовка повідомлення, вони також діляться тими ж повідомленнями, які перераховані нижче:
- Інформаційне повідомлення.
- Мульти-інформаційне повідомлення
- Повідомлення параметра
- Для розділу Дані, Повідомлення Параметра використовується, коли змінюється значення параметра
- Повідомлення параметра за замовчуванням
Вимоги до Parsers
Дійсний розбірник ULog повинен відповідати наступним вимогам:
- Повинен ігнорувати невідомі повідомлення (але може вивести попередження)
- Розбирайте майбутні/невідомі версії формату файлу також (але може вивести попередження).
- Повинен відмовитися від аналізу журналу, який містить невідомі біти несумісності (
incompat_flags
у Повідомлення про біти прапорців), що означає, що журнал містить руйнівні зміни, які парсер не може обробити. - Парсер повинен правильно обробляти журнали, які раптово закінчуються, посеред повідомлення. Недовершене повідомлення слід просто викинути.
- Для доданих даних: парсер може вважати, що секція Даних існує, тобто зміщення вказує на місце після секції Визначень.
- Додані дані повинні трактуватися так, ніби вони були частиною звичайного розділу даних.
Відомі Реалізації Парсера
- PX4-Autopilot++: C++
- logger module
- replay module
- Модуль hardfault_log: додайте дані про аварійне завершення.
- pyulog: бібліотека python, ULog для зчитування та запису з CLI скриптами.
- ulog_cpp: C++, бібліотека читання та запису ULog на C++.
- FlightPlot: Java, log plotter.
- MAVLink: Повідомлення для потокового відображення ULog через MAVLink (зауважте, що додавання даних не підтримується, принаймні не для відсічених повідомлень).
- QGroundControl: C++, потік ULog через MAVLink та мінімальний розбір для геознаходження.
- mavlink-router: C++, потік ULog через MAVLink.
- MAVGAnalysis: Java, ULog streaming через MAVLink та парсер для побудови графіків та аналізу.
- PlotJuggler: C++/Qt додаток для побудови графіків і часових рядів. Підтримує ULog з версії 2.1.3.
- ulogreader: Javascript, ULog читач і парсер виводить журнал у форматі об'єкта JSON.
- Foxglove Studio: інтегрований інструмент візуалізації та діагностики для робототехніки (Парсер ULog на Typescript: https://github.com/foxglove/ulog).
Історія версій формату файлу
Зміни у версії 2
- Додавання Повідомлення з багатою інформацією та Прапорці Повідомлення та можливість додавання даних до журналу.
- Це використовується для додавання даних про збій до існуючого журналу.
- Якщо дані додаються до журналу, який обрізаний посередині повідомлення, їх не можна розбирати з парсерами версії 1.
- Крім того, передня та задня сумісність забезпечується, якщо парсери ігнорують невідомі повідомлення.