Skip to content

Commit

Permalink
Merge pull request #132 from trkohler/dpt-impl
Browse files Browse the repository at this point in the history
Prepare implementation for DPT (Depth of Water) messages
  • Loading branch information
elpiel authored Oct 12, 2024
2 parents 2e14541 + 864ddde commit 874a36f
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 4 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ GNSS = ["APA", "ALM", "GBS", "GGA", "GLL", "GNS", "GSA", "GST", "GSV", "RMC", "V
waypoint = ["AAM", "BOD", "BWC", "BWW", "WNC", "ZFO", "ZTG"]
maritime = ["waypoint", "water", "radar"]
radar = ["TTM"]
water = ["DBK", "MTW", "VHW"]
water = ["DBK", "DPT", "MTW", "VHW"]
vendor-specific = ["RMZ"]
other = ["HDT", "MDA", "MWV", "TXT", "ZDA"]

Expand Down Expand Up @@ -104,6 +104,10 @@ BWW = []
# feature: water
DBK = []

# DPT - Depth of Water
# feature: water
DPT = []

# GBS - GPS Satellite Fault Detection
# feature: GNSS
GBS = []
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ NMEA Standard Sentences
- BWC
- BWW
- DBK
- DPT
- GBS
- GGA *
- GLL *
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//! - BWC
//! - BWW
//! - DBK
//! - DPT
//! - GBS
//! - GGA *
//! - GLL *
Expand Down
11 changes: 11 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub enum ParseResult {
BWC(BwcData),
BWW(BwwData),
DBK(DbkData),
DPT(DptData),
GBS(GbsData),
GGA(GgaData),
GLL(GllData),
Expand Down Expand Up @@ -170,6 +171,7 @@ impl From<&ParseResult> for SentenceType {
ParseResult::ZTG(_) => SentenceType::ZTG,
ParseResult::PGRMZ(_) => SentenceType::RMZ,
ParseResult::ZDA(_) => SentenceType::ZDA,
ParseResult::DPT(_) => SentenceType::DPT,
ParseResult::Unsupported(sentence_type) => *sentence_type,
}
}
Expand Down Expand Up @@ -457,6 +459,15 @@ pub fn parse_str(sentence_input: &str) -> Result<ParseResult, Error> {
}
}
}
SentenceType::DPT => {
cfg_if! {
if #[cfg(feature = "DPT")] {
parse_dpt(nmea_sentence).map(ParseResult::DPT)
} else {
return Err(Error::DisabledSentence);
}
}
}
sentence_type => Ok(ParseResult::Unsupported(sentence_type)),
}
} else {
Expand Down
1 change: 1 addition & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ impl<'a> Nmea {
| ParseResult::BWW(_)
| ParseResult::BOD(_)
| ParseResult::DBK(_)
| ParseResult::DPT(_)
| ParseResult::GBS(_)
| ParseResult::GST(_)
| ParseResult::AAM(_)
Expand Down
294 changes: 294 additions & 0 deletions src/sentences/dpt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
use nom::bytes::complete::is_not;
use nom::character::complete::char;
use nom::combinator::map_res;
use nom::combinator::opt;
use nom::number::complete::double;
use nom::IResult;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::sentences::utils::parse_float_num;
use crate::sentences::utils::parse_until_end;
use crate::Error;
use crate::ParseResult;
use crate::SentenceType;

/// DPT - Depth of Water
///
/// <https://gpsd.gitlab.io/gpsd/NMEA.html#_dpt_depth_of_water>
/// ```text
/// 1 2 3 4
/// | | | |
/// $--DPT,x.x,x.x,x.x*hh<CR><LF>
/// ```
///
/// Field Number:
///
/// 1. Water depth relative to transducer, meters
/// 2. Offset from transducer, meters positive means distance from transducer to water line negative means distance from transducer to keel
/// 3. Maximum range scale in use (NMEA 3.0 and above)
/// 4. Checksum
///
/// Examples:
/// * `$INDPT,2.3,0.0*46`
/// * `$SDDPT,15.2,0.5*68`
///
/// `$SDDPT` is the sentence identifier (`SD` for the talker ID, `DPT` for Depth)
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DptData {
pub water_depth: Option<f64>,
pub offset: Option<f64>,
pub max_range_scale: Option<f64>,
}

impl From<DptData> for ParseResult {
fn from(value: DptData) -> Self {
ParseResult::DPT(value)
}
}

pub fn parse_dpt(sentence: crate::NmeaSentence) -> Result<DptData, crate::Error> {
if sentence.message_id != crate::SentenceType::DPT {
Err(Error::WrongSentenceHeader {
expected: SentenceType::DPT,
found: sentence.message_id,
})
} else {
match do_parse_dpt(sentence.data) {
Ok((_, data)) => Ok(data),
Err(err) => Err(Error::ParsingError(err)),
}
}
}

fn parse_positive_f64(input: &str) -> IResult<&str, f64> {
let (input, value) = double(input)?;
if value < 0.0 {
Err(nom::Err::Failure(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)))
} else {
Ok((input, value))
}
}

fn take_and_make_f64(input: &str) -> IResult<&str, f64> {
map_res(is_not(","), parse_float_num)(input)
}

fn do_parse_dpt(i: &str) -> IResult<&str, DptData> {
let (i, water_depth) = opt(parse_positive_f64)(i)?;
let (i, _) = char(',')(i)?;
let (i, offset) = opt(parse_positive_f64)(i)?;
let (i, _) = opt(char(','))(i)?;
let (i, max_range_scale) = opt(take_and_make_f64)(i)?;

let (i, leftover) = parse_until_end(i)?;

if !leftover.is_empty() {
return Err(nom::Err::Failure(nom::error::Error::new(
i,
nom::error::ErrorKind::Verify,
)));
}

Ok((
i,
DptData {
water_depth,
offset,
max_range_scale,
},
))
}

#[cfg(test)]
mod tests {

use super::*;

struct TestExpectation(&'static str, DptData);
struct FailedTestExpectation(&'static str);

fn test_check_valid_message(
message: &str,
expected: DptData,
) -> std::result::Result<(), String> {
let result = crate::parse::parse_nmea_sentence(message);
match result {
Ok(sentence) => {
let dpt_data = parse_dpt(sentence);
match dpt_data {
Ok(data) => {
if data != expected {
return Err(format!(
"DPT parse result is different from expectations. Expected: {:?}, got {:?}",
expected, data
));
}
Ok(())
}
Err(_) => Err(format!("Failed to parse DPT sentence: {}", message)),
}
}
Err(_) => Err(format!(
"NMEA sentence is constructed incorrectly: {}",
message
)),
}
}

fn test_invalid_message(message: &str) -> std::result::Result<(), String> {
let result = crate::parse::parse_nmea_sentence(message);
match result {
Ok(sentence) => {
let dpt_data = parse_dpt(sentence);
match dpt_data {
Ok(_) => Err(format!(
"Parsing should have failed for message: {}",
message
)),
Err(_) => Ok(()),
}
}
Err(_) => Ok(()),
}
}

#[test]
fn test_parse_dpt() -> std::result::Result<(), String> {
let correct_dpt_messages: [TestExpectation; 11] = [
TestExpectation(
"$SDDPT,2.4,,*53",
DptData {
water_depth: Some(2.4),
offset: None,
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,15.2,0.5*64",
DptData {
water_depth: Some(15.2),
offset: Some(0.5),
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,15.5,0.5*63",
DptData {
water_depth: Some(15.5),
offset: Some(0.5),
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,15.8,0.5*6E",
DptData {
water_depth: Some(15.8),
offset: Some(0.5),
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,16.1,0.5*64",
DptData {
water_depth: Some(16.1),
offset: Some(0.5),
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,16.4,0.5*61",
DptData {
water_depth: Some(16.4),
offset: Some(0.5),
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,16.7,0.5*62",
DptData {
water_depth: Some(16.7),
offset: Some(0.5),
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,17.0,0.5*64",
DptData {
water_depth: Some(17.0),
offset: Some(0.5),
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,17.3,0.5*67",
DptData {
water_depth: Some(17.3),
offset: Some(0.5),
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,17.9,0.5*6D",
DptData {
water_depth: Some(17.9),
offset: Some(0.5),
max_range_scale: None,
},
),
TestExpectation(
"$SDDPT,18.7,0.5,2.0*6C",
DptData {
water_depth: Some(18.7),
offset: Some(0.5),
max_range_scale: Some(2.0),
},
), // Extra field (NMEA 2.3 DPT has only 2 fields before checksum)
];

let incorrect_dpt_messages: [FailedTestExpectation; 9] = [
FailedTestExpectation("$SDDPT,-12.3,0.5,*6A"),
FailedTestExpectation(
"$SDDPT,ABC,0.5*41", // non-numeric water depth
),
FailedTestExpectation(
"$SDDPT,20.1,XYZ*55", // non-numeric offset
),
FailedTestExpectation(
"$SDDPT,22.3*31", // missing offset
),
FailedTestExpectation(
"$SDDPT,19.8,0.5*ZZ", // Invalid checksum (not hexadecimal)
),
FailedTestExpectation(
"$SDDPT,16.5,0.5,3.0,4.0*6B", // Too many fields
),
FailedTestExpectation(
"$SDDPT,21.0,-1.5*65", // negative offset
),
FailedTestExpectation(
"$SDDPT,17.2 0.5*60", // missing comma
),
FailedTestExpectation(
"$SDDPT,18.3,0.5*XX", // Invalid checksum (not hexadecimal)
),
];

correct_dpt_messages
.iter()
.try_for_each(|test_expectation| {
test_check_valid_message(test_expectation.0, test_expectation.1)
})?;

incorrect_dpt_messages
.iter()
.try_for_each(|test_expectation| test_invalid_message(test_expectation.0))?;

Ok(())
}
}
2 changes: 2 additions & 0 deletions src/sentences/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod bod;
pub mod bwc;
pub mod bww;
pub mod dbk;
pub mod dpt;
pub mod gbs;
pub mod gga;
pub mod gll;
Expand Down Expand Up @@ -43,6 +44,7 @@ pub use {
bwc::{parse_bwc, BwcData},
bww::{parse_bww, BwwData},
dbk::{parse_dbk, DbkData},
dpt::{parse_dpt, DptData},
faa_mode::{FaaMode, FaaModes},
fix_type::FixType,
gbs::{parse_gbs, GbsData},
Expand Down
Loading

0 comments on commit 874a36f

Please sign in to comment.