uf_ulog 0.1.1

Allocator-free no_std ULog serializer for embedded systems
Documentation

uf-ulog

CI codecov crates.io docs.rs

uf_ulog is an allocator-free ULog serializer designed for embedded targets.

Features

  • no_std, allocator-free core.
  • MCU and transport agnostic (you provide writer/IO).
  • Supports both derive-based and manual trait implementations.
  • Split into two crates:
    • uf_ulog (core serializer)
    • uf_ulog_macro (derive macros)

Supported messages (current scope)

The initial implementation focuses on:

  • Data messages (topic payloads) (D type)
  • String log messages (plain and tagged) (L and Ca types)
  • Parameter messages, (P type)
  • Required format messages needed to describe logged data/strings (B, F, )

Cargo feature flags

  • derive (default): enables #[derive(ULogData)] and #[derive(ULogRegistry)].
  • async: enables async exporter support via embedded-io-async.

Installation

Add uf-ulog to your Cargo.toml:

[dependencies]
uf-ulog = "*" # replace * by the latest version of the crate.

Or use the command line:

cargo add uf-ulog

Examples

Based on examples/minimal.rs:

use std::convert::Infallible;

use uf_ulog::LogLevel;
use uf_ulog::ULogCoreExporter;
use uf_ulog::ULogData;
use uf_ulog::ULogProducer;
use uf_ulog::ULogRegistry;

#[derive(ULogData, Debug)]
struct Acc {
    timestamp: u64,
    x: f32,
    y: f32,
    z: f32,
}

#[derive(ULogData, Debug)]
struct Gyro {
    timestamp: u64,
    x: f32,
    y: f32,
    z: f32,
}

#[derive(ULogRegistry)]
pub enum UlogDataMessages {
    Gyro,
    Acc,
}

#[derive(Default)]
struct PrintWriter;

impl embedded_io::ErrorType for PrintWriter {
    type Error = Infallible;
}

impl embedded_io::Write for PrintWriter {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
        println!("{:?}", buf);
        Ok(buf.len())
    }

    fn flush(&mut self) -> Result<(), Self::Error> {
        Ok(())
    }
}

fn main() {
    let timestamp = 1772079727637;
    let producer = ULogProducer::<UlogDataMessages>::new();
    let mut exporter = ULogCoreExporter::<_, UlogDataMessages>::new(PrintWriter)
        .start(timestamp)
        .unwrap();

    let g = Gyro {
        timestamp,
        x: 1.0,
        y: 2.0,
        z: 3.0,
    };
    let a = Acc {
        timestamp,
        x: 1.0,
        y: 2.0,
        z: 9.0,
    };

    let record_data_gyro = producer.data::<Gyro>(&g).unwrap();
    let record_data_acc_instance = producer.data_instance::<Acc>(&a, 1).unwrap();
    let record_log_info = producer.log(LogLevel::Info, 43, "info log");
    let record_log_info_tagged = producer.log_tagged(LogLevel::Info, 1, 43, "info log");
    // Usually records produced by one task in non blocking fashion
    // to make sure that control loops are fast as possible. Records
    // then sent over channel to IO task, that may block or
    // take longer to offload data over compatible bus/net.
    exporter.accept(record_data_gyro).unwrap();
    exporter.accept(record_data_acc_instance).unwrap();
    exporter.accept(record_log_info).unwrap();
    exporter.accept(record_log_info_tagged).unwrap();
}

Run it:

cargo run -p uf_ulog --example minimal

Write ULog to file and parse with pyulog

For an end-to-end demo (write a .ulg file, then parse it with Python), use:

just demo

This runs the example in ../examples/simple-file and then executes ../examples/simple-file/read_ulg.py via uv.

References