px4_ulog/models/
data.rs

1use unpack;
2
3/// Container for a single data row
4#[derive(Debug)]
5pub struct ULogData {
6    data: Vec<u8>,
7    formats: Vec<String>,
8}
9
10/// Data set iterator
11///
12/// # Examples
13/// ```
14/// use std::fs::File;
15/// use px4_ulog::parser::dataset::*;
16/// let filename = format!(
17///     "{}/tests/fixtures/6ba1abc7-b433-4029-b8f5-3b2bb12d3b6c.ulg",
18///     env!("CARGO_MANIFEST_DIR")
19/// );
20///
21/// let mut log_file = File::open(&filename).unwrap();
22///
23/// let starting_gps_position = log_file
24///     .get_dataset("vehicle_gps_position")
25///     .unwrap()
26///     .next()
27///     .unwrap();
28/// assert_eq!(starting_gps_position.iter().count(), 23);
29/// ```
30pub struct ULogDataIter<'a> {
31    data: &'a ULogData,
32    format_index: usize,
33    data_index: usize,
34}
35
36/// Log data item type
37#[derive(Debug, PartialEq)]
38pub enum DataType {
39    UInt64(u64),
40    Int32(i32),
41    Float(f32),
42    UInt8(u8),
43    Bool(bool),
44}
45
46impl ULogData {
47    pub fn new(data: Vec<u8>, formats: Vec<String>) -> Self {
48        Self { data, formats }
49    }
50
51    /// Get the unformatted data for this item
52    pub fn data(&self) -> &Vec<u8> {
53        &self.data
54    }
55
56    /// Get the data formatting for this item
57    pub fn formats(&self) -> &Vec<String> {
58        &self.formats
59    }
60
61    /// Get the list of field names in this item
62    ///
63    /// # Examples
64    /// ```
65    /// use std::fs::File;
66    /// use px4_ulog::parser::dataset::*;
67    /// let filename = format!(
68    ///     "{}/tests/fixtures/6ba1abc7-b433-4029-b8f5-3b2bb12d3b6c.ulg",
69    ///     env!("CARGO_MANIFEST_DIR")
70    /// );
71    ///
72    /// let mut log_file = File::open(&filename).unwrap();
73    /// let items = log_file.get_dataset("vehicle_gps_position")
74    ///     .unwrap()
75    ///     .next()
76    ///     .unwrap()
77    ///     .items();
78    /// assert_eq!(items[0], "timestamp");
79    /// assert_eq!(items[1], "time_utc_usec");
80    /// assert_eq!(items.len(), 23);
81    /// ```
82    pub fn items(&self) -> Vec<String> {
83        self.formats
84            .iter()
85            .filter(|f| f.len() > 0 && !f.contains("_padding") && f.contains(" "))
86            .map(|f| f.split(" ").last().unwrap().to_string())
87            .collect()
88    }
89
90    /// Get an iterator for data fields
91    ///
92    /// The iterator value will be a tuple of (&str, DataType)
93    /// where the first item will be the field name and the second the value.
94    ///
95    /// # Examples
96    /// ```
97    /// use std::fs::File;
98    /// use px4_ulog::parser::dataset::*;
99    /// let filename = format!(
100    ///     "{}/tests/fixtures/6ba1abc7-b433-4029-b8f5-3b2bb12d3b6c.ulg",
101    ///     env!("CARGO_MANIFEST_DIR")
102    /// );
103    ///
104    /// let mut log_file = File::open(&filename).unwrap();
105    /// let mut dataset = log_file.get_dataset("vehicle_gps_position").unwrap();
106    /// let first_data = dataset.next().unwrap();
107    /// assert_eq!(first_data.iter().count(), 23);
108    /// ```
109    pub fn iter(&self) -> ULogDataIter {
110        ULogDataIter {
111            data: self,
112            format_index: 0,
113            data_index: 0,
114        }
115    }
116}
117
118impl<'a> Iterator for ULogDataIter<'a> {
119    type Item = (&'a str, DataType);
120
121    fn next(&mut self) -> Option<Self::Item> {
122        if self.format_index > self.data.formats.len() || self.data_index >= self.data.data.len() {
123            None
124        } else {
125            let format = &self.data.formats[self.format_index];
126            self.format_index += 1;
127            let space = format.find(" ").unwrap();
128            let (dtype, fname) = format.split_at(space);
129            let fname = fname.trim();
130
131            match dtype {
132                "uint64_t" => {
133                    let data_to = self.data_index + 8;
134                    let val = if self.data.data.len() > data_to {
135                        let mut buf: [u8; 8] = Default::default();
136                        buf.copy_from_slice(&self.data.data[self.data_index..data_to]);
137                        self.data_index += 8;
138                        unpack::as_u64_le(&buf)
139                    } else {
140                        0
141                    };
142                    Some((fname, DataType::UInt64(val)))
143                }
144                "int32_t" => {
145                    let data_to = self.data_index + 4;
146                    let val = if self.data.data.len() > data_to {
147                        let mut buf: [u8; 4] = Default::default();
148                        buf.copy_from_slice(&self.data.data[self.data_index..data_to]);
149                        self.data_index += 4;
150                        unpack::as_i32_le(&buf)
151                    } else {
152                        0
153                    };
154                    Some((fname, DataType::Int32(val)))
155                }
156                "float" => {
157                    let data_to = self.data_index + 4;
158                    let val = if self.data.data.len() > data_to {
159                        let mut buf: [u8; 4] = Default::default();
160                        buf.copy_from_slice(&self.data.data[self.data_index..data_to]);
161                        self.data_index += 4;
162                        unpack::as_f32_le(&buf)
163                    } else {
164                        0.0
165                    };
166                    Some((fname, DataType::Float(val)))
167                }
168                "uint8_t" => {
169                    let val = if self.data.data.len() > self.data_index {
170                        let v = self.data.data[self.data_index];
171                        self.data_index += 1;
172                        v
173                    } else {
174                        0
175                    };
176                    Some((fname, DataType::UInt8(val)))
177                }
178                "bool" => {
179                    let val = if self.data.data.len() > self.data_index {
180                        let v = self.data.data[self.data_index] > 0;
181                        self.data_index += 1;
182                        v
183                    } else {
184                        false
185                    };
186                    Some((fname, DataType::Bool(val)))
187                }
188                _ => None,
189            }
190        }
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use parser::dataset::*;
198    use std::collections::HashMap;
199    use std::fs::File;
200
201    #[test]
202    fn it_parses_the_data() {
203        let filename = format!(
204            "{}/tests/fixtures/6ba1abc7-b433-4029-b8f5-3b2bb12d3b6c.ulg",
205            env!("CARGO_MANIFEST_DIR")
206        );
207
208        let mut log_file = File::open(&filename).unwrap();
209
210        let first_position = log_file
211            .get_dataset("vehicle_gps_position")
212            .unwrap()
213            .next()
214            .unwrap();
215
216        let items = first_position.items();
217        let mut seen = HashMap::new();
218        for item in items.clone() {
219            seen.insert(item.clone(), 0);
220        }
221
222        for (name, data) in first_position.iter() {
223            *seen.get_mut(name).unwrap() += 1;
224            match name {
225                "timestamp" => assert_eq!(DataType::UInt64(375408345), data),
226                "time_utc_usec" => assert_eq!(DataType::UInt64(0), data),
227                "lat" => assert_eq!(DataType::Int32(407423012), data),
228                "lon" => assert_eq!(DataType::Int32(-741792999), data),
229                "alt" => assert_eq!(DataType::Int32(28495), data),
230                "alt_ellipsoid" => assert_eq!(DataType::Int32(0), data),
231                "s_variance_m_s" => assert_eq!(DataType::Float(0.0), data),
232                "c_variance_rad" => assert_eq!(DataType::Float(0.0), data),
233                "eph" => assert_eq!(DataType::Float(0.29999998), data),
234                "epv" => assert_eq!(DataType::Float(0.39999998), data),
235                "hdop" => assert_eq!(DataType::Float(0.0), data),
236                "vdop" => assert_eq!(DataType::Float(0.0), data),
237                "noise_per_ms" => assert_eq!(DataType::Int32(0), data),
238                "jamming_indicator" => assert_eq!(DataType::Int32(0), data),
239                "vel_m_s" => assert_eq!(DataType::Float(0.0), data),
240                "vel_n_m_s" => assert_eq!(DataType::Float(0.0), data),
241                "vel_e_m_s" => assert_eq!(DataType::Float(0.0), data),
242                "vel_d_m_s" => assert_eq!(DataType::Float(0.0), data),
243                "cog_rad" => assert_eq!(DataType::Float(0.0), data),
244                "timestamp_time_relative" => assert_eq!(DataType::Int32(0), data),
245                "fix_type" => assert_eq!(DataType::UInt8(3), data),
246                "vel_ned_valid" => assert_eq!(DataType::Bool(false), data),
247                "satellites_used" => assert_eq!(DataType::UInt8(10), data),
248                x => panic!(format!("unexpected field '{}'", x)),
249            }
250        }
251
252        for item in items {
253            assert_eq!(seen.get(item.as_str()), Some(&1), "item {} not seen", item);
254        }
255    }
256}