#logging #parser #format

mavlink_log

Provides utilities for interacting with a simple mavlink log

1 unstable release

Uses new Rust 2024

new 0.1.0 Mar 26, 2025

#496 in Parser implementations

MIT/Apache

83KB
1K SLoC

Mavlink Log

Crates.io Version Build + Test Build + Test + Deploy

Installation

cargo add mavlink_log

File Formats

.mav log - requires the MavLog feature .tlog log - requires the Tlog feature

Known Issues

read_versioned_msg

NOTE This is only relevant when parsing a .mav file that does not have the mavlink_only format flag set to true or no_timestamp is false.

The read_versioned_msg function from rust_mavlink is not built for mixed data streams which can cause problems on the mixed stream log file. Specifically it will search through the data for the mavlink packet start key. This only a problem for our parser if the file data got corrupted or an unexpected message defintion was used. Meaning it tried to read the current mavlink packet and failed. This method will immediately search for the next valid MAVLink message. It should in theory recover on the next mavlink message but until then, it is looping over potentially valid non mavlink file records or there could be false positives. This can result in misaligned timestamps as well.

We would rather it exit on failure so we can take the data as raw and move on to the next entry.

Examples

WARNING The following examples are were pieced together from the automated testing. They are meant to illustrate the usage concept and thus could err if attempting to run on your system.

Mav File Logging

features: mavlog, logger

use mavlink_log::mav_logger::MavLogger;
use mavlink_log::mavlog::header::FormatFlags;
use mavlink_log::mavlog::logger::RotatingMavLogger;
use mavlink::common::MavMessage;
use mavlink::{MavHeader, MavlinkVersion, MavFrame};

fn main() {
    // initialize a mav logger with no optimizations
    // this means all entries will be timestamped and raw, text, mavlink are all supported
    let mut logger: RotatingMavLogger =
            RotatingMavLogger::new("/tmp/ground_station.mav", 1024, 3, None, None)
                .expect("Failed to create logger");

    // example mav frame. this would most likely be received from a mavlink connection
    let mav_frame = MavFrame {
        header: MavHeader::default(),
        msg: MavMessage::HEARTBEAT(Default::default()),
        protocol_version: MavlinkVersion::V2,
    };

    // write mavlink
    let result = logger.write_mavlink(mav_frame);
    assert!(result.is_ok());

    // write text
    let result = logger.write_text("Test log entry");
    assert!(result.is_ok());

    // write raw
    let result = logger.write_raw(&[1, 2, 3, 4, 5]);
    assert!(result.is_ok());

    // initialize a mav logger with optimizations
    let flags = FormatFlags {
        mavlink_only: true,
        no_timestamp: true,
    };
    let mut logger: RotatingMavLogger =
            RotatingMavLogger::new("/tmp/ground_station.mav", 1024, 3, Some(flags), None)
                .expect("Failed to create logger");

    // example mav frame. this would most likely be received from a mavlink connection
    let mav_frame = MavFrame {
        header: MavHeader::default(),
        msg: MavMessage::HEARTBEAT(Default::default()),
        protocol_version: MavlinkVersion::V2,
    };

    // write mavlink
    let result = logger.write_mavlink(mav_frame);
    assert!(result.is_ok());

    // write text
    let result = logger.write_text("Test log entry");
    assert!(!result.is_ok());

    // write raw
    let result = logger.write_raw(&[1, 2, 3, 4, 5]);
    assert!(!result.is_ok());
}

Mav File Parsing

features: mavlog, parser

use std::io::ErrorKind::UnexpectedEof;
use mavlink::common::MavMessage;
use mavlink::error::MessageReadError;
use mavlink_log::mavlog::parser::MavLogParser;
use mavlink_log::mav_parser::{LogEntry, MavParser};

fn main() {
    let mut parser = MavLogParser::<MavMessage>::new("/tmp/ground_station.mav");
    let mut count: u64 = 0;
    loop {
        // retrieve next entry in a loop
        let entry: Result<LogEntry<MavMessage>, MessageReadError> = parser.parse_next_entry();
        match entry {
            Ok(entry_data) => {
                count += 1;
                // optionally get timestamp
                let timestamp = entry_data.timestamp;
                // optionally get the mavlink header
                let mav_header = entry_data.mav_header;
                // optionally get the mavlink message
                let mav_message = entry_data.mav_message;
                // optionally get text
                let text = entry_data.text;
                // optionally get raw
                let raw = entry_data.raw;
            }
            // exit loop on end of file
            Err(MessageReadError::Io(e)) => {
                if e.kind() == UnexpectedEof {
                    break
                }
            },
            Err(_) => {},
        }
    }
}

Tlog File Logging

features: tlog, logger

use mavlink_log::mav_logger::MavLogger;
use mavlink_log::tlog::logger::RotatingTlog;
use mavlink::common::MavMessage;
use mavlink::{MavHeader, MavlinkVersion, MavFrame};

fn main(){
    // example mav frame. this would most likely be received from a mavlink connection
    let mav_frame = MavFrame {
        header: MavHeader::default(),
        msg: MavMessage::HEARTBEAT(Default::default()),
        protocol_version: MavlinkVersion::V2,
    };

    // initialize rotating tlog logger
    let mut logger = RotatingTlog::new("/tmp/ground_station.tlog", 1024, 3).unwrap();
    // write a mavlink frame
    let result = logger.write_mavlink(mav_frame);
    assert!(result.is_ok());
}

Tlog File Parsing

features: tlog, parser

use std::io::ErrorKind::UnexpectedEof;
use mavlink::common::MavMessage;
use mavlink::error::MessageReadError;
use mavlink_log::mav_parser::{LogEntry, MavParser};
use mavlink_log::tlog::parser::TlogParser;

fn main() {
    // initialize the tlog parser
    let mut tlog = TlogParser::<MavMessage>::new("/tmp/ground_station.tlog");
    let mut count: u64 = 0;
    loop {
        // retrieve next entry in a loop
        let entry: Result<LogEntry<MavMessage>, MessageReadError> = tlog.parse_next_entry();
        match entry {
            Ok(entry_data) => {
                count += 1;
                // optionally get the mavlink header
                let mav_header = entry_data.mav_header;
                // optionally get the mavlink message
                let mav_message = entry_data.mav_message;
            }
            // exit loop on end of file
            Err(MessageReadError::Io(e)) => {
                if e.kind() == UnexpectedEof {
                    break
                }
            },
            Err(_) => {},
        }
    }
}

License

Licensed under either of the following:

Contribution

Public contribution is welcome and encouraged. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Documentation

All documentation that is associated with a specific block of code, should be documented inline per the rust documentation expectations. This documentation should be available in the auto geenrated rust docs. Documentation of more complex technical concepts surrounding this crate as a whole should be put in markdown files in the docs folder. Everything else such as crate requirements, installation instructions, etc should be documented in this README.md.

Code Standards

  • all code should be formatted per cargo's default fmt command
  • code should target 80% automated code coverage

Release Process

Releases are managed through both git tags and branches. Branches are used for convenience and tags actually trigger the relevant release actions. Whenever there is a new major or minor release a branch must be created at the relevant hash in the format v<major>.<minor> (ie v1.33). Branches with such a format are protected by a ruleset and can only be modified by admins. All release tags must point to hashes on said branch. There is also a ruleset protecting all git tags matching the semantic versioning format v*.*.* so that only admins can add such tags.

Major or Minor Release

In summary, you must be an admin and complete the following steps:

  • pick a hash
  • confirm all automated tests have passed
  • create a branch at the relevant hash in the format v<major>.<minor> (ie v1.33).
  • if necessary perform any last minuted changes
  • create a git tag pointing to the tip of that branch in the format v<major>.<minor>.0 (ie v1.33.0).

The git tag will kick off an automated process that deploys the crate to crates.io after validating crate version matches the tag version and all automated tests pass.

Future Work

  • URGENT - create a new read_versioned_msg for the more complex log file types
  • return error rather than panic even on critical errors so a parent can potentially handle and take action
  • extract timestamps from tlog while parsing
  • support async
  • allow optional buffering during writing. maybe use features to support this.
  • use rust features to select for certain optimizations such as no timestamps or mavlink only
  • support no copy logging if possible and necessary
  • add support for logging in embedded systems

Dependencies

~3MB
~55K SLoC