xsens_mti/wire/
data_id.rs

1//! MTData2 Data Identifier
2//!
3//! The data identifier is a 16-bit unsigned integer describing the contents
4//! of an MTData2 packet.
5//!
6//! It consists of the following fields:
7//! * Format: 4-bits describing the data precision and coordinate system
8//! * Type: lower 4-bits of the DataType
9//! * Group: upper 5-bits of the DataType
10//!
11//! ```no_compile
12//! B15                            B0
13//!  ┊                             ┊
14//!  ┊                             ┊
15//!  ┊                             ┊
16//! ║  second byte  ║  first byte   ║
17//! ╟───────────────╫───────────────╢
18//! ║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║
19//! ╟────────-╫────-╫────────╫──────╢
20//! ║  group  ║     ║  type  ║      ║
21//!              ┊               ┊
22//!              ┊               ┊
23//!           reserved         format
24//! ```
25
26// TODO
27// add all the datatype ids
28// docs on page 31
29// do a nicer Display impl
30
31use crate::wire::WireError;
32use byteorder::{BigEndian, ByteOrder};
33use core::fmt;
34use core::mem;
35use static_assertions::const_assert_eq;
36
37const_assert_eq!(WireDataId::<&[u8]>::WIRE_SIZE, mem::size_of::<u16>());
38
39enum_with_unknown! {
40    #[derive(Default)]
41    pub enum Precision(u8) {
42        /// Single precision IEEE 32-bit floating point number
43        #[default]
44        Float32 = 0x0,
45        /// Fixed point 12.20 32-bit number
46        Fp1220  = 0x1,
47        /// Fixed point 16.32 48-bit number
48        Fp1632  = 0x2,
49        /// Double precision IEEE 64-bit floating point number
50        Float64 = 0x3,
51    }
52}
53
54enum_with_unknown! {
55    #[derive(Default)]
56    pub enum CoordinateSystem(u8) {
57        /// East-North-Up coordinate system
58        #[default]
59        Enu = 0x0,
60        /// North-East-Down coordinate system
61        Ned = 0x4,
62        /// North-West-Up coordinate system
63        Nwu = 0x8,
64    }
65}
66
67enum_with_unknown! {
68    pub enum DataType(u16) {
69        // TemperatureGroup     = 0x08x0
70        Temperature             = 0x0810, // degrees Celsius
71
72        // TimestampGroup       = 0x10x0
73        UtcTime                 = 0x1010,
74        PacketCounter           = 0x1020,
75        SampleTimeFine          = 0x1060,
76        SampleTimeCoarse        = 0x1070,
77
78        // OrientationGroup     = 0x20xy
79        Quaternion              = 0x2010,
80        EulerAngles             = 0x2030, // degrees
81
82        // AccelerationGroup    = 0x40xy
83        DeltaV                  = 0x4010, // m/s
84        Acceleration            = 0x4020, // m/s^2
85        FreeAcceleration        = 0x4030, // m/s^2
86        AccelerationHR          = 0x4040, // m/s^2
87
88        // PositionGroup        = 0x50xy
89        AltitudeEllipsoid       = 0x5020, // meters
90        PositionEcef            = 0x5030, // meters
91        LatLon                  = 0x5040, // degrees
92
93        // GnssGroup            = 0x70x0
94        GnssPvtData             = 0x7010,
95        GnssSatInfo             = 0x7020,
96        GnssPvtPulse            = 0x7030, // seconds
97
98        // AngularVelocityGroup = 0x80xy
99        RateOfTurn              = 0x8020, // rad/s
100        DeltaQ                  = 0x8030,
101        RateOfTurnHr            = 0x8040, // rad/s
102
103        // MagneticGroup        = 0xC0xy
104        MagneticField           = 0xC020, // a.u. (atomic units)
105
106        // VelocityGroup        = 0xD0xy
107        VelocityXYZ             = 0xD010,
108
109        // StatusGroup          = 0xE0x0
110        StatusByte              = 0xE010,
111        StatusWord              = 0xE020,
112        DeviceId                = 0xE080,
113        LocationId              = 0xE090,
114    }
115}
116
117impl DataType {
118    /// Shave off the format bits (B0:B3) and the reserved bits (B8:B10)
119    /// to yield the type and group bits that form the DataType
120    const MASK: u16 = 0b1111_1000_1111_0000;
121}
122
123impl fmt::Display for DataType {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        match self {
126            DataType::Unknown(t) => write!(f, "Unknown(0x{:04X})", t),
127            _ => write!(f, "{:?}", self),
128        }
129    }
130}
131
132#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
133pub struct DataId {
134    pub data_type: DataType,
135    pub precision: Precision,
136    pub coordinate_system: CoordinateSystem,
137}
138
139impl DataId {
140    pub fn new(
141        data_type: DataType,
142        precision: Precision,
143        coordinate_system: CoordinateSystem,
144    ) -> Self {
145        DataId {
146            data_type,
147            precision,
148            coordinate_system,
149        }
150    }
151
152    pub fn to_wire(self) -> u16 {
153        let group_type = DataType::MASK & u16::from(self.data_type);
154        let precision = 0b11 & u8::from(self.precision) as u16;
155        let coordinate_system = 0b1100 & u8::from(self.coordinate_system) as u16;
156        group_type | coordinate_system | precision
157    }
158
159    pub fn from_wire(value: u16) -> Self {
160        let precision = value & 0b11;
161        let coordinate_system = value & 0b1100;
162        let group_type = value & DataType::MASK;
163        DataId {
164            data_type: DataType::from(group_type),
165            precision: Precision::from(precision as u8),
166            coordinate_system: CoordinateSystem::from(coordinate_system as u8),
167        }
168    }
169
170    pub fn from_data_type(data_type: DataType) -> Self {
171        DataId {
172            data_type,
173            precision: Precision::default(),
174            coordinate_system: CoordinateSystem::default(),
175        }
176    }
177
178    pub fn data_type(&self) -> DataType {
179        self.data_type
180    }
181
182    pub fn precision(&self) -> Precision {
183        self.precision
184    }
185
186    pub fn coordinate_system(&self) -> CoordinateSystem {
187        self.coordinate_system
188    }
189}
190
191impl From<u16> for DataId {
192    fn from(value: u16) -> Self {
193        DataId::from_wire(value)
194    }
195}
196
197impl From<DataId> for u16 {
198    fn from(value: DataId) -> Self {
199        value.to_wire()
200    }
201}
202
203impl fmt::Display for DataId {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        write!(
206            f,
207            "DataId(0x{:04X}, {}, {:?}, {:?})",
208            self.to_wire(),
209            self.data_type(),
210            self.precision(),
211            self.coordinate_system(),
212        )
213    }
214}
215
216#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
217pub struct WireDataId<T: AsRef<[u8]>> {
218    buffer: T,
219}
220
221mod field {
222    use crate::wire::Field;
223
224    pub const DATA_ID: Field = 0..2;
225}
226
227impl<T: AsRef<[u8]>> WireDataId<T> {
228    pub const WIRE_SIZE: usize = mem::size_of::<u16>();
229
230    pub fn new_unchecked(buffer: T) -> WireDataId<T> {
231        WireDataId { buffer }
232    }
233
234    pub fn new(buffer: T) -> Result<WireDataId<T>, WireError> {
235        let f = Self::new_unchecked(buffer);
236        f.check_len()?;
237        Ok(f)
238    }
239
240    pub fn check_len(&self) -> Result<(), WireError> {
241        let len = self.buffer.as_ref().len();
242        if len < Self::WIRE_SIZE {
243            Err(WireError::MissingBytes)
244        } else {
245            Ok(())
246        }
247    }
248
249    pub fn into_inner(self) -> T {
250        self.buffer
251    }
252
253    #[inline]
254    pub fn buffer_len() -> usize {
255        Self::WIRE_SIZE
256    }
257
258    #[inline]
259    pub fn data_id(&self) -> DataId {
260        let data = self.buffer.as_ref();
261        let value = BigEndian::read_u16(&data[field::DATA_ID]);
262        DataId::from(value)
263    }
264}
265
266impl<T: AsRef<[u8]> + AsMut<[u8]>> WireDataId<T> {
267    #[inline]
268    pub fn set_data_id(&mut self, value: DataId) {
269        let data = self.buffer.as_mut();
270        BigEndian::write_u16(&mut data[field::DATA_ID], u16::from(value));
271    }
272}
273
274impl<T: AsRef<[u8]>> AsRef<[u8]> for WireDataId<T> {
275    fn as_ref(&self) -> &[u8] {
276        self.buffer.as_ref()
277    }
278}
279
280#[cfg(test)]
281pub(crate) mod propt {
282    use super::*;
283    use proptest::{
284        arbitrary::Arbitrary,
285        num,
286        prelude::{any, RngCore},
287        prop_compose,
288        strategy::{NewTree, Strategy, ValueTree},
289        test_runner::TestRunner,
290    };
291
292    impl Precision {
293        const MASK: u8 = Precision::Float32.into_inner()
294            | Precision::Fp1220.into_inner()
295            | Precision::Fp1632.into_inner()
296            | Precision::Float64.into_inner();
297    }
298
299    pub struct PrecisionBinarySearch(num::u8::BinarySearch);
300
301    impl ValueTree for PrecisionBinarySearch {
302        type Value = Precision;
303
304        fn current(&self) -> Precision {
305            let v = self.0.current();
306            Precision::from(v & Precision::MASK)
307        }
308
309        fn simplify(&mut self) -> bool {
310            self.0.simplify()
311        }
312
313        fn complicate(&mut self) -> bool {
314            self.0.complicate()
315        }
316    }
317
318    #[derive(Debug)]
319    pub struct AnyPrecision;
320
321    impl Strategy for AnyPrecision {
322        type Tree = PrecisionBinarySearch;
323        type Value = Precision;
324
325        fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
326            Ok(PrecisionBinarySearch(num::u8::BinarySearch::new(
327                runner.rng().next_u32() as u8,
328            )))
329        }
330    }
331
332    impl Arbitrary for Precision {
333        type Parameters = ();
334        type Strategy = AnyPrecision;
335
336        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
337            AnyPrecision
338        }
339    }
340
341    pub fn gen_precision() -> impl Strategy<Value = Precision> {
342        any::<Precision>()
343    }
344
345    impl CoordinateSystem {
346        const MASK: u8 = CoordinateSystem::Enu.into_inner()
347            | CoordinateSystem::Ned.into_inner()
348            | CoordinateSystem::Nwu.into_inner();
349    }
350
351    pub struct CoordinateSystemBinarySearch(num::u8::BinarySearch);
352
353    impl ValueTree for CoordinateSystemBinarySearch {
354        type Value = CoordinateSystem;
355
356        fn current(&self) -> CoordinateSystem {
357            let v = self.0.current();
358            CoordinateSystem::from(v & CoordinateSystem::MASK)
359        }
360
361        fn simplify(&mut self) -> bool {
362            self.0.simplify()
363        }
364
365        fn complicate(&mut self) -> bool {
366            self.0.complicate()
367        }
368    }
369
370    #[derive(Debug)]
371    pub struct AnyCoordinateSystem;
372
373    impl Strategy for AnyCoordinateSystem {
374        type Tree = CoordinateSystemBinarySearch;
375        type Value = CoordinateSystem;
376
377        fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
378            Ok(CoordinateSystemBinarySearch(num::u8::BinarySearch::new(
379                runner.rng().next_u32() as u8,
380            )))
381        }
382    }
383
384    impl Arbitrary for CoordinateSystem {
385        type Parameters = ();
386        type Strategy = AnyCoordinateSystem;
387
388        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
389            AnyCoordinateSystem
390        }
391    }
392
393    pub fn gen_coordinate_system() -> impl Strategy<Value = CoordinateSystem> {
394        any::<CoordinateSystem>()
395    }
396
397    pub struct DataTypeBinarySearch(num::u16::BinarySearch);
398
399    impl ValueTree for DataTypeBinarySearch {
400        type Value = DataType;
401
402        fn current(&self) -> DataType {
403            let v = self.0.current();
404            DataType::from(v & DataType::MASK)
405        }
406
407        fn simplify(&mut self) -> bool {
408            self.0.simplify()
409        }
410
411        fn complicate(&mut self) -> bool {
412            self.0.complicate()
413        }
414    }
415
416    #[derive(Debug)]
417    pub struct AnyDataType;
418
419    impl Strategy for AnyDataType {
420        type Tree = DataTypeBinarySearch;
421        type Value = DataType;
422
423        fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
424            Ok(DataTypeBinarySearch(num::u16::BinarySearch::new(
425                runner.rng().next_u32() as u16,
426            )))
427        }
428    }
429
430    impl Arbitrary for DataType {
431        type Parameters = ();
432        type Strategy = AnyDataType;
433
434        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
435            AnyDataType
436        }
437    }
438
439    pub fn gen_data_type() -> impl Strategy<Value = DataType> {
440        any::<DataType>()
441    }
442
443    prop_compose! {
444        pub fn gen_data_id()(
445            data_type in gen_data_type(),
446            precision in gen_precision(),
447            coordinate_system in gen_coordinate_system(),
448        ) -> DataId {
449            DataId::new(data_type, precision, coordinate_system)
450        }
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457    use pretty_assertions::assert_eq;
458    use propt::*;
459    use proptest::prelude::*;
460
461    static WIRE_BYTES: [u8; 2] = [0x20, 0x16];
462
463    #[test]
464    fn buffer_len() {
465        assert_eq!(WireDataId::<&[u8]>::buffer_len(), mem::size_of::<u16>());
466        assert_eq!(WireDataId::<&[u8]>::WIRE_SIZE, mem::size_of::<u16>());
467    }
468
469    #[test]
470    fn construct() {
471        let mut bytes = [0xFF; 2];
472        let mut w = WireDataId::new_unchecked(&mut bytes[..]);
473        assert_eq!(w.check_len(), Ok(()));
474        w.set_data_id(DataId::new(
475            DataType::Quaternion,
476            Precision::Fp1632,
477            CoordinateSystem::Ned,
478        ));
479        assert_eq!(w.into_inner(), &WIRE_BYTES[..]);
480    }
481
482    #[test]
483    fn deconstruct() {
484        let w = WireDataId::new(&WIRE_BYTES[..]).unwrap();
485        assert_eq!(
486            w.data_id(),
487            DataId::new(
488                DataType::Quaternion,
489                Precision::Fp1632,
490                CoordinateSystem::Ned,
491            )
492        );
493    }
494
495    #[test]
496    fn missing_bytes() {
497        let bytes = [0xFF; 2 - 1];
498        assert_eq!(bytes.len(), WireDataId::<&[u8]>::buffer_len() - 1);
499        let w = WireDataId::new(&bytes[..]);
500        assert_eq!(w.unwrap_err(), WireError::MissingBytes);
501    }
502
503    proptest! {
504        #[test]
505        fn round_trip_precision(v_in in gen_precision()) {
506            let wire = u8::from(v_in);
507            let v_out = Precision::from(wire);
508            assert_eq!(v_in, v_out);
509        }
510
511        #[test]
512        fn round_trip_coordinate_system(v_in in gen_coordinate_system()) {
513            let wire = u8::from(v_in);
514            let v_out = CoordinateSystem::from(wire);
515            assert_eq!(v_in, v_out);
516        }
517
518        #[test]
519        fn round_trip_data_type(v_in in gen_data_type()) {
520            let wire = u16::from(v_in);
521            let v_out = DataType::from(wire);
522            assert_eq!(v_in, v_out);
523        }
524
525        #[test]
526        fn round_trip_data_id(v_in in gen_data_id()) {
527            let wire = u16::from(v_in).to_be_bytes();
528            let v_out = DataId::from(u16::from_be_bytes(wire));
529            assert_eq!(v_in, v_out);
530        }
531    }
532}