Skip to content

Commit

Permalink
Merge pull request #63 from ALTinners/mda-mwv-parser
Browse files Browse the repository at this point in the history
Adds MWV and MDA parsers
  • Loading branch information
elpiel authored Dec 20, 2022
2 parents 379d7a2 + a50f483 commit 12b6615
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 2 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Supported sentences:
- GNS
- GSA
- GSV
- MDA
- MWV
- RMC
- TXT
- VTG
Expand Down Expand Up @@ -78,7 +80,7 @@ use nmea::Nmea;
fn main() {
let mut nmea = Nmea::default();
let gga = "$GPGGA,092750.000,5321.6802,N,00630.3372,W,1,8,1.03,61.7,M,55.2,M,,*76";

nmea.parse(gga).unwrap();
println!("{}", nmea);
}
Expand Down
4 changes: 4 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ pub enum ParseResult {
GNS(GnsData),
GSA(GsaData),
GSV(GsvData),
MDA(MdaData),
MWV(MwvData),
RMC(RmcData),
TXT(TxtData),
VTG(VtgData),
Expand Down Expand Up @@ -154,6 +156,8 @@ pub fn parse_str(sentence_input: &str) -> Result<ParseResult, Error> {
SentenceType::GLL => parse_gll(nmea_sentence).map(ParseResult::GLL),
SentenceType::TXT => parse_txt(nmea_sentence).map(ParseResult::TXT),
SentenceType::GNS => parse_gns(nmea_sentence).map(ParseResult::GNS),
SentenceType::MDA => parse_mda(nmea_sentence).map(ParseResult::MDA),
SentenceType::MWV => parse_mwv(nmea_sentence).map(ParseResult::MWV),
SentenceType::RMZ => parse_pgrmz(nmea_sentence).map(ParseResult::PGRMZ),
sentence_type => Ok(ParseResult::Unsupported(sentence_type)),
}
Expand Down
4 changes: 3 additions & 1 deletion src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,9 @@ impl<'a> Nmea {
| ParseResult::BOD(_)
| ParseResult::GBS(_)
| ParseResult::AAM(_)
| ParseResult::PGRMZ(_) => return Ok(FixType::Invalid),
| ParseResult::PGRMZ(_)
| ParseResult::MWV(_)
| ParseResult::MDA(_) => return Ok(FixType::Invalid),

ParseResult::Unsupported(_) => {
return Ok(FixType::Invalid);
Expand Down
174 changes: 174 additions & 0 deletions src/sentences/mda.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use nom::{character::complete::char, combinator::opt, number::complete::float, IResult};

use crate::{parse::NmeaSentence, Error, SentenceType};

/// MDA - Meterological Composite
///
/// <https://gpsd.gitlab.io/gpsd/NMEA.html#_mda_meteorological_composite>
///
/// ```text
/// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/// | | | | | | | | | | | | | | | | | | | | |
/// $--MDA,n.nn,I,n.nnn,B,n.n,C,n.C,n.n,n,n.n,C,n.n,T,n.n,M,n.n,N,n.n,M*hh<CR><LF>
/// ```
#[derive(Debug, PartialEq)]
pub struct MdaData {
/// Pressure in inches of mercury
pub pressure_in_hg: Option<f32>,
/// Pressure in bars
pub pressure_bar: Option<f32>,
/// Air temp, deg celsius
pub air_temp_deg: Option<f32>,
/// Water temp, deg celsius
pub water_temp_deg: Option<f32>,
/// Relative humidity, percent
pub rel_humidity: Option<f32>,
/// Absolute humidity, percent
pub abs_humidity: Option<f32>,
/// Dew point, degrees celsius
pub dew_point: Option<f32>,
/// True Wind Direction, NED degrees
pub wind_direction_true: Option<f32>,
/// Magnetic Wind Direction, NED degrees
pub wind_direction_magnetic: Option<f32>,
/// Wind speed knots
pub wind_speed_knots: Option<f32>,
/// Wind speed meters/second
pub wind_speed_ms: Option<f32>,
}

/// # Parse MDA message
///
/// Information from mda:
///
/// NMEA 0183 standard Wind Speed and Angle, in relation to the vessel’s bow/centerline.
/// <https://gpsd.gitlab.io/gpsd/NMEA.html#_mda_meteorological_composite>
///
/// ## Example (Ignore the line break):
/// ```text
/// $WIMDA,29.7544,I,1.0076,B,35.5,C,17.5,C,42.1,30.6,20.6,C,116.4,T,107.7,M,1.2,N,0.6,M*66
///```
///
///
/// 1: 29.7544 Pressure in inches of mercury
/// 2: I
/// 3: 1.0076 Pressure in bars
/// 4: B
/// 5: 35.5 Air temp, deg celsius
/// 6: C
/// 7: 17.5 Water temp, deg celsius
/// 8: C
/// 9: 42.1 Relative humidity, percent
/// 10: 30.6 Absolute humidity, percent
/// 11: 20.6 Dew point, degrees celsius
/// 12: C
/// 13: 116.4 True Wind Direction, NED degrees
/// 14: T
/// 15: 107.7 Magnetic Wind Direction, NED degrees
/// 16: M
/// 17: 1.2 Wind speed knots
/// 18: N
/// 19: 0.6 Wind speed meters/second
/// 20: M
/// 21: *16 Mandatory NMEA checksum
pub fn parse_mda(sentence: NmeaSentence) -> Result<MdaData, Error> {
if sentence.message_id != SentenceType::MDA {
Err(Error::WrongSentenceHeader {
expected: SentenceType::MDA,
found: sentence.message_id,
})
} else {
Ok(do_parse_mda(sentence.data)?.1)
}
}

fn do_parse_mda(i: &str) -> IResult<&str, MdaData> {
let (i, pressure_in_hg) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('I'))(i)?;
let (i, _) = char(',')(i)?;
let (i, pressure_bar) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('B'))(i)?;
let (i, _) = char(',')(i)?;
let (i, air_temp_deg) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('C'))(i)?;
let (i, _) = char(',')(i)?;
let (i, water_temp_deg) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('C'))(i)?;
let (i, _) = char(',')(i)?;
let (i, rel_humidity) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, abs_humidity) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, dew_point) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('C'))(i)?;
let (i, _) = char(',')(i)?;
let (i, wind_direction_true) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('T'))(i)?;
let (i, _) = char(',')(i)?;
let (i, wind_direction_magnetic) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('M'))(i)?;
let (i, _) = char(',')(i)?;
let (i, wind_speed_knots) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('N'))(i)?;
let (i, _) = char(',')(i)?;
let (i, wind_speed_ms) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('M'))(i)?;

Ok((
i,
MdaData {
pressure_in_hg,
pressure_bar,
air_temp_deg,
water_temp_deg,
rel_humidity,
abs_humidity,
dew_point,
wind_direction_true,
wind_direction_magnetic,
wind_speed_knots,
wind_speed_ms,
},
))
}

#[cfg(test)]
mod tests {
use approx::assert_relative_eq;

use super::*;
use crate::parse::parse_nmea_sentence;

#[test]
fn test_parse_mda() {
// Partial sentence from AirMax 150 model weather station
let s = parse_nmea_sentence(
"$WIMDA,29.7544,I,1.0076,B,35.5,C,,,42.1,,20.6,C,116.4,T,107.7,M,1.2,N,0.6,M*66",
)
.unwrap();
assert_eq!(s.checksum, s.calc_checksum());
assert_eq!(s.checksum, 0x66);
let mda_data = parse_mda(s).unwrap();
assert_relative_eq!(29.7544, mda_data.pressure_in_hg.unwrap());
assert_relative_eq!(1.0076, mda_data.pressure_bar.unwrap());
assert_relative_eq!(35.5, mda_data.air_temp_deg.unwrap());
assert!(mda_data.water_temp_deg.is_none());
assert_relative_eq!(42.1, mda_data.rel_humidity.unwrap());
assert!(mda_data.abs_humidity.is_none());
assert_relative_eq!(20.6, mda_data.dew_point.unwrap());
assert_relative_eq!(116.4, mda_data.wind_direction_true.unwrap());
assert_relative_eq!(107.7, mda_data.wind_direction_magnetic.unwrap());
assert_relative_eq!(1.2, mda_data.wind_speed_knots.unwrap());
assert_relative_eq!(0.6, mda_data.wind_speed_ms.unwrap());
}
}
4 changes: 4 additions & 0 deletions src/sentences/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ mod gll;
mod gns;
mod gsa;
mod gsv;
mod mda;
mod mwv;
mod rmc;
mod rmz;
mod txt;
Expand All @@ -32,6 +34,8 @@ pub use {
gnss_type::GnssType,
gsa::{parse_gsa, GsaData},
gsv::{parse_gsv, GsvData},
mda::{parse_mda, MdaData},
mwv::{parse_mwv, MwvData, MwvReference, MwvWindSpeedUnits},
rmc::{parse_rmc, RmcData, RmcStatusOfFix},
rmz::{parse_pgrmz, PgrmzData},
txt::{parse_txt, TxtData},
Expand Down
131 changes: 131 additions & 0 deletions src/sentences/mwv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use nom::{
character::complete::{char, one_of},
combinator::opt,
number::complete::float,
sequence::preceded,
IResult,
};

use crate::{parse::NmeaSentence, Error, SentenceType};

/// MWV - Wind Speed and Angle
///
/// <https://gpsd.gitlab.io/gpsd/NMEA.html#_mwv_wind_speed_and_angle>
///
/// ```text
/// 1 2 3 4 5
/// | | | | |
/// $--MWV,x.x,a,x.x,a*hh<CR><LF>
/// ```
#[derive(Debug, PartialEq)]
pub struct MwvData {
pub wind_direction: Option<f32>,
pub reference: Option<MwvReference>,
pub wind_speed: Option<f32>,
pub wind_speed_units: Option<MwvWindSpeedUnits>,
pub data_valid: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MwvReference {
Relative,
Theoretical,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MwvWindSpeedUnits {
KilometersPerHour,
MetersPerSecond,
Knots,
MilesPerHour,
}

/// # Parse MWV message
///
/// Information from mwv:
///
/// NMEA 0183 standard Wind Speed and Angle, in relation to the vessel’s bow/centerline.
/// <https://gpsd.gitlab.io/gpsd/NMEA.html#_mwv_wind_speed_and_angle>
///
/// ## Example (Ignore the line break):
/// ```text
/// $WIMWV,041.1,R,01.0,N,A*16
///```
///
/// 1: 041.1 Wind direction, cardinal NED degrees
/// 2: R Relative or Theoretical windspeed
/// 3: 01.0 Wind speed
/// 4: N Wind speed units (Knots)
/// 5: A Data is OK
/// 6: *16 Mandatory NMEA checksum
pub fn parse_mwv(sentence: NmeaSentence) -> Result<MwvData, Error> {
if sentence.message_id != SentenceType::MWV {
Err(Error::WrongSentenceHeader {
expected: SentenceType::MWV,
found: sentence.message_id,
})
} else {
Ok(do_parse_mwv(sentence.data)?.1)
}
}

fn do_parse_mwv(i: &str) -> IResult<&str, MwvData> {
let (i, direction) = opt(float)(i)?;
let (i, reference_type) = opt(preceded(char(','), one_of("RT")))(i)?;
let reference_type = reference_type.map(|ch| match ch {
'R' => MwvReference::Relative,
'T' => MwvReference::Theoretical,
_ => unreachable!(),
});
let (i, _) = char(',')(i)?;
let (i, speed) = opt(float)(i)?;
let (i, wind_speed_type) = opt(preceded(char(','), one_of("KMNS")))(i)?;
let wind_speed_type = wind_speed_type.map(|ch| match ch {
'K' => MwvWindSpeedUnits::KilometersPerHour,
'M' => MwvWindSpeedUnits::MetersPerSecond,
'N' => MwvWindSpeedUnits::Knots,
'S' => MwvWindSpeedUnits::MilesPerHour,
_ => unreachable!(),
});
let (i, is_data_valid) = preceded(char(','), one_of("AV"))(i)?;
let is_data_valid = match is_data_valid {
'A' => true,
'V' => false,
_ => unreachable!(),
};

Ok((
i,
MwvData {
wind_direction: direction,
reference: reference_type,
wind_speed: speed,
wind_speed_units: wind_speed_type,
data_valid: is_data_valid,
},
))
}

#[cfg(test)]
mod tests {
use approx::assert_relative_eq;

use super::*;
use crate::parse::parse_nmea_sentence;

#[test]
fn test_parse_mwv() {
let s = parse_nmea_sentence("$WIMWV,041.1,R,01.0,N,A*16").unwrap();
assert_eq!(s.checksum, s.calc_checksum());
assert_eq!(s.checksum, 0x16);
let wimwv_data = parse_mwv(s).unwrap();
assert_relative_eq!(41.1, wimwv_data.wind_direction.unwrap());
assert_eq!(MwvReference::Relative, wimwv_data.reference.unwrap());
assert_relative_eq!(1.0, wimwv_data.wind_speed.unwrap());
assert_eq!(
MwvWindSpeedUnits::Knots,
wimwv_data.wind_speed_units.unwrap()
);
assert!(wimwv_data.data_valid);
}
}
4 changes: 4 additions & 0 deletions tests/all_supported_messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ fn test_all_supported_messages() {
(SentenceType::GSA, "$GPGSA,A,3,23,31,22,16,03,07,,,,,,,1.8,1.1,1.4*3E"),
// GSV
(SentenceType::GSV, "$GPGSV,3,1,12,01,49,196,41,03,71,278,32,06,02,323,27,11,21,196,39*72"),
// MDA
(SentenceType::MDA, "$WIMWV,041.1,R,01.0,N,A*16"),
// MWV
(SentenceType::MWV, "$WIMDA,29.7544,I,1.0076,B,35.5,C,,,42.1,,20.6,C,116.4,T,107.7,M,1.2,N,0.6,M*66"),
// RMC
(SentenceType::RMC, "$GPRMC,225446.33,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E,A*2B"),
// TXT
Expand Down

0 comments on commit 12b6615

Please sign in to comment.