nmea_parser/gnss/
gns.rs

1/*
2Copyright 2020 Timo Saarinen, Sebastian Urban
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use super::*;
18
19/// GNS - GNSS fix data
20#[derive(Clone, Debug, PartialEq)]
21pub struct GnsData {
22    /// Navigation system
23    pub source: NavigationSystem,
24
25    /// UTC of position fix
26    pub timestamp: Option<DateTime<Utc>>,
27
28    /// Latitude in degrees
29    pub latitude: Option<f64>,
30
31    /// Longitude in degrees
32    pub longitude: Option<f64>,
33
34    /// GPS mode indicator
35    pub gps_mode: GnsModeIndicator,
36
37    /// GLONASS mode indicator
38    pub glonass_mode: GnsModeIndicator,
39
40    /// Mode indicators for other navigation systems
41    pub other_modes: Vec<GnsModeIndicator>,
42
43    /// Number of satellites in use
44    pub satellite_count: Option<u8>,
45
46    /// Horizontal dilution of position using all the satellites.
47    pub hdop: Option<f64>,
48
49    /// Altitude above mean sea level (metres)
50    pub altitude: Option<f64>,
51
52    /// Height of geoid (mean sea level) above WGS84 ellipsoid
53    pub geoid_separation: Option<f64>,
54
55    /// Age of differential GPS data record, Type 1 or Type 9.
56    pub age_of_dgps: Option<f64>,
57
58    /// Reference station ID, range 0000-4095
59    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/// GNS mode indicator
73#[derive(Clone, Copy, Debug, PartialEq)]
74pub enum GnsModeIndicator {
75    /// Satellite system not used in position fix, or fix not valid
76    Invalid,
77    /// Satellite system used in non-differential mode in position fix
78    Autonomous,
79    /// Satellite system used in differential mode in position fix
80    Differential,
81    /// Satellite system used in precision mode
82    ///
83    /// Precision mode is defined as:
84    /// no deliberate degradation (such as Selective Availability) and
85    /// higher resolution code (P-code) is used to compute position fix.
86    Precise,
87    /// Satellite system used in RTK mode with fixed integers
88    RealTimeKinematic,
89    /// Satellite system used in real time kinematic mode with floating integers
90    RealTimeKinematicFloat,
91    /// Estimated (dead reckoning) mode
92    DeadReckoning,
93    /// Manual input mode
94    ManualInputMode,
95    /// Simulator mode
96    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
134// -------------------------------------------------------------------------------------------------
135
136/// xxGNS: Global Positioning System Fix Data
137pub(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// -------------------------------------------------------------------------------------------------
173
174#[cfg(test)]
175mod test {
176    use super::*;
177
178    #[test]
179    fn test_parse_cpgns() {
180        // General test
181        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                    // The expected result
188                    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        // Empty fields test
218        let mut p = NmeaParser::new();
219        match p.parse_sentence("$GPGNS,123519,,,,,,,,,,,,,*40") {
220            Ok(ps) => {
221                match ps {
222                    // The expected result
223                    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}