1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#![no_std]
#![warn(clippy::std_instead_of_alloc, clippy::std_instead_of_core)]
#![warn(unreachable_pub, clippy::missing_panics_doc)]

//! Ergonomic parser for Betaflight blackbox logs.
//!
//! For details about the format of blackbox logs, see the *Blackbox Internals*
//! development documentation from [INAV][inav-doc], [Betaflight][bf-doc]
//!
//! # Examples
//!
//! The simplest way to extract a few fields of interest:
//!
//! ```
//! use blackbox_log::frame::FieldDef;
//! use blackbox_log::prelude::*;
//! use blackbox_log::Filter;
//!
//! let filters = blackbox_log::FilterSet {
//!     // This restricts the included fields to `rcCommand[0]` through `rcCommand[3]`
//!     main: Filter::OnlyFields(["rcCommand"].into()),
//!     // ... only `flightModeFlags` for slow frames
//!     slow: Filter::OnlyFields(["flightModeFlags"].into()),
//!     // ... and no filter for gps frames -- include all fields
//!     gps: Filter::Unfiltered,
//! };
//!
//! let file = b"...";
//! for headers in blackbox_log::File::new(file).iter() {
//!     let headers = headers.expect("valid log headers");
//!
//!     let mut parser = headers.data_parser_with_filters(&filters);
//!     while let Some(event) = parser.next() {
//!         match event {
//!             ParserEvent::Main(main) => {
//!                 for (value, FieldDef { name, .. }) in
//!                     main.iter().zip(headers.main_frame_def().iter())
//!                 {
//!                     println!("{name}: {value:?}");
//!                 }
//!             }
//!             ParserEvent::Slow(slow) => {
//!                 for (value, FieldDef { name, .. }) in
//!                     slow.iter().zip(headers.slow_frame_def().iter())
//!                 {
//!                     println!("{name}: {value:?}");
//!                 }
//!             }
//!             ParserEvent::Event(_) | ParserEvent::Gps(_) => {}
//!         }
//!     }
//! }
//! ```
//!
//! Get only the GPS data without parsing logs that cannot contain GPS frames:
//!
//! ```
//! use blackbox_log::frame::FieldDef;
//! use blackbox_log::prelude::*;
//!
//! let file = b"...";
//! for headers in blackbox_log::File::new(file).iter() {
//!     let headers = headers.expect("valid log headers");
//!
//!     if let Some(gps_def) = &headers.gps_frame_def() {
//!         let mut parser = headers.data_parser();
//!
//!         while let Some(event) = parser.next() {
//!             if let ParserEvent::Gps(gps) = event {
//!                 for (value, FieldDef { name, .. }) in gps.iter().zip(gps_def.iter()) {
//!                     println!("{name}: {value:?}");
//!                 }
//!             }
//!         }
//!     }
//! }
//! ```
//!
//! # Features
//!
//! - `std`: **Enabled** by default. Currently, this only implements
//!   [`std::error::Error`] for [`headers::ParseError`].
//!
//! [bf-doc]: https://betaflight.com/docs/development/Blackbox-Internals
//! [inav-doc]: https://github.com/iNavFlight/inav/blob/master/docs/development/Blackbox%20Internals.md

extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

#[macro_use]
mod utils;

pub mod data;
pub mod event;
mod file;
mod filter;
pub mod frame;
pub mod headers;
mod parser;
mod predictor;
pub mod prelude;
mod reader;
pub mod units;

use core::ops::Range;

pub use self::data::{DataParser, ParserEvent};
pub use self::event::Event;
pub use self::file::File;
pub use self::filter::{FieldFilter, Filter, FilterSet};
pub use self::frame::{Unit, Value};
use self::headers::FirmwareVersion;
pub use self::headers::Headers;
use self::reader::Reader;

/// The first line of any blackbox log.
const MARKER: &[u8] = b"H Product:Blackbox flight data recorder by Nicholas Sherlock\n";

const BETAFLIGHT_SUPPORT: Range<FirmwareVersion> =
    FirmwareVersion::new(4, 2, 0)..FirmwareVersion::new(4, 6, 0);
const INAV_SUPPORT: Range<FirmwareVersion> =
    FirmwareVersion::new(5, 0, 0)..FirmwareVersion::new(8, 0, 0);