use deku::prelude::*;
use serde::{Deserialize, Serialize};
use super::common::{EpfdType, ShipType};
use super::converters::*;
#[derive(Debug, Clone, PartialEq, DekuRead, Serialize, Deserialize)]
#[deku(endian = "big")]
pub struct StaticAndVoyageData {
#[deku(bits = "6")]
pub msg_type: u8,
#[deku(bits = "2")]
pub repeat: u8,
#[deku(
bits = "30",
map = "|x: u32| -> Result<_, DekuError> { Ok(from_mmsi(x)) }"
)]
pub mmsi: u32,
#[deku(bits = "2")]
pub ais_version: u8,
#[deku(
bits = "30",
map = "|x: u32| -> Result<_, DekuError> { Ok(from_imo(x)) }"
)]
pub imo: Option<u32>,
#[deku(
bits = "42",
map = "|x: u64| -> Result<_, DekuError> { Ok(from_sixbit_ascii(x, 7)) }"
)]
pub callsign: String,
#[deku(
bits = "120",
map = "|x: u128| -> Result<_, DekuError> { Ok(from_sixbit_ascii_120(x, 20)) }"
)]
pub shipname: String,
#[deku(
bits = "8",
map = "|x: u8| -> Result<_, DekuError> { Ok(ShipType::from_bits(x)) }"
)]
pub ship_type: ShipType,
#[deku(bits = "9")]
pub to_bow: u16,
#[deku(bits = "9")]
pub to_stern: u16,
#[deku(bits = "6")]
pub to_port: u8,
#[deku(bits = "6")]
pub to_starboard: u8,
#[deku(
bits = "4",
map = "|x: u8| -> Result<_, DekuError> { Ok(EpfdType::from_bits(x)) }"
)]
pub epfd: EpfdType,
#[deku(
bits = "4",
map = "|x: u8| -> Result<_, DekuError> { Ok(from_month(x)) }"
)]
pub month: Option<u8>,
#[deku(
bits = "5",
map = "|x: u8| -> Result<_, DekuError> { Ok(from_day(x)) }"
)]
pub day: Option<u8>,
#[deku(
bits = "5",
map = "|x: u8| -> Result<_, DekuError> { Ok(from_hour(x)) }"
)]
pub hour: Option<u8>,
#[deku(
bits = "6",
map = "|x: u8| -> Result<_, DekuError> { Ok(from_minute(x)) }"
)]
pub minute: Option<u8>,
#[deku(
bits = "8",
map = "|x: u8| -> Result<_, DekuError> { Ok(from_draught(x)) }"
)]
pub draught: Option<f32>,
#[deku(
bits = "120",
map = "|x: u128| -> Result<_, DekuError> { Ok(from_sixbit_ascii_120(x, 20)) }"
)]
pub destination: String,
#[deku(bits = "1", map = "|x: u8| -> Result<_, DekuError> { Ok(x != 0) }")]
pub dte: bool,
#[deku(bits = "1")]
#[serde(skip)]
pub spare_1: u8,
}
impl StaticAndVoyageData {
pub fn asdict(&self) -> serde_json::Value {
serde_json::json!({
"msg_type": self.msg_type,
"repeat": self.repeat,
"mmsi": self.mmsi,
"ais_version": self.ais_version,
"imo": self.imo,
"callsign": self.callsign,
"shipname": self.shipname,
"ship_type": self.ship_type,
"to_bow": self.to_bow,
"to_stern": self.to_stern,
"to_port": self.to_port,
"to_starboard": self.to_starboard,
"epfd": self.epfd,
"month": self.month,
"day": self.day,
"hour": self.hour,
"minute": self.minute,
"draught": self.draught,
"destination": self.destination,
"dte": self.dte,
})
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decode::nmea::{MessageAssembler, NmeaAisMessage};
fn decode(sentence1: &str, sentence2: &str) -> StaticAndVoyageData {
let nmea_msg1 = NmeaAisMessage::parse(sentence1).unwrap();
let nmea_msg2 = NmeaAisMessage::parse(sentence2).unwrap();
let messages = vec![nmea_msg1, nmea_msg2];
let binary_data = MessageAssembler::assemble_from_iterable(messages).unwrap();
let (_, msg) = StaticAndVoyageData::from_bytes((&binary_data, 0)).unwrap();
msg
}
#[test]
fn test_msg_type_5() {
let msg = decode(
"!AIVDM,2,1,1,A,55?MbV02;H;s<HtKR20EHE:0@T4@Dn2222222216L961O5Gf0NSQEp6ClRp8,0*1C",
"!AIVDM,2,2,1,A,88888888880,2*25",
);
assert_eq!(msg.callsign, "3FOF8");
assert_eq!(msg.shipname, "EVER DIADEM");
assert_eq!(msg.ship_type, ShipType::Cargo);
assert_eq!(msg.to_bow, 225);
assert_eq!(msg.to_stern, 70);
assert_eq!(msg.to_port, 1);
assert_eq!(msg.to_starboard, 31);
assert!((msg.draught.unwrap() - 12.2).abs() < 0.1);
assert_eq!(msg.destination, "NEW YORK");
assert!(!msg.dte);
assert_eq!(msg.epfd, EpfdType::Gps);
}
#[test]
fn test_message_serialization() {
let msg = decode(
"!AIVDM,2,1,1,A,55?MbV02;H;s<HtKR20EHE:0@T4@Dn2222222216L961O5Gf0NSQEp6ClRp8,0*1C",
"!AIVDM,2,2,1,A,88888888880,2*25",
);
let json = msg.to_json().unwrap();
let deserialized: StaticAndVoyageData = serde_json::from_str(&json).unwrap();
assert_eq!(msg, deserialized);
}
#[test]
fn test_msg_type_5_fields() {
let msg = decode(
"!AIVDM,2,1,1,A,55?MbV02;H;s<HtKR20EHE:0@T4@Dn2222222216L961O5Gf0NSQEp6ClRp8,0*1C",
"!AIVDM,2,2,1,A,88888888880,2*25",
);
assert_eq!(msg.msg_type, 5);
assert_eq!(msg.repeat, 0);
assert!(msg.mmsi > 0);
assert!(!msg.callsign.is_empty());
assert!(!msg.shipname.is_empty());
assert!(msg.to_json().is_ok());
}
}