nmea_parser/gnss/
rmc.rs

1/*
2Copyright 2020 Timo Saarinen
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*/
16use super::*;
17
18/// RMC - position, velocity, and time (Recommended Minimum sentence C)
19#[derive(Clone, Debug, PartialEq, Serialize)]
20pub struct RmcData {
21    /// Navigation system
22    pub source: NavigationSystem,
23
24    /// Fix datetime based on HHMMSS and DDMMYY
25    #[serde(with = "json_date_time_utc")]
26    pub timestamp: Option<DateTime<Utc>>,
27
28    /// Status: true = active, false = void.
29    pub status_active: Option<bool>,
30
31    /// Latitude in degrees
32    pub latitude: Option<f64>,
33
34    /// Longitude in degrees
35    pub longitude: Option<f64>,
36
37    /// Speed over ground in knots
38    pub sog_knots: Option<f64>,
39
40    /// Track angle in degrees (True)
41    pub bearing: Option<f64>,
42
43    /// Magnetic variation in degrees
44    pub variation: Option<f64>,
45}
46
47impl LatLon for RmcData {
48    fn latitude(&self) -> Option<f64> {
49        self.latitude
50    }
51
52    fn longitude(&self) -> Option<f64> {
53        self.longitude
54    }
55}
56
57// -------------------------------------------------------------------------------------------------
58
59/// xxRMC: Recommended minimum specific GPS/Transit data
60pub(crate) fn handle(
61    sentence: &str,
62    nav_system: NavigationSystem,
63) -> Result<ParsedMessage, ParseError> {
64    let split: Vec<&str> = sentence.split(',').collect();
65
66    Ok(ParsedMessage::Rmc(RmcData {
67        source: nav_system,
68        timestamp: parse_yymmdd_hhmmss(split.get(9).unwrap_or(&""), split.get(1).unwrap_or(&""))
69            .ok(),
70        status_active: {
71            let s = split.get(2).unwrap_or(&"");
72            match *s {
73                "A" => Some(true),
74                "V" => Some(false),
75                "" => None,
76                _ => {
77                    return Err(format!("Invalid RMC navigation receiver status: {}", s).into());
78                }
79            }
80        },
81        latitude: parse_latitude_ddmm_mmm(
82            split.get(3).unwrap_or(&""),
83            split.get(4).unwrap_or(&""),
84        )?,
85        longitude: parse_longitude_dddmm_mmm(
86            split.get(5).unwrap_or(&""),
87            split.get(6).unwrap_or(&""),
88        )?,
89        sog_knots: pick_number_field(&split, 7)?,
90        bearing: pick_number_field(&split, 8)?,
91        variation: {
92            if let Some(val) = pick_number_field::<f64>(&split, 10)? {
93                let side = split.get(11).unwrap_or(&"");
94                match *side {
95                    "E" => Some(val),
96                    "W" => Some(-val),
97                    _ => {
98                        return Err(format!("Invalid RMC variation side: {}", side).into());
99                    }
100                }
101            } else {
102                None
103            }
104        },
105    }))
106}
107
108// -------------------------------------------------------------------------------------------------
109
110#[cfg(test)]
111mod test {
112    use super::*;
113
114    #[test]
115    fn test_parse_cprmc() {
116        // General test
117        let mut p = NmeaParser::new();
118        match p.parse_sentence("$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191120,020.3,E*67")
119        {
120            Ok(ps) => {
121                match ps {
122                    // The expected result
123                    ParsedMessage::Rmc(rmc) => {
124                        assert_eq!(rmc.status_active, Some(true));
125                        assert_eq!(rmc.timestamp, {
126                            Utc.with_ymd_and_hms(2020, 11, 19, 22, 54, 46).single()
127                        });
128                        assert_eq!(rmc.sog_knots.unwrap(), 0.5);
129                        assert::close(rmc.bearing.unwrap_or(0.0), 54.7, 0.1);
130                        assert_eq!(rmc.variation.unwrap(), 20.3);
131                    }
132                    ParsedMessage::Incomplete => {
133                        assert!(false);
134                    }
135                    _ => {
136                        assert!(false);
137                    }
138                }
139            }
140            Err(e) => {
141                assert_eq!(e.to_string(), "OK");
142            }
143        }
144
145        // Empty fields test
146        let mut p = NmeaParser::new();
147        match p.parse_sentence("$GPRMC,225446,A,,,,,,,070809,,*23") {
148            Ok(ps) => {
149                match ps {
150                    // The expected result
151                    ParsedMessage::Rmc(rmc) => {
152                        assert_eq!(rmc.status_active, Some(true));
153                        assert_eq!(rmc.timestamp, {
154                            Utc.with_ymd_and_hms(2009, 8, 7, 22, 54, 46).single()
155                        });
156                        assert_eq!(rmc.sog_knots, None);
157                        assert_eq!(rmc.bearing, None);
158                        assert_eq!(rmc.variation, None);
159                    }
160                    ParsedMessage::Incomplete => {
161                        assert!(false);
162                    }
163                    _ => {
164                        assert!(false);
165                    }
166                }
167            }
168            Err(e) => {
169                assert_eq!(e.to_string(), "OK");
170            }
171        }
172    }
173}