@foxglove/ulog
Version:
PX4 ULog file reader
361 lines (320 loc) • 14.2 kB
text/typescript
import path from "path";
import { ULog } from "./ULog";
import { MessageType } from "./enums";
import { MessageAddLogged, MessageDataParsed } from "./messages";
import { FileReader } from "./node/FileReader";
jest.setTimeout(1000 * 30);
describe("ULog sample.ulg", () => {
const sampleFixture = path.join(__dirname, "..", "tests", "sample.ulg");
it("open()", async () => {
const reader = new FileReader(sampleFixture);
const ulog = new ULog(reader);
await ulog.open();
const header = ulog.header!;
expect(header.version).toBe(0);
expect(header.timestamp).toBe(112500176n);
expect(header.flagBits).toBeUndefined();
expect(header.information.size).toBe(4);
expect(header.information.get("ver_sw")).toBe("fd483321a5cf50ead91164356d15aa474643aa73");
expect(header.information.get("ver_hw")).toBe("AUAV_X21");
expect(header.information.get("sys_name")).toBe("PX4");
expect(header.information.get("time_ref_utc")).toBe(0);
expect(header.parameters.size).toEqual(493);
expect(header.parameters.get("RC12_TRIM")).toEqual({ defaultTypes: 0, value: 1500 });
expect(header.parameters.get("SENS_BARO_QNH")).toEqual({ defaultTypes: 0, value: 1013.25 });
expect(header.definitions.size).toEqual(103);
const esc_status = header.definitions.get("esc_status")!;
expect(esc_status).toBeDefined();
expect(esc_status.name).toBe("esc_status");
expect(esc_status.fields).toHaveLength(6);
const timestamp = esc_status.fields[0]!;
expect(timestamp.name).toBe("timestamp");
expect(timestamp.isComplex).toBe(false);
expect(timestamp.type).toBe("uint64_t");
const counter = esc_status.fields[1]!;
expect(counter.name).toBe("counter");
expect(counter.isComplex).toBe(false);
expect(counter.type).toBe("uint16_t");
const esc_count = esc_status.fields[2]!;
expect(esc_count.name).toBe("esc_count");
expect(esc_count.isComplex).toBe(false);
expect(esc_count.type).toBe("uint8_t");
const esc_connectiontype = esc_status.fields[3]!;
expect(esc_connectiontype.name).toBe("esc_connectiontype");
expect(esc_connectiontype.isComplex).toBe(false);
expect(esc_connectiontype.type).toBe("uint8_t");
const padding0 = esc_status.fields[4]!;
expect(padding0.name).toBe("_padding0");
expect(padding0.isComplex).toBe(false);
expect(padding0.type).toBe("uint8_t");
expect(padding0.arrayLength).toBe(4);
const esc = esc_status.fields[5]!;
expect(esc.name).toBe("esc");
expect(esc.isComplex).toBe(true);
expect(esc.type).toBe("esc_report");
expect(esc.arrayLength).toBe(8);
void reader.close();
});
it("readMessages()", async () => {
const reader = new FileReader(sampleFixture);
const ulog = new ULog(reader);
await ulog.open();
let prevTimestamp = 0n;
for await (const msg of ulog.readMessages()) {
if (msg.type === MessageType.Data) {
// eslint-disable-next-line jest/no-conditional-expect
expect(msg.value.timestamp).toBeGreaterThanOrEqual(prevTimestamp);
prevTimestamp = msg.value.timestamp;
} else if (msg.type === MessageType.Log || msg.type === MessageType.LogTagged) {
// eslint-disable-next-line jest/no-conditional-expect
expect(msg.timestamp).toBeGreaterThanOrEqual(prevTimestamp);
prevTimestamp = msg.timestamp;
}
}
void reader.close();
});
it("readMessages() reverse", async () => {
const reader = new FileReader(sampleFixture);
const ulog = new ULog(reader);
await ulog.open();
let prevTimestamp = BigInt(Number.MAX_SAFE_INTEGER);
for await (const msg of ulog.readMessages({ reverse: true })) {
if (msg.type === MessageType.Data) {
// eslint-disable-next-line jest/no-conditional-expect
expect(msg.value.timestamp).toBeLessThanOrEqual(prevTimestamp);
prevTimestamp = msg.value.timestamp;
} else if (msg.type === MessageType.Log || msg.type === MessageType.LogTagged) {
// eslint-disable-next-line jest/no-conditional-expect
expect(msg.timestamp).toBeLessThanOrEqual(prevTimestamp);
prevTimestamp = msg.timestamp;
}
}
void reader.close();
});
it("MessageAddLogged", async () => {
const reader = new FileReader(sampleFixture);
const ulog = new ULog(reader);
await ulog.open();
let subscribe: MessageAddLogged | undefined;
for await (const msg of ulog.readMessages()) {
if (msg.type === MessageType.AddLogged) {
subscribe = msg;
break;
}
}
expect(subscribe).toBeDefined();
expect(subscribe!.type).toBe(MessageType.AddLogged);
expect(subscribe!.size).toBe(19);
expect(subscribe!.multiId).toBe(0);
expect(subscribe!.msgId).toBe(0);
expect(subscribe!.messageName).toBe("vehicle_attitude");
void reader.close();
});
it("MessageDataParsed", async () => {
const reader = new FileReader(sampleFixture);
const ulog = new ULog(reader);
await ulog.open();
let data: MessageDataParsed | undefined;
for await (const msg of ulog.readMessages()) {
if (msg.type === MessageType.Data && msg.msgId === 0) {
data = msg;
break;
}
}
expect(data).toBeDefined();
expect(data!.type).toBe(MessageType.Data);
expect(data!.size).toBe(38);
expect(data!.msgId).toBe(0);
expect(data!.data.byteLength).toBe(36);
expect(data!.data[0]).toBe(99);
expect(data!.data[data!.data.byteLength - 1]).toBe(190);
expect(data!.value).toEqual({
timestamp: 112574307n,
rollspeed: -0.0004259266424924135,
pitchspeed: 0.000473720021545887,
yawspeed: 0.0008371851872652769,
q: [0.9545906186103821, 0.041478633880615234, 0.048174899071455, -0.2910595238208771],
});
void reader.close();
});
});
describe("ULog sample_appended.ulg", () => {
const sampleFixture = path.join(__dirname, "..", "tests", "sample_appended.ulg");
it("open()", async () => {
const reader = new FileReader(sampleFixture);
const ulog = new ULog(reader);
await ulog.open();
const header = ulog.header!;
expect(header.version).toBe(1);
expect(header.timestamp).toBe(5115156n);
expect(header.flagBits).toEqual({
appendedOffsets: [4530735n, 0n, 0n],
compatibleFlags: [0, 0, 0, 0, 0, 0, 0, 0],
incompatibleFlags: [1, 0, 0, 0, 0, 0, 0, 0],
size: 40,
type: 66,
});
expect(header.information.size).toBe(47);
expect(Object.fromEntries(header.information)).toEqual({
ver_sw: "f54a6c2999e1e2fcbf56dd89de06b615b4186a6e",
ver_sw_release: 17170432,
ver_hw: "PX4FMU_V4",
sys_name: "PX4",
sys_os_name: "NuttX",
ver_sw_branch: "ulog_crash_dump",
sys_os_ver: "8b81cf5c7ece0c228eaaea3e9d8e667fc4d21a06",
sys_os_ver_release: 192,
sys_toolchain: "GNU GCC",
sys_toolchain_ver: "5.4.1 20160919 (release) [ARM/embedded-5-branch revision 240496]",
sys_mcu: "STM32F42x, rev. 3",
sys_uuid: "004F00413335510D30383336",
time_ref_utc: 0,
"perf_counter_preflight-00":
"navigator: 3 events, 80us elapsed, 26us avg, min 25us max 28us 1.528us rms",
"perf_counter_preflight-01":
"mc_att_control: 766 events, 38087us elapsed, 49us avg, min 23us max 395us 32.174us rms",
"perf_counter_preflight-02":
"logger_sd_fsync: 0 events, 0us elapsed, 0us avg, min 0us max 0us 0.000us rms",
"perf_counter_preflight-03":
"logger_sd_write: 3 events, 72442us elapsed, 24147us avg, min 10us max 36356us 20904.014us rms",
"perf_counter_preflight-04": "mavlink_txe: 226 events",
"perf_counter_preflight-05":
"mavlink_el: 1016 events, 163693us elapsed, 161us avg, min 84us max 2478us 191.598us rms",
"perf_counter_preflight-06": "mavlink_txe: 0 events",
"perf_counter_preflight-07":
"mavlink_el: 286 events, 33394us elapsed, 116us avg, min 46us max 1851us 166.045us rms",
"perf_counter_preflight-08": "mavlink_txe: 0 events",
"perf_counter_preflight-09":
"mavlink_el: 318 events, 48587us elapsed, 152us avg, min 66us max 2327us 259.293us rms",
"perf_counter_preflight-10": "mavlink_txe: 0 events",
"perf_counter_preflight-11":
"mavlink_el: 1030 events, 214017us elapsed, 207us avg, min 79us max 4163us 310.871us rms",
"perf_counter_preflight-12":
"ctl_lat: 321 events, 13187us elapsed, 41us avg, min 38us max 111us 11.183us rms",
"perf_counter_preflight-13":
"stack_check: 7 events, 69us elapsed, 9us avg, min 2us max 16us 4.488us rms",
"perf_counter_preflight-14":
"sensors: 826 events, 93853us elapsed, 113us avg, min 65us max 5118us 179.764us rms",
"perf_counter_preflight-15":
"ctrl_latency: 321 events, 40037us elapsed, 124us avg, min 103us max 3022us 166.815us rms",
"perf_counter_preflight-16": "mpu9250_dupe: 898 events",
"perf_counter_preflight-17": "mpu9250_reset: 0 events",
"perf_counter_preflight-18": "mpu9250_good_trans: 3443 events",
"perf_counter_preflight-19": "mpu9250_bad_reg: 0 events",
"perf_counter_preflight-20": "mpu9250_bad_trans: 0 events",
"perf_counter_preflight-21":
"mpu9250_read: 4342 events, 269357us elapsed, 62us avg, min 41us max 91us 13.632us rms",
"perf_counter_preflight-22": "mpu9250_gyro_read: 0 events",
"perf_counter_preflight-23": "mpu9250_acc_read: 2 events",
"perf_counter_preflight-24": "mpu9250_mag_duplicates: 3066 events",
"perf_counter_preflight-25": "mpu9250_mag_overflows: 0 events",
"perf_counter_preflight-26": "mpu9250_mag_overruns: 51 events",
"perf_counter_preflight-27": "mpu9250_mag_errors: 0 events",
"perf_counter_preflight-28": "mpu9250_mag_reads: 0 events",
"perf_counter_preflight-29":
"adc_samples: 3024 events, 8046us elapsed, 2us avg, min 2us max 3us 0.474us rms",
"perf_counter_preflight-30": "ms5611_com_err: 0 events",
"perf_counter_preflight-31":
"ms5611_measure: 321 events, 5603us elapsed, 17us avg, min 8us max 679us 54.355us rms",
"perf_counter_preflight-32":
"ms5611_read: 320 events, 23168us elapsed, 72us avg, min 13us max 543us 49.197us rms",
"perf_counter_preflight-33": "dma_alloc: 4 events",
});
expect(header.parameters.size).toEqual(713);
expect(header.parameters.get("RC12_TRIM")).toEqual({ defaultTypes: 0, value: 1500 });
expect(header.parameters.get("SENS_BARO_QNH")).toEqual({ defaultTypes: 0, value: 1013.25 });
expect(header.definitions.size).toEqual(110);
const esc_status = header.definitions.get("esc_status")!;
expect(esc_status).toBeDefined();
expect(esc_status.name).toBe("esc_status");
expect(esc_status.fields).toHaveLength(6);
const timestamp = esc_status.fields[0]!;
expect(timestamp.name).toBe("timestamp");
expect(timestamp.isComplex).toBe(false);
expect(timestamp.type).toBe("uint64_t");
const counter = esc_status.fields[1]!;
expect(counter.name).toBe("counter");
expect(counter.isComplex).toBe(false);
expect(counter.type).toBe("uint16_t");
const esc_count = esc_status.fields[2]!;
expect(esc_count.name).toBe("esc_count");
expect(esc_count.isComplex).toBe(false);
expect(esc_count.type).toBe("uint8_t");
const esc_connectiontype = esc_status.fields[3]!;
expect(esc_connectiontype.name).toBe("esc_connectiontype");
expect(esc_connectiontype.isComplex).toBe(false);
expect(esc_connectiontype.type).toBe("uint8_t");
const padding0 = esc_status.fields[4]!;
expect(padding0.name).toBe("_padding0");
expect(padding0.isComplex).toBe(false);
expect(padding0.type).toBe("uint8_t");
expect(padding0.arrayLength).toBe(4);
const esc = esc_status.fields[5]!;
expect(esc.name).toBe("esc");
expect(esc.isComplex).toBe(true);
expect(esc.type).toBe("esc_report");
expect(esc.arrayLength).toBe(8);
void reader.close();
});
});
describe("log_6_2021-7-20-11-41-56.ulg", () => {
const sampleFixture = path.join(__dirname, "..", "tests", "log_6_2021-7-20-11-41-56.ulg");
it("should parse", async () => {
const reader = new FileReader(sampleFixture);
const ulog = new ULog(reader);
await ulog.open();
expect(ulog.messageCount()).toBe(1023911);
await reader.close();
});
});
describe("truncated.ulg", () => {
const sampleFixture = path.join(__dirname, "..", "tests", "truncated.ulg");
it("should parse", async () => {
const reader = new FileReader(sampleFixture);
const ulog = new ULog(reader);
await ulog.open();
expect(ulog.messageCount()).toBe(187433);
await reader.close();
});
});
describe("README.md", () => {
const sampleFixture = path.join(__dirname, "..", "tests", "sample.ulg");
it("example code works", async () => {
const reader = new FileReader(sampleFixture);
const ulog = new ULog(reader);
await ulog.open(); // required before any other operations
expect(ulog.messageCount()).toBe(64599); // ex: 64599
expect(ulog.timeRange()).toEqual([0n, 181493506n]); // ex: [ 0n, 181493506n ]
const msgIdCounts = new Map<number, number>();
for await (const msg of ulog.readMessages()) {
if (msg.type === MessageType.Data) {
// NOTE: `msg.value` holds the deserialized message
msgIdCounts.set(msg.msgId, (msgIdCounts.get(msg.msgId) ?? 0) + 1);
}
}
const msgCounts = Object.fromEntries(
Array.from(msgIdCounts.entries()).map(([id, count]) => [
ulog.subscriptions.get(id)!.name,
count,
]),
);
expect(msgCounts).toEqual({
vehicle_attitude: 6461,
actuator_outputs: 1311,
telemetry_status: 70,
vehicle_status: 294,
commander_state: 678,
vehicle_attitude_setpoint: 3272,
vehicle_rates_setpoint: 6448,
actuator_controls_0: 3269,
vehicle_local_position: 678,
ekf2_innovations: 3271,
sensor_preflight: 17072,
sensor_combined: 17070,
control_state: 3268,
estimator_status: 1311,
cpuload: 69,
});
await reader.close();
});
});