use deku::prelude::*;
use serde::{Deserialize, Serialize};
use super::common::{EpfdType, NavAid, Timestamp};
use super::converters::*;
fn read_optional_name_ext<R: std::io::Read + std::io::Seek>(
reader: &mut Reader<R>,
) -> Result<Option<String>, DekuError> {
match u128::from_reader_with_ctx(reader, deku::ctx::BitSize(84)) {
Ok(raw_name_ext) => Ok(from_sixbit_ascii_optional(raw_name_ext, 14)),
Err(DekuError::Incomplete(_)) => Ok(None), Err(e) => Err(e), }
}
#[derive(Debug, Clone, PartialEq, DekuRead, Serialize, Deserialize)]
#[deku(endian = "big")]
pub struct AidToNavigationReport {
#[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 = "5",
map = "|x: u8| -> Result<_, DekuError> { Ok(NavAid::from_bits(x)) }"
)]
pub aid_type: NavAid,
#[deku(
bits = "120",
map = "|x: u128| -> Result<_, DekuError> { Ok(from_sixbit_ascii_120(x, 20)) }"
)]
pub name: String,
#[deku(bits = "1", map = "|x: u8| -> Result<_, DekuError> { Ok(x != 0) }")]
pub accuracy: bool,
#[deku(
bits = "28",
map = "|x: u32| -> Result<_, DekuError> { Ok(from_longitude(x)) }"
)]
pub longitude: Option<f64>,
#[deku(
bits = "27",
map = "|x: u32| -> Result<_, DekuError> { Ok(from_latitude(x)) }"
)]
pub latitude: Option<f64>,
#[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 = "6",
map = "|x: u8| -> Result<_, DekuError> { Ok(Timestamp::from_bits(x)) }"
)]
pub second: Timestamp,
#[deku(bits = "1", map = "|x: u8| -> Result<_, DekuError> { Ok(x != 0) }")]
pub off_position: bool,
#[deku(bits = "8")]
pub reserved_1: u8,
#[deku(bits = "1", map = "|x: u8| -> Result<_, DekuError> { Ok(x != 0) }")]
pub raim: bool,
#[deku(bits = "1", map = "|x: u8| -> Result<_, DekuError> { Ok(x != 0) }")]
pub virtual_aid: bool,
#[deku(bits = "1", map = "|x: u8| -> Result<_, DekuError> { Ok(x != 0) }")]
pub assigned: bool,
#[deku(bits = "1")]
#[serde(skip)]
pub spare_1: u8,
#[deku(reader = "read_optional_name_ext(deku::reader)")]
pub name_ext: Option<String>,
}
impl AidToNavigationReport {
pub fn full_name(&self) -> String {
match &self.name_ext {
Some(ext) if !ext.is_empty() => format!("{}{}", self.name, ext),
_ => self.name.clone(),
}
}
pub fn asdict(&self) -> serde_json::Value {
serde_json::json!({
"msg_type": self.msg_type,
"repeat": self.repeat,
"mmsi": self.mmsi,
"aid_type": self.aid_type as u8,
"name": self.name,
"accuracy": self.accuracy,
"longitude": self.longitude,
"latitude": self.latitude,
"to_bow": self.to_bow,
"to_stern": self.to_stern,
"to_port": self.to_port,
"to_starboard": self.to_starboard,
"epfd": self.epfd as u8,
"second": self.second,
"off_position": self.off_position,
"reserved_1": self.reserved_1,
"raim": self.raim,
"virtual_aid": self.virtual_aid,
"assigned": self.assigned,
"name_ext": self.name_ext,
})
}
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) -> AidToNavigationReport {
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) = AidToNavigationReport::from_bytes((&binary_data, 0)).unwrap();
msg
}
#[test]
fn test_msg_type_21() {
let msg = decode(
"!AIVDM,2,1,7,B,E4eHJhPR37q0000000000000000KUOSc=rq4h00000a,0*4A",
"!AIVDM,2,2,7,B,@20,4*54",
);
assert_eq!(msg.msg_type, 21);
assert_eq!(msg.mmsi, 316021442);
assert_eq!(msg.aid_type, NavAid::ReferencePoint);
assert_eq!(msg.name, "DFO2");
assert!(msg.accuracy);
assert!((msg.latitude.unwrap() - 48.65457).abs() < 0.00001);
assert!((msg.longitude.unwrap() - (-123.429155)).abs() < 0.000001);
assert_eq!(msg.to_bow, 0);
assert_eq!(msg.to_stern, 0);
assert_eq!(msg.to_port, 0);
assert_eq!(msg.to_starboard, 0);
assert!(msg.off_position);
assert_eq!(msg.reserved_1, 0);
assert!(msg.raim);
assert!(!msg.virtual_aid);
assert!(!msg.assigned);
assert_eq!(msg.name_ext, None);
assert_eq!(msg.epfd, EpfdType::Gps);
}
#[test]
fn test_message_serialization() {
let msg = decode(
"!AIVDM,2,1,7,B,E4eHJhPR37q0000000000000000KUOSc=rq4h00000a,0*4A",
"!AIVDM,2,2,7,B,@20,4*54",
);
let json = msg.to_json().unwrap();
let deserialized: AidToNavigationReport = serde_json::from_str(&json).unwrap();
assert_eq!(msg, deserialized);
}
#[test]
fn test_full_name() {
let msg = decode(
"!AIVDM,2,1,7,B,E4eHJhPR37q0000000000000000KUOSc=rq4h00000a,0*4A",
"!AIVDM,2,2,7,B,@20,4*54",
);
assert_eq!(msg.full_name(), "DFO2");
}
}