1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crate::{
commands::gga::GGA,
types::{CommandTypes, Error, TalkerIds},
};
/// Parser struct
#[derive(Debug, Clone)]
pub struct Parser {
/// CommandType
pub r#type: CommandTypes,
/// Commands to parse
pub commands: Vec<String>,
/// Talker ID
pub talker_id: TalkerIds,
type_start_collected: bool,
command_type_collected: bool,
}
impl Parser {
/// Parse given line
/// # Examples
/// ```
/// use rust_nmea::{parser, types::{CommandTypes, Time, CardinalDirection, Cordinate, GGAStatus}, commands::gga::GGA};
/// let line = "$GPGGA,161009.00,1122.20418,N,02339.35234,E,1,08,1.09,11.5,M,11.3,M,,*62";
/// let parsed = parser::Parser::parse_line(line);
/// assert_eq!(parsed, Ok(
/// CommandTypes::GGA(GGA {
/// time: Time {
/// hour: 16,
/// minute: 10,
/// second: 9,
/// decimal_seconds: 0,
/// },
/// lat: Cordinate {
/// degree: 112,
/// minute: 2.20418,
/// },
/// northing_indicator: CardinalDirection::North,
/// lon: Cordinate {
/// degree: 23,
/// minute: 39.35234,
/// },
/// easting_indicator: CardinalDirection::East,
/// status: GGAStatus::S2d3D,
/// number_of_satellites: 8,
/// horizontal_dilution_of_position: 1.09,
/// altitude: 11.5,
/// altitude_unit: "M".to_string(),
/// geoid_separation: 11.3,
/// geoid_separation_unit: "M".to_string(),
/// differential_age_of_position: 0.0,
/// differential_reference_station_id: 0,
/// })
/// ));
pub fn parse_line(line: &str) -> Result<CommandTypes, Error> {
let mut parser = Parser {
r#type: CommandTypes::GGA(GGA::default()),
talker_id: TalkerIds::GP,
commands: Vec::new(),
type_start_collected: false,
command_type_collected: false,
};
let mut command = String::new();
let mut commands: Vec<String> = Vec::new();
let checksum = match line.split('*').last() {
Some(e) => e,
None => {
return Err(Error::ParseError("Invalid line".to_string()));
}
};
//Parse hexa decimal checksum to u8
let checksum_u8: u8 = match u8::from_str_radix(checksum, 16) {
Ok(e) => e,
Err(_) => {
return Err(Error::ParseError(format!(
"Invalid line, checksum is invalid \"{}\"",
checksum
)));
}
};
let command_clean = match line.split('$').last() {
Some(e) => e,
None => {
return Err(Error::ParseError("Invalid line".to_string()));
}
};
let command_clean = command_clean.split('*').collect::<Vec<_>>()[0];
//nmea checksum calculation
let mut checksum_calculated = 0;
for c in command_clean.chars() {
checksum_calculated ^= c as u8;
}
if checksum_calculated != checksum_u8 {
return Err(Error::ChecksumError(checksum_u8, checksum_calculated));
}
for char in line.chars() {
command += &char.to_string();
if !parser.type_start_collected {
if command.len() == 3 {
if TalkerIds::is_correct(&command) {
parser.type_start_collected = true;
parser.talker_id = TalkerIds::parse(&command);
command = "".to_string();
} else {
return Err(Error::ParseError(format!(
"Invalid command start \"{}\"",
command
)));
}
}
} else if !parser.command_type_collected {
if command.len() == 4 && char == ',' {
command = command.replace(',', "");
match CommandTypes::from_str(&command) {
Ok(command_type) => {
parser.r#type = command_type;
parser.command_type_collected = true;
command = "".to_string();
}
Err(_) => {
return Err(Error::UnknownCommand(command));
}
}
}
} else if char == ',' {
commands.push(command.replace(',', ""));
command = "".to_string();
} else if char == '*' {
commands.push(command.replace([',', '*'], ""));
command = "".to_string();
break;
}
}
if !command.is_empty() {
commands.push(command);
}
if parser.command_type_collected && parser.type_start_collected {
parser.r#type.parse_commands(commands)
} else {
Err(Error::ParseError("Invalid line".to_string()))
}
}
}