1use super::*;
18
19#[derive(Clone, Debug, PartialEq)]
21pub struct GnsData {
22 pub source: NavigationSystem,
24
25 pub timestamp: Option<DateTime<Utc>>,
27
28 pub latitude: Option<f64>,
30
31 pub longitude: Option<f64>,
33
34 pub gps_mode: GnsModeIndicator,
36
37 pub glonass_mode: GnsModeIndicator,
39
40 pub other_modes: Vec<GnsModeIndicator>,
42
43 pub satellite_count: Option<u8>,
45
46 pub hdop: Option<f64>,
48
49 pub altitude: Option<f64>,
51
52 pub geoid_separation: Option<f64>,
54
55 pub age_of_dgps: Option<f64>,
57
58 pub ref_station_id: Option<u16>,
60}
61
62impl LatLon for GnsData {
63 fn latitude(&self) -> Option<f64> {
64 self.latitude
65 }
66
67 fn longitude(&self) -> Option<f64> {
68 self.longitude
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq)]
74pub enum GnsModeIndicator {
75 Invalid,
77 Autonomous,
79 Differential,
81 Precise,
87 RealTimeKinematic,
89 RealTimeKinematicFloat,
91 DeadReckoning,
93 ManualInputMode,
95 SimulationMode,
97}
98
99impl GnsModeIndicator {
100 pub fn new(a: char) -> GnsModeIndicator {
101 match a {
102 'N' => GnsModeIndicator::Invalid,
103 'A' => GnsModeIndicator::Autonomous,
104 'D' => GnsModeIndicator::Differential,
105 'P' => GnsModeIndicator::Precise,
106 'R' => GnsModeIndicator::RealTimeKinematic,
107 'F' => GnsModeIndicator::RealTimeKinematicFloat,
108 'E' => GnsModeIndicator::DeadReckoning,
109 'M' => GnsModeIndicator::ManualInputMode,
110 'S' => GnsModeIndicator::SimulationMode,
111 _ => GnsModeIndicator::Invalid,
112 }
113 }
114}
115
116impl core::fmt::Display for GnsModeIndicator {
117 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118 match self {
119 GnsModeIndicator::Invalid => write!(f, "invalid"),
120 GnsModeIndicator::Autonomous => write!(f, "autonomous fix"),
121 GnsModeIndicator::Differential => write!(f, "differential fix"),
122 GnsModeIndicator::Precise => write!(f, "precise fix"),
123 GnsModeIndicator::RealTimeKinematic => write!(f, "Real-Time Kinematic"),
124 GnsModeIndicator::RealTimeKinematicFloat => {
125 write!(f, "Real-Time Kinematic (floating point)")
126 }
127 GnsModeIndicator::DeadReckoning => write!(f, "dead reckoning"),
128 GnsModeIndicator::ManualInputMode => write!(f, "manual input mode"),
129 GnsModeIndicator::SimulationMode => write!(f, "simulation mode"),
130 }
131 }
132}
133
134pub(crate) fn handle(
138 sentence: &str,
139 nav_system: NavigationSystem,
140) -> Result<ParsedMessage, ParseError> {
141 let now: DateTime<Utc> = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).single().unwrap();
142 let split: Vec<&str> = sentence.split(',').collect();
143 let modes: Vec<char> = split.get(6).unwrap_or(&"").chars().collect();
144
145 Ok(ParsedMessage::Gns(GnsData {
146 source: nav_system,
147 timestamp: parse_hhmmss(split.get(1).unwrap_or(&""), now).ok(),
148 latitude: parse_latitude_ddmm_mmm(
149 split.get(2).unwrap_or(&""),
150 split.get(3).unwrap_or(&""),
151 )?,
152 longitude: parse_longitude_dddmm_mmm(
153 split.get(4).unwrap_or(&""),
154 split.get(5).unwrap_or(&""),
155 )?,
156 gps_mode: GnsModeIndicator::new(*modes.first().unwrap_or(&' ')),
157 glonass_mode: GnsModeIndicator::new(*modes.get(1).unwrap_or(&' ')),
158 other_modes: modes
159 .into_iter()
160 .skip(2)
161 .map(GnsModeIndicator::new)
162 .collect(),
163 satellite_count: pick_number_field(&split, 7)?,
164 hdop: pick_number_field(&split, 8)?,
165 altitude: pick_number_field(&split, 9)?,
166 geoid_separation: pick_number_field(&split, 10)?,
167 age_of_dgps: pick_number_field(&split, 11)?,
168 ref_station_id: pick_number_field(&split, 12)?,
169 }))
170}
171
172#[cfg(test)]
175mod test {
176 use super::*;
177
178 #[test]
179 fn test_parse_cpgns() {
180 let mut p = NmeaParser::new();
182 match p.parse_sentence(
183 "$GNGNS,090310.00,4806.891632,N,01134.134167,E,AAN,10,1.0,532.4,47.0,,,V*68",
184 ) {
185 Ok(ps) => {
186 match ps {
187 ParsedMessage::Gns(gns) => {
189 assert_eq!(gns.timestamp, {
190 Utc.with_ymd_and_hms(2000, 01, 01, 09, 03, 10).single()
191 });
192 assert::close(gns.latitude.unwrap_or(0.0), 48.114, 0.001);
193 assert::close(gns.longitude.unwrap_or(0.0), 11.569, 0.001);
194 assert_eq!(gns.gps_mode, GnsModeIndicator::Autonomous);
195 assert_eq!(gns.glonass_mode, GnsModeIndicator::Autonomous);
196 assert_eq!(gns.other_modes[0], GnsModeIndicator::Invalid);
197 assert_eq!(gns.satellite_count.unwrap_or(0), 10);
198 assert::close(gns.hdop.unwrap_or(0.0), 0.9, 0.1);
199 assert::close(gns.altitude.unwrap_or(0.0), 532.4, 0.1);
200 assert::close(gns.geoid_separation.unwrap_or(0.0), 47.0, 0.1);
201 assert_eq!(gns.age_of_dgps, None);
202 assert_eq!(gns.ref_station_id, None);
203 }
204 ParsedMessage::Incomplete => {
205 assert!(false);
206 }
207 _ => {
208 assert!(false);
209 }
210 }
211 }
212 Err(e) => {
213 assert_eq!(e.to_string(), "OK");
214 }
215 }
216
217 let mut p = NmeaParser::new();
219 match p.parse_sentence("$GPGNS,123519,,,,,,,,,,,,,*40") {
220 Ok(ps) => {
221 match ps {
222 ParsedMessage::Gns(gns) => {
224 assert_eq!(gns.timestamp, {
225 Utc.with_ymd_and_hms(2000, 1, 1, 12, 35, 19).single()
226 });
227 assert_eq!(gns.latitude, None);
228 assert_eq!(gns.longitude, None);
229 assert_eq!(gns.gps_mode, GnsModeIndicator::Invalid);
230 assert_eq!(gns.glonass_mode, GnsModeIndicator::Invalid);
231 assert!(gns.other_modes.is_empty());
232 assert_eq!(gns.satellite_count, None);
233 assert_eq!(gns.hdop, None);
234 assert_eq!(gns.altitude, None);
235 assert_eq!(gns.geoid_separation, None);
236 assert_eq!(gns.age_of_dgps, None);
237 assert_eq!(gns.ref_station_id, None);
238 }
239 ParsedMessage::Incomplete => {
240 assert!(false);
241 }
242 _ => {
243 assert!(false);
244 }
245 }
246 }
247 Err(e) => {
248 assert_eq!(e.to_string(), "OK");
249 }
250 }
251 }
252}