use super::*;
pub(crate) fn handle(
bv: &BitVec,
station: Station,
own_vessel: bool,
) -> Result<ParsedMessage, ParseError> {
Ok(ParsedMessage::VesselDynamicData(VesselDynamicData {
own_vessel: { own_vessel },
station: { station },
ais_type: { AisClass::ClassA },
mmsi: { pick_u64(bv, 8, 30) as u32 },
nav_status: { NavigationStatus::new(pick_u64(bv, 38, 4) as u8) },
rot: {
let raw = pick_i64(bv, 42, 8);
if (-126..0).contains(&raw) {
Some(-((-raw as f64 * 708.0 / 126.0) / 4.733).powi(2))
} else if (0..=126).contains(&raw) {
Some(((raw as f64 * 708.0 / 126.0) / 4.733).powi(2))
} else {
None
}
},
rot_direction: {
let raw = pick_i64(bv, 42, 8);
if raw == -128 {
None
} else if raw <= -2 {
Some(RotDirection::Port)
} else if raw < 2 {
Some(RotDirection::Center)
} else if raw < 128 {
Some(RotDirection::Starboard)
} else {
None
}
},
sog_knots: {
let raw = pick_u64(bv, 50, 10);
if raw < 1023 {
Some((raw as f64) * 0.1)
} else {
None
}
},
high_position_accuracy: pick_u64(bv, 60, 1) != 0,
latitude: {
let lat_raw = pick_i64(bv, 89, 27) as i32;
if lat_raw != 0x3412140 {
Some((lat_raw as f64) / 600000.0)
} else {
None
}
},
longitude: {
let lon_raw = pick_i64(bv, 61, 28) as i32;
if lon_raw != 0x6791AC0 {
Some((lon_raw as f64) / 600000.0)
} else {
None
}
},
cog: {
let cog_raw = pick_u64(bv, 116, 12);
if cog_raw != 0xE10 {
Some(cog_raw as f64 * 0.1)
} else {
None
}
},
heading_true: {
let th_raw = pick_u64(bv, 128, 9);
if th_raw != 511 {
Some(th_raw as f64)
} else {
None
}
},
timestamp_seconds: pick_u64(bv, 137, 6) as u8,
positioning_system_meta: {
let sec_raw = pick_u64(bv, 137, 6) as u16;
match sec_raw {
60 => None,
61 => Some(PositioningSystemMeta::ManualInputMode),
62 => Some(PositioningSystemMeta::DeadReckoningMode),
63 => Some(PositioningSystemMeta::Inoperative),
_ => Some(PositioningSystemMeta::Operative),
}
},
current_gnss_position: { None },
special_manoeuvre: {
let raw = pick_u64(bv, 143, 2);
match raw {
0 => None,
1 => Some(true),
2 => Some(true),
_ => {
warn!("Unrecognized Maneuver Indicator value: {}", raw);
None
}
}
},
raim_flag: pick_u64(bv, 148, 1) != 0,
class_b_unit_flag: None,
class_b_display: None,
class_b_dsc: None,
class_b_band_flag: None,
class_b_msg22_flag: None,
class_b_mode_flag: None,
class_b_css_flag: None,
radio_status: { Some(pick_u64(bv, 149, 19) as u32) },
}))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_vdm_type1() {
let mut p = NmeaParser::new();
match p.parse_sentence("!AIVDM,1,1,,A,15RTgt0PAso;90TKcjM8h6g208CQ,0*4A") {
Ok(ps) => {
match ps {
ParsedMessage::VesselDynamicData(vdd) => {
assert_eq!(vdd.mmsi, 371798000);
assert_eq!(vdd.nav_status, NavigationStatus::UnderWayUsingEngine);
assert_eq!(vdd.rot, None);
assert_eq!(vdd.rot_direction, Some(RotDirection::Port));
assert_eq!(vdd.sog_knots, Some(12.3));
assert!(vdd.high_position_accuracy);
assert_eq!((vdd.latitude.unwrap_or(0.0) * 10.0).round() as i32, 484); assert_eq!((vdd.longitude.unwrap_or(0.0) * 10.0).round() as i32, -1234); assert_eq!(vdd.cog, Some(224.0));
assert_eq!(vdd.heading_true, Some(215.0));
assert_eq!(vdd.timestamp_seconds, 33);
assert_eq!(
vdd.positioning_system_meta,
Some(PositioningSystemMeta::Operative)
);
assert_eq!(vdd.special_manoeuvre, None);
assert!(!vdd.raim_flag);
}
ParsedMessage::Incomplete => {
assert!(false);
}
_ => {
assert!(false);
}
}
}
Err(e) => {
assert_eq!(e.to_string(), "OK");
}
}
}
#[test]
fn test_parse_vdm_type2() {
let mut p = NmeaParser::new();
match p.parse_sentence("!AIVDM,1,1,,A,16SteH0P00Jt63hHaa6SagvJ087r,0*42") {
Ok(ps) => {
match ps {
ParsedMessage::VesselDynamicData(vdd) => {
assert_eq!(vdd.mmsi, 440348000);
assert_eq!(vdd.nav_status, NavigationStatus::UnderWayUsingEngine);
assert_eq!(vdd.rot, None);
assert_eq!(vdd.rot_direction, None);
assert_eq!(vdd.sog_knots, Some(0.0));
assert!(!vdd.high_position_accuracy);
assert_eq!((vdd.latitude.unwrap_or(0.0) * 10.0).round() as i32, 431); assert_eq!((vdd.longitude.unwrap_or(0.0) * 10.0).round() as i32, -708); assert_eq!(vdd.cog, Some(93.4));
assert_eq!(vdd.heading_true, None);
assert_eq!(vdd.timestamp_seconds, 13);
assert_eq!(
vdd.positioning_system_meta,
Some(PositioningSystemMeta::Operative)
);
assert_eq!(vdd.special_manoeuvre, None);
assert!(!vdd.raim_flag);
}
ParsedMessage::Incomplete => {
assert!(false);
}
_ => {
assert!(false);
}
}
}
Err(e) => {
assert_eq!(e.to_string(), "OK");
}
}
}
#[test]
fn test_parse_vdm_type3() {
let mut p = NmeaParser::new();
match p.parse_sentence("!AIVDM,1,1,,A,38Id705000rRVJhE7cl9n;160000,0*40") {
Ok(ps) => {
match ps {
ParsedMessage::VesselDynamicData(vdd) => {
assert_eq!(vdd.mmsi, 563808000);
assert_eq!(vdd.nav_status, NavigationStatus::Moored);
assert_eq!(vdd.rot, Some(0.0));
assert_eq!(vdd.rot_direction, Some(RotDirection::Center));
assert_eq!(vdd.sog_knots, Some(0.0));
assert!(vdd.high_position_accuracy);
assert::close(vdd.latitude.unwrap_or(0.0), 36.91, 0.01);
assert::close(vdd.longitude.unwrap_or(0.0), -76.33, 0.01);
assert_eq!(vdd.cog, Some(252.0));
assert_eq!(vdd.heading_true, Some(352.0));
assert_eq!(vdd.timestamp_seconds, 35);
assert_eq!(
vdd.positioning_system_meta,
Some(PositioningSystemMeta::Operative)
);
assert_eq!(vdd.special_manoeuvre, None);
assert!(!vdd.raim_flag);
}
ParsedMessage::Incomplete => {
assert!(false);
}
_ => {
assert!(false);
}
}
}
Err(e) => {
assert_eq!(e.to_string(), "OK");
}
}
}
}