binex/message/record/solutions/
mod.rs

1//! Monument / Geodetic marker frames
2
3use crate::{
4    message::time::{decode_gpst_epoch, encode_epoch, TimeResolution},
5    Error,
6};
7
8pub use frame::{PositionEcef3d, PositionGeo3d, TemporalSolution, Velocity3d, VelocityNED3d};
9use hifitime::{Epoch, TimeScale};
10
11mod fid;
12mod frame;
13
14// private
15use fid::FieldID;
16
17// public
18pub use frame::SolutionsFrame;
19
20#[derive(Debug, Clone, Default, PartialEq)]
21pub struct Solutions {
22    /// [Epoch]
23    pub epoch: Epoch,
24    /// Frames also refered to as Subrecords
25    pub frames: Vec<SolutionsFrame>,
26}
27
28impl Iterator for Solutions {
29    type Item = SolutionsFrame;
30    fn next(&mut self) -> Option<Self::Item> {
31        self.frames.first().cloned()
32    }
33}
34
35impl Solutions {
36    /// 4 byte date uint4       }
37    /// 2 byte ms               } epoch
38    /// 1 byte FID
39    ///     if FID corresponds to a character string
40    ///     the next 1-4 BNXI byte represent the number of bytes in the caracter string
41    /// follows: FID dependent sequence. See [FieldID].
42    const MIN_SIZE: usize = 4 + 2 + 1;
43
44    /// Creates new empty [Solutions], which is not suitable for encoding yet.
45    /// Use other method to customize it!
46    /// ```
47    /// use binex::prelude::{
48    ///     Epoch, Solutions, TemporalSolution,
49    ///     Message, Record, Meta,
50    /// };
51    ///     
52    /// // forge pvt solutions message
53    /// let meta = Meta {
54    ///     reversed: false,
55    ///     big_endian: true,
56    ///     enhanced_crc: false,
57    /// };
58    ///
59    /// let t = Epoch::from_gpst_seconds(60.0 + 0.75);
60    ///
61    /// // Position Velocity Time (PVT) solution update
62    /// let solutions = Solutions::new(t)
63    ///     .with_pvt_ecef_wgs84(
64    ///         1.0, // x(t) [m/s]
65    ///         2.0, // y(t) [m/s]
66    ///         3.0, // z(t) [m/s]
67    ///         4.0, // dx(t)/dt [m/s^2]
68    ///         5.0, // dy(t)/dt [m/s^2]
69    ///         6.0, // dz(t)/dt [m/s^2]
70    ///         TemporalSolution {
71    ///             offset_s: 7.0, // [s]
72    ///             drift_s_s: Some(8.0), // [s/s]
73    ///         });
74    ///
75    /// let solutions = Record::new_solutions(solutions);
76    /// let msg = Message::new(meta, solutions);
77    ///
78    /// let mut encoded = [0; 256];
79    /// let _ = msg.encode(&mut encoded, msg.encoding_size())
80    ///     .unwrap();
81    ///
82    /// let decoded = Message::decode(&encoded)
83    ///     .unwrap();
84    ///
85    /// assert_eq!(decoded, msg);
86    /// ```
87    pub fn new(epoch: Epoch) -> Self {
88        Self {
89            epoch,
90            frames: Vec::with_capacity(8),
91        }
92    }
93
94    /// [Solutions] decoding attempt from buffered content.
95    /// ## Inputs
96    ///    - mlen: message length in bytes
97    ///    - big_endian: endianness
98    ///    - buf: buffered content
99    /// ## Outputs
100    ///    - Result<[Solutions], [Error]>
101    pub(crate) fn decode(mlen: usize, big_endian: bool, buf: &[u8]) -> Result<Self, Error> {
102        if mlen < Self::MIN_SIZE {
103            // does not look good
104            return Err(Error::NotEnoughBytes);
105        }
106
107        // decode timestamp
108        let epoch = decode_gpst_epoch(big_endian, TimeResolution::MilliSecond, buf)?;
109
110        // parse inner frames (= subrecords)
111        let mut ptr = 6;
112        let mut frames = Vec::<SolutionsFrame>::with_capacity(8);
113
114        while ptr < mlen {
115            // decode frame
116            match SolutionsFrame::decode(big_endian, &buf[ptr..]) {
117                Ok(fr) => {
118                    ptr += fr.encoding_size();
119                    frames.push(fr);
120                },
121                Err(_) => {
122                    if ptr == 6 {
123                        // did not parse a single record: incorrect message
124                        return Err(Error::NotEnoughBytes);
125                    } else {
126                        break; // parsed all records
127                    }
128                },
129            }
130        }
131
132        Ok(Self { epoch, frames })
133    }
134
135    /// Encodes [Solutions] into buffer, returns encoded size (total bytes).
136    /// [Solutions] must fit in preallocated buffer.
137    pub(crate) fn encode(&self, big_endian: bool, buf: &mut [u8]) -> Result<usize, Error> {
138        let size = self.encoding_size();
139        if buf.len() < size {
140            return Err(Error::NotEnoughBytes);
141        }
142
143        // encode tstamp
144        let t = self.epoch.to_time_scale(TimeScale::GPST);
145        let mut ptr = encode_epoch(t, TimeResolution::MilliSecond, big_endian, buf)?;
146
147        // encode subrecords
148        for fr in self.frames.iter() {
149            let size = fr.encode(big_endian, &mut buf[ptr..])?;
150            ptr += size;
151        }
152
153        Ok(size)
154    }
155
156    /// Returns total length (bytewise) required to fully encode [Self].
157    /// Use this to fulfill [Self::encode] requirements.
158    pub(crate) fn encoding_size(&self) -> usize {
159        let mut size = 6; // tstamp
160        for fr in self.frames.iter() {
161            size += fr.encoding_size(); // content
162        }
163        size
164    }
165
166    /// Add one [MonumentGeoFrame::Comment] to [MonumentGeoRecord].
167    /// You can add as many as needed.
168    pub fn with_comment(&self, comment: &str) -> Self {
169        let mut s = self.clone();
170        s.frames.push(SolutionsFrame::Comment(comment.to_string()));
171        s
172    }
173
174    /// Attach readable Extra information to the solutions (like context description).
175    pub fn with_extra_info(&self, info: &str) -> Self {
176        let mut s = self.clone();
177        // preserve unique item
178        if let Some(info) = s
179            .frames
180            .iter_mut()
181            .filter_map(|fr| match fr {
182                SolutionsFrame::Extra(info) => Some(info),
183                _ => None,
184            })
185            .reduce(|k, _| k)
186        {
187            *info = info.to_string(); // overwrite ; replace
188        } else {
189            s.frames.push(SolutionsFrame::Extra(info.to_string()));
190        }
191        s
192    }
193
194    /// Define PVT solution update, in ECEF
195    pub fn with_pvt_ecef_wgs84(
196        &self,
197        x_ecef_m: f64,
198        y_ecef_m: f64,
199        z_ecef_m: f64,
200        velx_ecef_m_s: f64,
201        vely_ecef_m_s: f64,
202        velz_ecef_m_s: f64,
203        temporal_sol: TemporalSolution,
204    ) -> Self {
205        let mut s = self.clone();
206        s.frames.push(SolutionsFrame::AntennaEcefPosition(
207            PositionEcef3d::new_wgs84(x_ecef_m, y_ecef_m, z_ecef_m),
208        ));
209        s.frames
210            .push(SolutionsFrame::AntennaEcefVelocity(Velocity3d {
211                x_m_s: velx_ecef_m_s,
212                y_m_s: vely_ecef_m_s,
213                z_m_s: velz_ecef_m_s,
214            }));
215        s.frames
216            .push(SolutionsFrame::TemporalSolution(temporal_sol));
217        s
218    }
219
220    /// Define PVT solution update, in ellipsoid of your choice
221    pub fn with_pvt_ecef(
222        &self,
223        x_ecef_m: f64,
224        y_ecef_m: f64,
225        z_ecef_m: f64,
226        velx_ecef_m_s: f64,
227        vely_ecef_m_s: f64,
228        velz_ecef_m_s: f64,
229        temporal_sol: TemporalSolution,
230        ellipsoid: &str,
231    ) -> Self {
232        let mut s = self.clone();
233        s.frames
234            .push(SolutionsFrame::AntennaEcefPosition(PositionEcef3d {
235                x_ecef_m,
236                y_ecef_m,
237                z_ecef_m,
238                ellipsoid: ellipsoid.to_string(),
239            }));
240        s.frames
241            .push(SolutionsFrame::AntennaEcefVelocity(Velocity3d {
242                x_m_s: velx_ecef_m_s,
243                y_m_s: vely_ecef_m_s,
244                z_m_s: velz_ecef_m_s,
245            }));
246        s.frames
247            .push(SolutionsFrame::TemporalSolution(temporal_sol));
248        s
249    }
250}
251
252#[cfg(test)]
253mod test {
254    use crate::{
255        message::record::{Solutions, TemporalSolution},
256        prelude::Epoch,
257    };
258
259    #[test]
260    fn pvt_solutions_ecef() {
261        let big_endian = true;
262
263        let t0 = Epoch::from_gpst_seconds(71.000);
264
265        let solutions = Solutions::new(t0).with_comment("Hello");
266
267        let mut buf = [0; 32];
268        let size = solutions.encode(big_endian, &mut buf).unwrap();
269
270        assert_eq!(size, solutions.encoding_size());
271        assert_eq!(
272            buf,
273            [
274                0, 0, 0, 1, 0x2a, 0xf8, 0, 5, 72, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
275                0, 0, 0, 0, 0, 0, 0, 0, 0,
276            ]
277        );
278
279        let decoded = Solutions::decode(size, big_endian, &buf).unwrap();
280
281        assert_eq!(decoded, solutions);
282
283        let solutions = solutions.with_pvt_ecef_wgs84(
284            1.0,
285            2.0,
286            3.0,
287            4.0,
288            5.0,
289            6.0,
290            TemporalSolution {
291                offset_s: 7.0,
292                drift_s_s: None,
293            },
294        );
295
296        let mut buf = [0; 128];
297        let size = solutions.encode(big_endian, &mut buf).unwrap();
298
299        assert_eq!(size, solutions.encoding_size());
300        assert_eq!(
301            buf,
302            [
303                0, 0, 0, 1, 0x2a, 0xf8, 0, 5, 72, 101, 108, 108, 111, 1, 0, 63, 240, 0, 0, 0, 0, 0,
304                0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 3, 64, 16, 0, 0, 0, 0, 0, 0,
305                64, 20, 0, 0, 0, 0, 0, 0, 64, 24, 0, 0, 0, 0, 0, 0, 6, 64, 28, 0, 0, 0, 0, 0, 0, 0,
306                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
307                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
308            ]
309        );
310
311        let decoded = Solutions::decode(size, big_endian, &buf).unwrap();
312
313        assert_eq!(decoded, solutions);
314    }
315}