use crate::nmea::field::{FieldReader, FieldWriter, NmeaEncodable};
#[derive(Debug, Clone, PartialEq)]
pub struct Gns {
pub time: Option<String>,
pub lat: Option<f64>,
pub ns: Option<char>,
pub lon: Option<f64>,
pub ew: Option<char>,
pub mode: Option<String>,
pub num_sats: Option<u8>,
pub hdop: Option<f32>,
pub altitude: Option<f32>,
pub geoid_sep: Option<f32>,
pub dgps_age: Option<f32>,
pub dgps_station: Option<String>,
pub nav_status: Option<char>,
}
impl Gns {
pub fn parse(fields: &[&str]) -> Option<Self> {
let mut r = FieldReader::new(fields);
Some(Self {
time: r.string(),
lat: r.f64(),
ns: r.char(),
lon: r.f64(),
ew: r.char(),
mode: r.string(),
num_sats: r.u8(),
hdop: r.f32(),
altitude: r.f32(),
geoid_sep: r.f32(),
dgps_age: r.f32(),
dgps_station: r.string(),
nav_status: r.char(),
})
}
}
impl NmeaEncodable for Gns {
const SENTENCE_TYPE: &str = "GNS";
fn encode(&self) -> Vec<String> {
let mut w = FieldWriter::new();
w.string(self.time.as_deref());
w.f64(self.lat);
w.char(self.ns);
w.f64(self.lon);
w.char(self.ew);
w.string(self.mode.as_deref());
w.u8(self.num_sats);
w.f32(self.hdop);
w.f32(self.altitude);
w.f32(self.geoid_sep);
w.f32(self.dgps_age);
w.string(self.dgps_station.as_deref());
w.char(self.nav_status);
w.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse_frame;
#[test]
fn gns_empty() {
let frame = parse_frame("$GPGNS,,,,,,,,,,,,,S*32").expect("valid empty GNS frame");
let gns = Gns::parse(&frame.fields).expect("parse empty GNS");
assert!(gns.time.is_none());
assert!(gns.lat.is_none());
assert!(gns.mode.is_none());
assert!(gns.num_sats.is_none());
assert_eq!(gns.nav_status, Some('S'));
}
#[test]
fn gns_full_signalk() {
let frame =
parse_frame("$GPGNS,111648.00,0235.0379,S,04422.1450,W,ANN,12,0.8,8.5,-22.3,,,S*5D")
.expect("valid GNS frame");
let gns = Gns::parse(&frame.fields).expect("parse GNS");
assert_eq!(gns.time, Some("111648.00".to_string()));
assert!((gns.lat.expect("lat") - 235.0379).abs() < 0.001);
assert_eq!(gns.ns, Some('S'));
assert_eq!(gns.mode, Some("ANN".to_string()));
assert_eq!(gns.num_sats, Some(12));
assert!((gns.hdop.expect("hdop") - 0.8).abs() < 0.01);
assert_eq!(gns.nav_status, Some('S'));
}
#[test]
fn gns_multi_constellation_pynmeagps() {
let frame = parse_frame(
"$GNGNS,103607.00,5327.03942,N,00214.42462,W,AANN,06,5.88,56.0,48.5,,,V*34",
)
.expect("valid GN GNS frame");
let gns = Gns::parse(&frame.fields).expect("parse GN GNS");
assert_eq!(gns.mode, Some("AANN".to_string()));
assert_eq!(gns.num_sats, Some(6));
assert!((gns.hdop.expect("hdop") - 5.88).abs() < 0.01);
assert!((gns.altitude.expect("alt") - 56.0).abs() < 0.1);
assert_eq!(gns.nav_status, Some('V'));
}
#[test]
fn gns_encode_roundtrip() {
let gns = Gns {
time: Some("120000.00".to_string()),
lat: Some(4807.038),
ns: Some('N'),
lon: Some(1131.0),
ew: Some('E'),
mode: Some("AAN".to_string()),
num_sats: Some(10),
hdop: Some(0.9),
altitude: Some(100.5),
geoid_sep: Some(-23.0),
dgps_age: None,
dgps_station: None,
nav_status: Some('S'),
};
let sentence = gns.to_sentence("GP");
let frame = parse_frame(sentence.trim()).expect("re-parse GNS");
let gns2 = Gns::parse(&frame.fields).expect("parse roundtrip GNS");
assert_eq!(gns, gns2);
}
}