use crate::nmea::field::{FieldReader, FieldWriter};
#[derive(Debug, Clone, PartialEq)]
pub struct Gst {
pub time: Option<String>,
pub range_rms: Option<f32>,
pub std_major: Option<f32>,
pub std_minor: Option<f32>,
pub orient: Option<f32>,
pub std_lat: Option<f32>,
pub std_lon: Option<f32>,
pub std_alt: Option<f32>,
}
impl Gst {
pub const SENTENCE_TYPE: &str = "GST";
pub fn parse(fields: &[&str]) -> Option<Self> {
let mut r = FieldReader::new(fields);
Some(Self {
time: r.string(),
range_rms: r.f32(),
std_major: r.f32(),
std_minor: r.f32(),
orient: r.f32(),
std_lat: r.f32(),
std_lon: r.f32(),
std_alt: r.f32(),
})
}
pub fn encode(&self) -> Vec<String> {
let mut w = FieldWriter::new();
w.string(self.time.as_deref());
w.f32(self.range_rms);
w.f32(self.std_major);
w.f32(self.std_minor);
w.f32(self.orient);
w.f32(self.std_lat);
w.f32(self.std_lon);
w.f32(self.std_alt);
w.finish()
}
pub fn to_sentence(&self, talker: &str) -> String {
let fields = self.encode();
let field_refs: Vec<&str> = fields.iter().map(|s| s.as_str()).collect();
crate::encode_frame('$', talker, Self::SENTENCE_TYPE, &field_refs)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse_frame;
#[test]
fn gst_empty() {
let f = parse_frame("$GPGST,,,,,,,,*57").expect("valid");
let g = Gst::parse(&f.fields).expect("parse");
assert!(g.time.is_none());
assert!(g.range_rms.is_none());
assert!(g.std_major.is_none());
assert!(g.std_minor.is_none());
assert!(g.orient.is_none());
assert!(g.std_lat.is_none());
assert!(g.std_lon.is_none());
assert!(g.std_alt.is_none());
}
#[test]
fn gst_encode_roundtrip() {
let gst = Gst {
time: Some("131519.00".to_string()),
range_rms: Some(11.0),
std_major: Some(5.2),
std_minor: Some(3.1),
orient: Some(45.0),
std_lat: Some(0.7),
std_lon: Some(0.49),
std_alt: Some(1.1),
};
let sentence = gst.to_sentence("GP");
assert!(sentence.starts_with("$GPGST,"));
let frame = parse_frame(sentence.trim()).expect("re-parse");
let gst2 = Gst::parse(&frame.fields).expect("re-parse GST");
assert_eq!(gst, gst2);
}
#[test]
fn gst_full_pynmeagps() {
let frame =
parse_frame("$GNGST,103607.00,38,60,38,89,15,24,31*63").expect("valid pynmeagps GST");
let gst = Gst::parse(&frame.fields).expect("parse GST");
assert_eq!(gst.time, Some("103607.00".to_string()));
assert!((gst.range_rms.expect("rms") - 38.0).abs() < 0.1);
assert!((gst.std_major.expect("std_major") - 60.0).abs() < 0.1);
assert!((gst.std_minor.expect("std_minor") - 38.0).abs() < 0.1);
assert!((gst.orient.expect("orient") - 89.0).abs() < 0.1);
assert!((gst.std_lat.expect("std_lat") - 15.0).abs() < 0.1);
assert!((gst.std_lon.expect("std_lon") - 24.0).abs() < 0.1);
assert!((gst.std_alt.expect("std_alt") - 31.0).abs() < 0.1);
}
#[test]
fn gst_partial_gpsd() {
let frame = parse_frame("$GPGST,131519.00,11,,,,0.70,0.49,1.1*53").expect("valid");
let gst = Gst::parse(&frame.fields).expect("parse GST");
assert_eq!(gst.time, Some("131519.00".to_string()));
assert!((gst.range_rms.expect("rms") - 11.0).abs() < 0.1);
assert!(gst.std_major.is_none());
assert!(gst.std_minor.is_none());
assert!(gst.orient.is_none());
assert!((gst.std_lat.expect("std_lat") - 0.70).abs() < 0.01);
assert!((gst.std_lon.expect("std_lon") - 0.49).abs() < 0.01);
assert!((gst.std_alt.expect("std_alt") - 1.1).abs() < 0.1);
}
}