A robust Rust implementation of a parser for the ULog file format, commonly used in PX4 flight stack for logging system data. This parser provides a safe, efficient way to read and process ULog files with strong type checking and error handling.
- Complete implementation of the ULog file format specification
- Support for all ULog message types including:
- Data messages
- Format messages
- Parameter messages
- Logged messages (both plain and tagged)
- Multi messages
- Subscription messages
- Dropout tracking
- Safe handling of nested message types
- Comprehensive error handling
- Zero-copy parsing where possible
- Support for appended data sections
Add this to your Cargo.toml
:
[dependencies]
ulog_rs = "0.1.0"
use std::fs::File;
use ulog_rs::ULogParser;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Open a ULog file
let file = File::open("sample.ulg")?;
// Create parser instance
let parser = ULogParser::parse_reader(file)?;
// Access header information
println!("ULog Header:");
println!(" Version: {}", parser.header().version);
println!(" Timestamp: {} μs", parser.header().timestamp);
println!(" Final Timestamp: {} μs", parser.last_timestamp());
// Access logged messages
for message in parser.logged_messages() {
println!("[{}] {}", message.timestamp, message.message);
}
Ok(())
}
The project is organized into several modules, each handling specific aspects of ULog parsing:
lib.rs
: Core parser implementation and type definitionsdata_message.rs
: Handles data message parsingdropout_message.rs
: Manages dropout tracking and statisticsformat_message.rs
: Processes message format definitionsinfo_message.rs
: Handles info message parsinglogged_message.rs
: Manages regular logged messagesmulti_message.rs
: Handles multi-part messagesparameter_message.rs
: Processes parameter messagessubscription_message.rs
: Manages message subscriptionstagged_logged_message.rs
: Handles tagged log messages
The ULogParser
struct provides the main interface for parsing ULog files:
pub struct ULogParser<R: Read> {
// ... internal fields ...
}
impl<R: Read> ULogParser<R> {
// Create a new parser and parse the entire file
pub fn parse_reader(reader: R) -> Result<ULogParser<R>, ULogError>;
// Access various components
pub fn header(&self) -> &ULogHeader;
pub fn formats(&self) -> &HashMap<String, FormatMessage>;
pub fn subscriptions(&self) -> &HashMap<u16, SubscriptionMessage>;
pub fn logged_messages(&self) -> &[LoggedMessage];
pub fn info_messages(&self) -> &HashMap<String, InfoMessage>;
pub fn initial_params(&self) -> &HashMap<String, ParameterMessage>;
pub fn multi_messages(&self) -> &HashMap<String, Vec<MultiMessage>>;
pub fn default_params(&self) -> &HashMap<String, DefaultParameterMessage>;
pub fn dropout_details(&self) -> &DropoutStats;
pub fn last_timestamp(&self) -> u64;
}
The parser uses a custom error type ULogError
for comprehensive error handling:
pub enum ULogError {
Io(std::io::Error),
InvalidMagic,
UnsupportedVersion(u8),
InvalidMessageType(u8),
InvalidString,
InvalidTypeName(String),
ParseError(String),
IncompatibleFlags(Vec<u8>),
}
Data messages contain the actual logged data and are accessed through subscriptions:
// Subscribe to a message type
let subscription = parser.subscriptions().get(&msg_id)?;
// Access the data
for data_point in &subscription.data {
// Process data...
}
The parser maintains both initial parameters and parameter changes:
// Access initial parameters
for (key, param) in parser.initial_params() {
println!("{}: {:?}", key, param.value);
}
// Access default parameters
for (key, param) in parser.default_params() {
println!("{}: {:?}", key, param.value);
}
Both regular and tagged logged messages are supported:
// Regular logged messages
for msg in parser.logged_messages() {
println!("[{}] {}", msg.timestamp, msg.message);
}
// Tagged logged messages
for (tag, messages) in parser.logged_messages_tagged() {
for msg in messages {
println!("[{}][{}] {}", tag, msg.timestamp, msg.message);
}
}
Contributions are welcome! Please feel free to submit a Pull Request.
[Insert your chosen license here]