#![allow(dead_code)]
#[macro_use] extern crate log;
extern crate env_logger;
extern crate chrono;
use std::collections::{HashMap};
use bitvec::prelude::*;
use chrono::{DateTime};
use chrono::prelude::*;
pub mod ais;
pub mod gnss;
mod error;
mod util;
use util::*;
pub use error::{ParseError};
#[derive(Clone, Debug, PartialEq)]
pub enum ParsedSentence {
Incomplete,
VesselDynamicData(ais::VesselDynamicData),
VesselStaticData(ais::VesselStaticData),
BaseStationReport(ais::BaseStationReport),
BinaryAddressedMessage(ais::BinaryAddressedMessage),
AidToNavigationReport(ais::AidToNavigationReport),
Gga(gnss::GgaData),
Rmc(gnss::RmcData),
Gsa(gnss::GsaData),
Gsv(Vec<gnss::GsvData>),
Vtg(gnss::VtgData),
Gll(gnss::GllData),
}
pub trait LatLon {
fn latitude(&self) -> Option<f64>;
fn longitude(&self) -> Option<f64>;
}
pub struct NmeaParser {
saved_fragments: HashMap<String, String>,
saved_vsds: HashMap<u32, ais::VesselStaticData>,
}
impl NmeaParser {
pub fn new() -> NmeaParser {
NmeaParser {
saved_fragments: HashMap::new(),
saved_vsds: HashMap::new(),
}
}
fn push_string(&mut self, key: String, value: String) {
self.saved_fragments.insert(key, value);
}
fn pull_string(&mut self, key: String) -> Option<String> {
self.saved_fragments.remove(&key)
}
fn contains_key(&mut self, key: String) -> bool {
self.saved_fragments.contains_key(&key)
}
fn strings_count(&self) -> usize {
self.saved_fragments.len()
}
fn push_vsd(&mut self, mmsi: u32, vsd: ais::VesselStaticData) {
self.saved_vsds.insert(mmsi, vsd);
}
fn pull_vsd(&mut self, mmsi: u32) -> Option<ais::VesselStaticData> {
self.saved_vsds.remove(&mmsi)
}
fn vsds_count(&self) -> usize {
self.saved_vsds.len()
}
pub fn parse_sentence(&mut self, sentence: &str) -> Result<ParsedSentence, ParseError> {
let mut checksum = 0;
let (sentence, checksum_hex_given) = {
if let Some(pos) = sentence.rfind('*') {
(sentence[0..pos].to_string(), sentence[(pos+1)..sentence.len()].to_string())
} else {
debug!("No checksum found for sentence: {}", sentence);
(sentence.to_string(), "".to_string())
}
};
for c in sentence.as_str().chars().skip(1) {
checksum = checksum ^ (c as u8);
}
let checksum_hex_calculated = format!("{:02X?}", checksum);
if checksum_hex_calculated != checksum_hex_given && checksum_hex_given != "" {
return Err(ParseError::CorruptedSentence(
format!("Corrupted NMEA sentence: {:02X?} != {:02X?}",
checksum_hex_calculated, checksum_hex_given)));
}
let mut sentence_type: String = {
if let Some(i) = sentence.find(',') {
sentence[0..i].into()
} else {
return Err(ParseError::InvalidSentence(format!("Invalid NMEA sentence: {}", sentence)));
}
};
let nav_system = {
if &sentence_type[0..1] == "$" {
match &sentence_type[1..3] {
"GN" => Some(gnss::NavigationSystem::Combination),
"GP" => Some(gnss::NavigationSystem::Gps),
"GL" => Some(gnss::NavigationSystem::Glonass),
"GA" => Some(gnss::NavigationSystem::Galileo),
"BD" => Some(gnss::NavigationSystem::Beidou),
"GI" => Some(gnss::NavigationSystem::Navic),
"QZ" => Some(gnss::NavigationSystem::Qzss),
_ => Some(gnss::NavigationSystem::Other),
}
} else {
None
}
};
if nav_system != None {
if sentence_type.len() <= 6 {
sentence_type = format!("${}", &sentence_type[3..6]);
}
}
let station = {
if &sentence_type[0..1] == "!" {
match &sentence_type[1..3] {
"AB" => Some(ais::Station::BaseStation),
"AD" => Some(ais::Station::DependentAisBaseStation),
"AI" => Some(ais::Station::MobileStation),
"AN" => Some(ais::Station::AidToNavigationStation),
"AR" => Some(ais::Station::AisReceivingStation),
"AS" => Some(ais::Station::LimitedBaseStation),
"AT" => Some(ais::Station::AisTransmittingStation),
"AX" => Some(ais::Station::RepeaterStation),
_ => Some(ais::Station::Other),
}
} else {
None
}
};
if station != None {
if sentence_type.len() <= 6 {
sentence_type = format!("!{}", &sentence_type[3..6]);
}
}
match sentence_type.as_str() {
"$GGA" => {
return gnss::gga::handle(sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other));
},
"$RMC" => {
return gnss::rmc::handle(sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other));
},
"$GSA" => {
return gnss::gsa::handle(sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other));
},
"$GSV" => {
return gnss::gsv::handle(sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other),
self);
},
"$VTG" => {
return gnss::vtg::handle(sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other));
},
"$GLL" => {
return gnss::gll::handle(sentence.as_str(),
nav_system.unwrap_or(gnss::NavigationSystem::Other));
},
"$ALM" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"$HDT" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"$TRF" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"$STN" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"$VBW" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"$XTC" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"$XTE" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"$ZDA" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"$BOD" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"$RMA" => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unimplemented NMEA sentence: {}", sentence_type)));
},
"!VDM" | "!VDO" => {
let own_vessel = sentence_type.as_str() == "!VDO";
let mut num = 0;
let mut fragment_count = 0;
let mut fragment_number = 0;
let mut message_id = None;
let mut radio_channel_code = None;
let mut payload_string: String = "".into();
for s in sentence.split(",") {
match num {
1 => {
match s.parse::<u8>() {
Ok(i) => { fragment_count = i; },
Err(_) => {
return Err(ParseError::InvalidSentence(
format!("Failed to parse fragment count: {}", s)));
}
};
},
2 => {
match s.parse::<u8>() {
Ok(i) => { fragment_number = i; },
Err(_) => {
return Err(ParseError::InvalidSentence(
format!("Failed to parse fragment count: {}", s)));
}
};
},
3 => {
message_id = s.parse::<u64>().ok();
},
4 => {
radio_channel_code = Some(s);
},
5 => {
payload_string = s.to_string();
},
6 => {
},
_ => {
}
}
num += 1;
}
let mut bv: Option<BitVec> = None;
if fragment_count == 1 {
bv = parse_payload(&payload_string).ok();
} else if fragment_count == 2 {
if let Some(msg_id) = message_id {
let key1 = make_fragment_key(&sentence_type.to_string(), msg_id, fragment_count,
1, radio_channel_code.unwrap_or(""));
let key2 = make_fragment_key(&sentence_type.to_string(), msg_id, fragment_count,
2, radio_channel_code.unwrap_or(""));
if fragment_number == 1 {
if let Some(p) = self.pull_string(key2.into()) {
let mut payload_string_combined = payload_string;
payload_string_combined.push_str(p.as_str());
bv = parse_payload(&payload_string_combined). ok();
} else {
self.push_string(key1.into(), payload_string);
}
} else if fragment_number == 2 {
if let Some(p) = self.pull_string(key1.into()) {
let mut payload_string_combined = p.clone();
payload_string_combined.push_str(payload_string.as_str());
bv = parse_payload(&payload_string_combined).ok();
} else {
self.push_string(key2.into(), payload_string);
}
} else {
warn!("Unexpected NMEA fragment number: {}/{}", fragment_number, fragment_count);
}
} else {
warn!("NMEA message_id missing from {} than supported 2", sentence_type);
}
} else {
warn!("NMEA sentence fragment count greater ({}) than supported 2", fragment_count);
}
if let Some(bv) = bv {
let message_type = pick_u64(&bv, 0, 6);
match message_type {
1 | 2 | 3 => {
return ais::vdm_t1t2t3::handle(&bv, station.unwrap_or(ais::Station::Other),
own_vessel);
},
4 => {
return ais::vdm_t4::handle(&bv, station.unwrap_or(ais::Station::Other),
own_vessel);
},
5 => {
return ais::vdm_t5::handle(&bv, station.unwrap_or(ais::Station::Other),
own_vessel);
},
6 => {
return ais::vdm_t6::handle(&bv, station.unwrap_or(ais::Station::Other),
own_vessel);
},
7 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
8 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
9 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
10 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
11 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
12 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
13 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
14 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
15 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
16 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
17 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
18 => {
return ais::vdm_t18::handle(&bv, station.unwrap_or(ais::Station::Other),
own_vessel);
},
19 => {
return ais::vdm_t19::handle(&bv, station.unwrap_or(ais::Station::Other),
own_vessel);
},
20 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
21 => {
return ais::vdm_t21::handle(&bv, station.unwrap_or(ais::Station::Other),
own_vessel);
},
22 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
23 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
24 => {
return ais::vdm_t24::handle(&bv, station.unwrap_or(ais::Station::Other),
self, own_vessel);
},
25 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
26 => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
},
27 => {
return ais::vdm_t27::handle(&bv, station.unwrap_or(ais::Station::Other),
own_vessel);
},
_ => {
return Err(ParseError::UnsupportedSentenceType(
format!("Unsupported {} message type: {}",
sentence_type, message_type)));
}
}
} else {
Ok(ParsedSentence::Incomplete)
}
},
_ => {
Err(ParseError::UnsupportedSentenceType(
format!("Unsupported sentence type: {}", sentence_type)))
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_corrupted() {
let mut p = NmeaParser::new();
assert!(p.parse_sentence("!AIVDM,1,1,,A,38Id705000rRVJhE7cl9n;160000,0*41").ok().is_none());
}
#[test]
fn test_parse_missing_checksum() {
let mut p = NmeaParser::new();
assert!(p.parse_sentence("!AIVDM,1,1,,A,38Id705000rRVJhE7cl9n;160000,0").ok().is_some());
}
#[test]
fn test_self() {
let mut p = NmeaParser::new();
p.push_string("a".into(), "b".into());
assert_eq!(p.strings_count(), 1);
p.push_string("c".into(), "d".into());
assert_eq!(p.strings_count(), 2);
p.pull_string("a".into());
assert_eq!(p.strings_count(), 1);
p.pull_string("c".into());
assert_eq!(p.strings_count(), 0);
p.push_vsd(1, Default::default());
assert_eq!(p.vsds_count(), 1);
p.push_vsd(2, Default::default());
assert_eq!(p.vsds_count(), 2);
p.pull_vsd(1);
assert_eq!(p.vsds_count(), 1);
p.pull_vsd(2);
assert_eq!(p.vsds_count(), 0);
}
#[test]
fn test_mmsi_to_country_code_conversion() {
let mut vsd = ais::VesselStaticData::default();
vsd.mmsi = 230992580; assert_eq!(vsd.country().unwrap(), "FI");
vsd.mmsi = 276009860; assert_eq!(vsd.country().unwrap(), "EE");
vsd.mmsi = 265803690; assert_eq!(vsd.country().unwrap(), "SE");
vsd.mmsi = 273353180; assert_eq!(vsd.country().unwrap(), "RU");
vsd.mmsi = 211805060; assert_eq!(vsd.country().unwrap(), "DE");
vsd.mmsi = 257037270; assert_eq!(vsd.country().unwrap(), "NO");
vsd.mmsi = 227232370; assert_eq!(vsd.country().unwrap(), "FR");
vsd.mmsi = 248221000; assert_eq!(vsd.country().unwrap(), "MT");
vsd.mmsi = 374190000; assert_eq!(vsd.country().unwrap(), "PA");
vsd.mmsi = 412511368; assert_eq!(vsd.country().unwrap(), "CN");
vsd.mmsi = 512003200; assert_eq!(vsd.country().unwrap(), "NZ");
vsd.mmsi = 995126020; assert_eq!(vsd.country(), None);
vsd.mmsi = 2300049; assert_eq!(vsd.country(), None);
}
}