Skip to main content

ardupilot_binlog/
entry.rs

1use std::sync::Arc;
2
3use crate::value::FieldValue;
4
5/// A single parsed message from a DataFlash BIN log.
6///
7/// ```
8/// use ardupilot_binlog::Reader;
9/// use std::io::Cursor;
10///
11/// let reader = Reader::new(Cursor::new(Vec::new()));
12/// for result in reader {
13///     let entry = result.unwrap();
14///     println!("{} ({} fields)", entry.name, entry.len());
15///     for (label, value) in entry.fields() {
16///         println!("  {label} = {value}");
17///     }
18/// }
19/// ```
20#[derive(Debug, Clone)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(feature = "serde", serde(into = "EntryRepr", from = "EntryRepr"))]
23pub struct Entry {
24    /// Message type name (e.g. "ATT", "GPS", "BARO")
25    pub name: String,
26
27    /// Message type ID (0–255)
28    pub msg_type: u8,
29
30    /// Timestamp in microseconds since boot (from the first Q-typed field).
31    /// None for FMT messages and messages without a Q-typed first field.
32    pub timestamp_usec: Option<u64>,
33
34    pub(crate) labels: Arc<[String]>,
35    pub(crate) values: Vec<FieldValue>,
36}
37
38impl Entry {
39    /// Look up a field by name.
40    pub fn get(&self, field: &str) -> Option<&FieldValue> {
41        let idx = self.labels.iter().position(|l| l == field)?;
42        self.values.get(idx)
43    }
44
45    /// Look up a field and convert to f64 (convenience for charting).
46    pub fn get_f64(&self, field: &str) -> Option<f64> {
47        self.get(field).and_then(|v| v.as_f64())
48    }
49
50    /// Look up a field and convert to i64.
51    pub fn get_i64(&self, field: &str) -> Option<i64> {
52        self.get(field).and_then(|v| v.as_i64())
53    }
54
55    /// Look up a field and convert to u64.
56    pub fn get_u64(&self, field: &str) -> Option<u64> {
57        self.get(field).and_then(|v| v.as_u64())
58    }
59
60    /// Look up a field and convert to string reference.
61    pub fn get_str(&self, field: &str) -> Option<&str> {
62        self.get(field).and_then(|v| v.as_str())
63    }
64
65    /// Iterate over (label, value) pairs in definition order.
66    pub fn fields(&self) -> impl Iterator<Item = (&str, &FieldValue)> {
67        self.labels
68            .iter()
69            .map(|l| l.as_str())
70            .zip(self.values.iter())
71    }
72
73    /// Return field labels in definition order.
74    #[must_use]
75    pub fn labels(&self) -> &[String] {
76        &self.labels
77    }
78
79    /// Return field values in definition order.
80    #[must_use]
81    pub fn values(&self) -> &[FieldValue] {
82        &self.values
83    }
84
85    /// Return the number of fields.
86    #[must_use]
87    pub fn len(&self) -> usize {
88        self.values.len()
89    }
90
91    /// Return true if the entry has no fields.
92    #[must_use]
93    pub fn is_empty(&self) -> bool {
94        self.values.is_empty()
95    }
96}
97
98// Serde helper: serialize Entry as a flat struct with fields as Vec<(String, FieldValue)>
99#[cfg(feature = "serde")]
100#[derive(serde::Serialize, serde::Deserialize)]
101struct EntryRepr {
102    name: String,
103    msg_type: u8,
104    timestamp_usec: Option<u64>,
105    fields: Vec<(String, FieldValue)>,
106}
107
108#[cfg(feature = "serde")]
109impl From<Entry> for EntryRepr {
110    fn from(e: Entry) -> Self {
111        let fields = e.labels.iter().cloned().zip(e.values).collect();
112        EntryRepr {
113            name: e.name,
114            msg_type: e.msg_type,
115            timestamp_usec: e.timestamp_usec,
116            fields,
117        }
118    }
119}
120
121#[cfg(feature = "serde")]
122impl From<EntryRepr> for Entry {
123    fn from(r: EntryRepr) -> Self {
124        let (labels, values): (Vec<String>, Vec<FieldValue>) = r.fields.into_iter().unzip();
125        Entry {
126            name: r.name,
127            msg_type: r.msg_type,
128            timestamp_usec: r.timestamp_usec,
129            labels: labels.into(),
130            values,
131        }
132    }
133}