irox_nmea0183/
lib.rs

1#![forbid(unsafe_code)]
2
3use std::fmt::{Display, Formatter};
4use std::str::FromStr;
5
6use log::trace;
7
8pub use error::*;
9use irox_bits::{Bits, ErrorKind};
10use irox_carto::altitude::{Altitude, AltitudeReferenceFrame};
11use irox_carto::coordinate::{Latitude, Longitude};
12use irox_time::gregorian::Date;
13use irox_time::Time;
14use irox_tools::options::MaybeInto;
15pub use irox_tools::packetio::{Packet, PacketBuilder, PacketData, Packetization};
16use irox_units::units::angle::Angle;
17use irox_units::units::compass::{CompassReference, RotationDirection, Track};
18use irox_units::units::length::{Length, LengthUnits};
19use irox_units::units::speed::{Speed, SpeedUnits};
20pub use output::*;
21
22use crate::gga::GGABuilder;
23use crate::gns::GNSBuilder;
24use crate::gsa::GSABuilder;
25use crate::gsv::GSVBuilder;
26use crate::rmc::RMCBuilder;
27
28mod error;
29pub mod input;
30mod output;
31
32#[derive(Debug, Copy, Clone, PartialEq)]
33pub enum MessageType {
34    GGA,
35    GLL,
36    GNS,
37    GSA,
38    GSV,
39    RMC,
40    VTG,
41    MSS,
42    ZDA,
43    SRF103,
44    SRF125,
45}
46
47#[derive(Debug, Clone, PartialEq)]
48pub struct Frame {
49    pub payload: FramePayload,
50    pub raw: Option<String>,
51}
52
53impl Display for Frame {
54    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
55        f.write_fmt(format_args!("NMEA Frame: {}", self.payload))
56    }
57}
58
59#[derive(Debug, Clone, PartialEq)]
60pub enum FramePayload {
61    GGA(gga::GGA),
62    GSA(gsa::GSA),
63    GSV(gsv::GSV),
64    GNS(gns::GNS),
65    RMC(rmc::RMC),
66    Unknown { key: String, raw_data: String },
67}
68
69impl Display for FramePayload {
70    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
71        match self {
72            FramePayload::GGA(gga) => f.write_fmt(format_args!("GGA: {gga}")),
73            FramePayload::GSA(gsa) => f.write_fmt(format_args!("GSA: {gsa}")),
74            FramePayload::GSV(gsv) => f.write_fmt(format_args!("GSV: {gsv}")),
75            FramePayload::GNS(gns) => f.write_fmt(format_args!("GNS: {gns}")),
76            FramePayload::RMC(rmc) => f.write_fmt(format_args!("RMC: {rmc}")),
77            FramePayload::Unknown { key, raw_data } => {
78                f.write_fmt(format_args!("UNK: {key} : {raw_data}"))
79            }
80        }
81    }
82}
83
84impl Packet for Frame {
85    type PacketType = ();
86
87    fn get_bytes(&self) -> Result<Vec<u8>, irox_bits::Error> {
88        if let Some(raw) = &self.raw {
89            return Ok(Vec::from(raw.as_bytes()));
90        }
91        match &self.payload {
92            FramePayload::GGA(gga) => gga.get_bytes(),
93            FramePayload::GSA(gsa) => gsa.get_bytes(),
94            FramePayload::GSV(gsv) => gsv.get_bytes(),
95            FramePayload::GNS(gns) => gns.get_bytes(),
96            FramePayload::RMC(rmc) => rmc.get_bytes(),
97            FramePayload::Unknown { .. } => Err(ErrorKind::Unsupported.into()),
98        }
99    }
100
101    fn get_type(&self) -> Self::PacketType {}
102}
103
104pub struct NMEAParser;
105
106impl PacketBuilder<Frame> for NMEAParser {
107    type Error = Error;
108
109    fn build_from<T: Bits>(&self, input: &mut T) -> Result<Frame, Self::Error> {
110        let packet = NMEAPacketizer::new().read_next_packet(input)?;
111        let raw = String::from_utf8_lossy(&packet).to_string();
112        trace!("PKT: {}", raw);
113
114        let key = packet.as_slice().read_until(b",")?;
115        let mut pkt = packet.as_slice();
116        let payload = if key.ends_with("GGA".as_bytes()) {
117            FramePayload::GGA(GGABuilder::new().build_from(&mut pkt)?)
118        } else if key.ends_with("GSA".as_bytes()) {
119            FramePayload::GSA(GSABuilder.build_from(&mut pkt)?)
120        } else if key.ends_with("GSV".as_bytes()) {
121            FramePayload::GSV(GSVBuilder.build_from(&mut pkt)?)
122        } else if key.ends_with("GNS".as_bytes()) {
123            FramePayload::GNS(GNSBuilder.build_from(&mut pkt)?)
124        } else if key.ends_with("RMC".as_bytes()) {
125            FramePayload::RMC(RMCBuilder.build_from(&mut pkt)?)
126        } else {
127            let key = String::from_utf8_lossy(key.as_slice()).to_string();
128            FramePayload::Unknown {
129                key,
130                raw_data: raw.clone(),
131            }
132        };
133        Ok(Frame {
134            raw: Some(raw),
135            payload,
136        })
137    }
138}
139
140#[derive(Default, Copy, Clone)]
141pub struct NMEAPacketizer;
142impl NMEAPacketizer {
143    pub fn new() -> NMEAPacketizer {
144        NMEAPacketizer {}
145    }
146}
147impl<T: Bits> Packetization<T> for NMEAPacketizer {
148    fn read_next_packet(&mut self, source: &mut T) -> Result<PacketData, irox_bits::Error> {
149        loop {
150            let val = source.read_u8()?;
151            // search for SOF
152            if val == b'$' {
153                break;
154            }
155        }
156
157        let mut packet: Vec<u8> = vec![b'$'];
158        packet.append(&mut source.read_until(b"\r\n")?);
159        Ok(packet)
160    }
161}
162
163pub fn calculate_checksum<T: AsRef<[u8]>>(data: &T) -> u8 {
164    let mut sl = data.as_ref();
165    if sl.starts_with(b"$") {
166        (_, sl) = sl.split_at(1);
167    }
168
169    let mut out: u8 = 0;
170    for v in sl {
171        if *v == b'*' {
172            break;
173        }
174        out ^= v;
175    }
176    out
177}
178
179#[allow(clippy::match_same_arms)]
180pub(crate) fn maybe_latitude(val: Option<&str>, ns: Option<&str>) -> Option<Latitude> {
181    let val = val?;
182    if val.len() < 3 {
183        return None;
184    }
185    let deg = &val.get(0..2)?.parse::<f64>().ok()?;
186    let min = &val.get(2..)?.parse::<f64>().ok()?;
187
188    let sig = match ns.maybe_into()? {
189        'N' | 'n' => 1.0,
190        'S' | 's' => -1.0,
191        _ => 1.0,
192    };
193
194    Some(Latitude(Angle::new_degrees(sig * (deg + min / 60.))))
195}
196#[allow(clippy::match_same_arms)]
197pub(crate) fn maybe_longitude(val: Option<&str>, ew: Option<&str>) -> Option<Longitude> {
198    let val = val?;
199    if val.len() < 4 {
200        return None;
201    }
202    let deg = &val.get(0..3)?.parse::<f64>().ok()?;
203    let min = &val.get(3..)?.parse::<f64>().ok()?;
204
205    let sig = match ew.maybe_into()? {
206        'E' | 'e' => 1.0,
207        'W' | 'w' => -1.0,
208        _ => 1.0,
209    };
210
211    Some(Longitude(Angle::new_degrees(sig * (deg + min / 60.))))
212}
213
214#[allow(clippy::match_same_arms)]
215pub(crate) fn maybe_length(val: Option<&str>, unit: Option<&str>) -> Option<Length> {
216    let val = val.maybe_into()?;
217    let unit = unit.maybe_into().unwrap_or('M');
218
219    let unit = match unit {
220        'M' | 'm' => LengthUnits::Meters,
221        'F' | 'f' => LengthUnits::Feet,
222        _ => LengthUnits::Meters,
223    };
224    Some(Length::new(val, unit))
225}
226
227pub(crate) fn maybe_altitude(
228    val: Option<&str>,
229    unit: Option<&str>,
230    frame: AltitudeReferenceFrame,
231) -> Option<Altitude> {
232    Some(Altitude::new(maybe_length(val, unit)?, frame))
233}
234
235pub struct NMEALatitude(pub Latitude);
236
237pub(crate) fn maybe_timestamp(val: Option<&str>) -> Option<Time> {
238    let time = val?;
239
240    let hh = time.get(0..2)?.parse::<u8>().ok()?;
241    let mm = time.get(2..4)?.parse::<u8>().ok()?;
242    let ss = time.get(4..)?.parse::<f64>().ok()?;
243
244    Time::from_hms_f64(hh, mm, ss).ok()
245}
246
247pub(crate) fn maybe_date(val: Option<&str>) -> Option<Date> {
248    let val = val?;
249    let (dd, rest) = val.split_at(2);
250    let (mm, yy) = rest.split_at(2);
251
252    let year = i32::from_str(yy).ok()? + 2000;
253    let mm = u8::from_str(mm).ok()?;
254    let dd = u8::from_str(dd).ok()?;
255
256    Date::try_from_values(year, mm, dd).ok()
257}
258
259pub(crate) fn maybe_speed(val: Option<&str>) -> Option<Speed> {
260    let speed = val?;
261
262    let speed = f64::from_str(speed).ok()?;
263    Some(Speed::new(speed, SpeedUnits::Knots))
264}
265
266pub(crate) fn maybe_track(val: Option<&str>) -> Option<Track> {
267    let angle = val?;
268    let angle = f64::from_str(angle).ok()?;
269    Some(Track::new_track(
270        Angle::new_degrees(angle),
271        RotationDirection::PositiveClockwise,
272        CompassReference::TrueNorth,
273    ))
274}
275
276#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
277pub enum ModeIndicator {
278    Autonomous,
279    Differential,
280    Estimated,
281    RTKFloat,
282    ManualInput,
283    NoValidFix,
284    Precise,
285    RTKInteger,
286    Simulator,
287    Valid,
288
289    #[default]
290    UnsetUnknown,
291}
292
293impl From<Option<char>> for ModeIndicator {
294    fn from(value: Option<char>) -> Self {
295        if let Some(value) = value {
296            return match value {
297                'A' => ModeIndicator::Autonomous,
298                'D' => ModeIndicator::Differential,
299                'E' => ModeIndicator::Estimated,
300                'F' => ModeIndicator::RTKFloat,
301                'M' => ModeIndicator::ManualInput,
302                'N' => ModeIndicator::NoValidFix,
303                'P' => ModeIndicator::Precise,
304                'R' => ModeIndicator::RTKInteger,
305                'S' => ModeIndicator::Simulator,
306                'V' => ModeIndicator::Valid,
307                _ => ModeIndicator::UnsetUnknown,
308            };
309        }
310        Default::default()
311    }
312}
313impl From<Option<&str>> for ModeIndicator {
314    fn from(value: Option<&str>) -> Self {
315        if let Some(val) = value {
316            return val.chars().next().into();
317        }
318        Default::default()
319    }
320}