Skip to content

Commit 874a36f

Browse files
authored
Merge pull request #132 from trkohler/dpt-impl
Prepare implementation for DPT (Depth of Water) messages
2 parents 2e14541 + 864ddde commit 874a36f

File tree

9 files changed

+324
-4
lines changed

9 files changed

+324
-4
lines changed

Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ GNSS = ["APA", "ALM", "GBS", "GGA", "GLL", "GNS", "GSA", "GST", "GSV", "RMC", "V
7272
waypoint = ["AAM", "BOD", "BWC", "BWW", "WNC", "ZFO", "ZTG"]
7373
maritime = ["waypoint", "water", "radar"]
7474
radar = ["TTM"]
75-
water = ["DBK", "MTW", "VHW"]
75+
water = ["DBK", "DPT", "MTW", "VHW"]
7676
vendor-specific = ["RMZ"]
7777
other = ["HDT", "MDA", "MWV", "TXT", "ZDA"]
7878

@@ -104,6 +104,10 @@ BWW = []
104104
# feature: water
105105
DBK = []
106106

107+
# DPT - Depth of Water
108+
# feature: water
109+
DPT = []
110+
107111
# GBS - GPS Satellite Fault Detection
108112
# feature: GNSS
109113
GBS = []

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ NMEA Standard Sentences
1818
- BWC
1919
- BWW
2020
- DBK
21+
- DPT
2122
- GBS
2223
- GGA *
2324
- GLL *

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
//! - BWC
1818
//! - BWW
1919
//! - DBK
20+
//! - DPT
2021
//! - GBS
2122
//! - GGA *
2223
//! - GLL *

src/parse.rs

+11
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub enum ParseResult {
114114
BWC(BwcData),
115115
BWW(BwwData),
116116
DBK(DbkData),
117+
DPT(DptData),
117118
GBS(GbsData),
118119
GGA(GgaData),
119120
GLL(GllData),
@@ -170,6 +171,7 @@ impl From<&ParseResult> for SentenceType {
170171
ParseResult::ZTG(_) => SentenceType::ZTG,
171172
ParseResult::PGRMZ(_) => SentenceType::RMZ,
172173
ParseResult::ZDA(_) => SentenceType::ZDA,
174+
ParseResult::DPT(_) => SentenceType::DPT,
173175
ParseResult::Unsupported(sentence_type) => *sentence_type,
174176
}
175177
}
@@ -457,6 +459,15 @@ pub fn parse_str(sentence_input: &str) -> Result<ParseResult, Error> {
457459
}
458460
}
459461
}
462+
SentenceType::DPT => {
463+
cfg_if! {
464+
if #[cfg(feature = "DPT")] {
465+
parse_dpt(nmea_sentence).map(ParseResult::DPT)
466+
} else {
467+
return Err(Error::DisabledSentence);
468+
}
469+
}
470+
}
460471
sentence_type => Ok(ParseResult::Unsupported(sentence_type)),
461472
}
462473
} else {

src/parser.rs

+1
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ impl<'a> Nmea {
373373
| ParseResult::BWW(_)
374374
| ParseResult::BOD(_)
375375
| ParseResult::DBK(_)
376+
| ParseResult::DPT(_)
376377
| ParseResult::GBS(_)
377378
| ParseResult::GST(_)
378379
| ParseResult::AAM(_)

src/sentences/dpt.rs

+294
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
use nom::bytes::complete::is_not;
2+
use nom::character::complete::char;
3+
use nom::combinator::map_res;
4+
use nom::combinator::opt;
5+
use nom::number::complete::double;
6+
use nom::IResult;
7+
#[cfg(feature = "serde")]
8+
use serde::{Deserialize, Serialize};
9+
10+
use crate::sentences::utils::parse_float_num;
11+
use crate::sentences::utils::parse_until_end;
12+
use crate::Error;
13+
use crate::ParseResult;
14+
use crate::SentenceType;
15+
16+
/// DPT - Depth of Water
17+
///
18+
/// <https://gpsd.gitlab.io/gpsd/NMEA.html#_dpt_depth_of_water>
19+
/// ```text
20+
/// 1 2 3 4
21+
/// | | | |
22+
/// $--DPT,x.x,x.x,x.x*hh<CR><LF>
23+
/// ```
24+
///
25+
/// Field Number:
26+
///
27+
/// 1. Water depth relative to transducer, meters
28+
/// 2. Offset from transducer, meters positive means distance from transducer to water line negative means distance from transducer to keel
29+
/// 3. Maximum range scale in use (NMEA 3.0 and above)
30+
/// 4. Checksum
31+
///
32+
/// Examples:
33+
/// * `$INDPT,2.3,0.0*46`
34+
/// * `$SDDPT,15.2,0.5*68`
35+
///
36+
/// `$SDDPT` is the sentence identifier (`SD` for the talker ID, `DPT` for Depth)
37+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38+
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
39+
#[derive(Debug, Clone, Copy, PartialEq)]
40+
pub struct DptData {
41+
pub water_depth: Option<f64>,
42+
pub offset: Option<f64>,
43+
pub max_range_scale: Option<f64>,
44+
}
45+
46+
impl From<DptData> for ParseResult {
47+
fn from(value: DptData) -> Self {
48+
ParseResult::DPT(value)
49+
}
50+
}
51+
52+
pub fn parse_dpt(sentence: crate::NmeaSentence) -> Result<DptData, crate::Error> {
53+
if sentence.message_id != crate::SentenceType::DPT {
54+
Err(Error::WrongSentenceHeader {
55+
expected: SentenceType::DPT,
56+
found: sentence.message_id,
57+
})
58+
} else {
59+
match do_parse_dpt(sentence.data) {
60+
Ok((_, data)) => Ok(data),
61+
Err(err) => Err(Error::ParsingError(err)),
62+
}
63+
}
64+
}
65+
66+
fn parse_positive_f64(input: &str) -> IResult<&str, f64> {
67+
let (input, value) = double(input)?;
68+
if value < 0.0 {
69+
Err(nom::Err::Failure(nom::error::Error::new(
70+
input,
71+
nom::error::ErrorKind::Verify,
72+
)))
73+
} else {
74+
Ok((input, value))
75+
}
76+
}
77+
78+
fn take_and_make_f64(input: &str) -> IResult<&str, f64> {
79+
map_res(is_not(","), parse_float_num)(input)
80+
}
81+
82+
fn do_parse_dpt(i: &str) -> IResult<&str, DptData> {
83+
let (i, water_depth) = opt(parse_positive_f64)(i)?;
84+
let (i, _) = char(',')(i)?;
85+
let (i, offset) = opt(parse_positive_f64)(i)?;
86+
let (i, _) = opt(char(','))(i)?;
87+
let (i, max_range_scale) = opt(take_and_make_f64)(i)?;
88+
89+
let (i, leftover) = parse_until_end(i)?;
90+
91+
if !leftover.is_empty() {
92+
return Err(nom::Err::Failure(nom::error::Error::new(
93+
i,
94+
nom::error::ErrorKind::Verify,
95+
)));
96+
}
97+
98+
Ok((
99+
i,
100+
DptData {
101+
water_depth,
102+
offset,
103+
max_range_scale,
104+
},
105+
))
106+
}
107+
108+
#[cfg(test)]
109+
mod tests {
110+
111+
use super::*;
112+
113+
struct TestExpectation(&'static str, DptData);
114+
struct FailedTestExpectation(&'static str);
115+
116+
fn test_check_valid_message(
117+
message: &str,
118+
expected: DptData,
119+
) -> std::result::Result<(), String> {
120+
let result = crate::parse::parse_nmea_sentence(message);
121+
match result {
122+
Ok(sentence) => {
123+
let dpt_data = parse_dpt(sentence);
124+
match dpt_data {
125+
Ok(data) => {
126+
if data != expected {
127+
return Err(format!(
128+
"DPT parse result is different from expectations. Expected: {:?}, got {:?}",
129+
expected, data
130+
));
131+
}
132+
Ok(())
133+
}
134+
Err(_) => Err(format!("Failed to parse DPT sentence: {}", message)),
135+
}
136+
}
137+
Err(_) => Err(format!(
138+
"NMEA sentence is constructed incorrectly: {}",
139+
message
140+
)),
141+
}
142+
}
143+
144+
fn test_invalid_message(message: &str) -> std::result::Result<(), String> {
145+
let result = crate::parse::parse_nmea_sentence(message);
146+
match result {
147+
Ok(sentence) => {
148+
let dpt_data = parse_dpt(sentence);
149+
match dpt_data {
150+
Ok(_) => Err(format!(
151+
"Parsing should have failed for message: {}",
152+
message
153+
)),
154+
Err(_) => Ok(()),
155+
}
156+
}
157+
Err(_) => Ok(()),
158+
}
159+
}
160+
161+
#[test]
162+
fn test_parse_dpt() -> std::result::Result<(), String> {
163+
let correct_dpt_messages: [TestExpectation; 11] = [
164+
TestExpectation(
165+
"$SDDPT,2.4,,*53",
166+
DptData {
167+
water_depth: Some(2.4),
168+
offset: None,
169+
max_range_scale: None,
170+
},
171+
),
172+
TestExpectation(
173+
"$SDDPT,15.2,0.5*64",
174+
DptData {
175+
water_depth: Some(15.2),
176+
offset: Some(0.5),
177+
max_range_scale: None,
178+
},
179+
),
180+
TestExpectation(
181+
"$SDDPT,15.5,0.5*63",
182+
DptData {
183+
water_depth: Some(15.5),
184+
offset: Some(0.5),
185+
max_range_scale: None,
186+
},
187+
),
188+
TestExpectation(
189+
"$SDDPT,15.8,0.5*6E",
190+
DptData {
191+
water_depth: Some(15.8),
192+
offset: Some(0.5),
193+
max_range_scale: None,
194+
},
195+
),
196+
TestExpectation(
197+
"$SDDPT,16.1,0.5*64",
198+
DptData {
199+
water_depth: Some(16.1),
200+
offset: Some(0.5),
201+
max_range_scale: None,
202+
},
203+
),
204+
TestExpectation(
205+
"$SDDPT,16.4,0.5*61",
206+
DptData {
207+
water_depth: Some(16.4),
208+
offset: Some(0.5),
209+
max_range_scale: None,
210+
},
211+
),
212+
TestExpectation(
213+
"$SDDPT,16.7,0.5*62",
214+
DptData {
215+
water_depth: Some(16.7),
216+
offset: Some(0.5),
217+
max_range_scale: None,
218+
},
219+
),
220+
TestExpectation(
221+
"$SDDPT,17.0,0.5*64",
222+
DptData {
223+
water_depth: Some(17.0),
224+
offset: Some(0.5),
225+
max_range_scale: None,
226+
},
227+
),
228+
TestExpectation(
229+
"$SDDPT,17.3,0.5*67",
230+
DptData {
231+
water_depth: Some(17.3),
232+
offset: Some(0.5),
233+
max_range_scale: None,
234+
},
235+
),
236+
TestExpectation(
237+
"$SDDPT,17.9,0.5*6D",
238+
DptData {
239+
water_depth: Some(17.9),
240+
offset: Some(0.5),
241+
max_range_scale: None,
242+
},
243+
),
244+
TestExpectation(
245+
"$SDDPT,18.7,0.5,2.0*6C",
246+
DptData {
247+
water_depth: Some(18.7),
248+
offset: Some(0.5),
249+
max_range_scale: Some(2.0),
250+
},
251+
), // Extra field (NMEA 2.3 DPT has only 2 fields before checksum)
252+
];
253+
254+
let incorrect_dpt_messages: [FailedTestExpectation; 9] = [
255+
FailedTestExpectation("$SDDPT,-12.3,0.5,*6A"),
256+
FailedTestExpectation(
257+
"$SDDPT,ABC,0.5*41", // non-numeric water depth
258+
),
259+
FailedTestExpectation(
260+
"$SDDPT,20.1,XYZ*55", // non-numeric offset
261+
),
262+
FailedTestExpectation(
263+
"$SDDPT,22.3*31", // missing offset
264+
),
265+
FailedTestExpectation(
266+
"$SDDPT,19.8,0.5*ZZ", // Invalid checksum (not hexadecimal)
267+
),
268+
FailedTestExpectation(
269+
"$SDDPT,16.5,0.5,3.0,4.0*6B", // Too many fields
270+
),
271+
FailedTestExpectation(
272+
"$SDDPT,21.0,-1.5*65", // negative offset
273+
),
274+
FailedTestExpectation(
275+
"$SDDPT,17.2 0.5*60", // missing comma
276+
),
277+
FailedTestExpectation(
278+
"$SDDPT,18.3,0.5*XX", // Invalid checksum (not hexadecimal)
279+
),
280+
];
281+
282+
correct_dpt_messages
283+
.iter()
284+
.try_for_each(|test_expectation| {
285+
test_check_valid_message(test_expectation.0, test_expectation.1)
286+
})?;
287+
288+
incorrect_dpt_messages
289+
.iter()
290+
.try_for_each(|test_expectation| test_invalid_message(test_expectation.0))?;
291+
292+
Ok(())
293+
}
294+
}

src/sentences/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod bod;
77
pub mod bwc;
88
pub mod bww;
99
pub mod dbk;
10+
pub mod dpt;
1011
pub mod gbs;
1112
pub mod gga;
1213
pub mod gll;
@@ -43,6 +44,7 @@ pub use {
4344
bwc::{parse_bwc, BwcData},
4445
bww::{parse_bww, BwwData},
4546
dbk::{parse_dbk, DbkData},
47+
dpt::{parse_dpt, DptData},
4648
faa_mode::{FaaMode, FaaModes},
4749
fix_type::FixType,
4850
gbs::{parse_gbs, GbsData},

0 commit comments

Comments
 (0)