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 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}