nmeasis 26.4.1

A memory-safe NMEA 0183 parser with a C FFI
Documentation
use crate::{
    encoder::NmeaEncode,
    faa::FaaMode,
    macros::{write_byte, write_str},
    message::NmeaMessageError,
    number::NmeaNumber,
    parser::NmeaParse,
};

// NOTE: Some older versions use a different sentence structure for this.
// We currently only support the newer version.

/// VTG - Track made good and Ground Speed
#[derive(Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Vtg<'a> {
    /// 1. Course over ground, Degrees True
    pub degrees_true: &'a str,
    /// 2. "T" for relative to True North
    pub degrees_true_indicator: &'a str,
    /// 3. Course over ground, Degrees Magnetic
    pub degrees_magnetic: &'a str,
    /// 4. "M" for relative to magnetic North
    pub degrees_magnetic_indicator: &'a str,
    /// 5. Speed in Knots
    pub speed_knots: &'a str,
    /// 6. "N" for knots.
    pub speed_knots_units: &'a str,
    /// 7. Speed in kph
    pub speed_kph: &'a str,
    /// 8. "K" for kph.
    pub speed_kph_units: &'a str,
    /// 9. Mode Indicator (>= NMEA 2.3)
    pub faa_mode: Option<&'a str>,
}

impl<'a> NmeaParse<'a> for Vtg<'a> {
    fn parse(fields: &'a str) -> Result<Self, NmeaMessageError> {
        let mut f = fields.splitn(9, ',');
        Ok(Self {
            degrees_true: f.next().ok_or(NmeaMessageError::MissingField)?,
            degrees_true_indicator: f.next().ok_or(NmeaMessageError::MissingField)?,
            degrees_magnetic: f.next().ok_or(NmeaMessageError::MissingField)?,
            degrees_magnetic_indicator: f.next().ok_or(NmeaMessageError::MissingField)?,
            speed_knots: f.next().ok_or(NmeaMessageError::MissingField)?,
            speed_knots_units: f.next().ok_or(NmeaMessageError::MissingField)?,
            speed_kph: f.next().ok_or(NmeaMessageError::MissingField)?,
            speed_kph_units: f.next().ok_or(NmeaMessageError::MissingField)?,
            faa_mode: f.next().filter(|s| !s.is_empty()),
        })
    }
}

impl NmeaEncode for Vtg<'_> {
    fn encoded_len(&self) -> usize {
        self.degrees_true.len()
            + self.degrees_true_indicator.len()
            + self.degrees_magnetic.len()
            + self.degrees_magnetic_indicator.len()
            + self.speed_knots.len()
            + self.speed_knots_units.len()
            + self.speed_kph.len()
            + self.speed_kph_units.len()
            + self.faa_mode.map_or(0, |f| f.len() + 1)
            + 7
    }

    fn encode(&self, buf: &mut [u8]) -> usize {
        let mut pos = 0;
        write_str!(buf, pos, self.degrees_true);
        write_byte!(buf, pos, b',');
        write_str!(buf, pos, self.degrees_true_indicator);
        write_byte!(buf, pos, b',');
        write_str!(buf, pos, self.degrees_magnetic);
        write_byte!(buf, pos, b',');
        write_str!(buf, pos, self.degrees_magnetic_indicator);
        write_byte!(buf, pos, b',');
        write_str!(buf, pos, self.speed_knots);
        write_byte!(buf, pos, b',');
        write_str!(buf, pos, self.speed_knots_units);
        write_byte!(buf, pos, b',');
        write_str!(buf, pos, self.speed_kph);
        write_byte!(buf, pos, b',');
        write_str!(buf, pos, self.speed_kph_units);

        if let Some(faa_mode) = self.faa_mode {
            write_byte!(buf, pos, b',');
            write_str!(buf, pos, faa_mode);
        }

        pos
    }
}

impl Vtg<'_> {
    #[must_use]
    pub fn degrees_true(&self) -> Option<NmeaNumber> {
        if self.degrees_true_indicator != "T" {
            return None;
        }
        NmeaNumber::parse(self.degrees_true)
    }

    #[must_use]
    pub fn degrees_magnetic(&self) -> Option<NmeaNumber> {
        if self.degrees_magnetic_indicator != "M" {
            return None;
        }
        NmeaNumber::parse(self.degrees_magnetic)
    }

    #[must_use]
    pub fn speed_knots(&self) -> Option<NmeaNumber> {
        if self.speed_knots_units != "N" {
            return None;
        }

        NmeaNumber::parse(self.speed_knots)
    }

    #[must_use]
    pub fn speed_kph(&self) -> Option<NmeaNumber> {
        if self.speed_kph_units != "K" {
            return None;
        }

        NmeaNumber::parse(self.speed_kph)
    }

    pub fn faa_mode(&self) -> Option<FaaMode> {
        self.faa_mode.and_then(FaaMode::parse)
    }
}