use kinavis_kernel::angle::TrueCourse;
use kinavis_kernel::event::TargetId;
use kinavis_kernel::position::Position;
use kinavis_kernel::snapshot::GroundTrack;
use kinavis_kernel::units::{RateOfTurn, Speed};
use crate::error::Nmea2000Error;
use crate::fields::{direction, position, resolution, second, speed, value_of, Fields};
use crate::frame::Payload;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum StationClass {
A,
B,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AisPositionReport {
pub class: StationClass,
pub message_id: u8,
pub mmsi: TargetId,
pub position: Option<Position>,
pub accurate: bool,
pub raim: bool,
pub second: Option<u8>,
pub course: Option<TrueCourse>,
pub speed: Option<Speed>,
pub heading: Option<TrueCourse>,
pub turn: Option<RateOfTurn>,
pub status: Option<u8>,
}
impl AisPositionReport {
const CLASS_A_NEEDED: usize = 28;
const CLASS_B_NEEDED: usize = 26;
pub(crate) fn class_a(payload: &Payload) -> Result<Self, Nmea2000Error> {
let field = Fields::of(payload, Self::CLASS_A_NEEDED)?;
let mut report = Self::common(&field, StationClass::A)?;
report.turn = field
.signed(23, 2)
.map(|value| {
#[allow(clippy::cast_precision_loss)]
RateOfTurn::from_radians_per_second(
value as f64 * resolution::RATE_OF_TURN_RAD_PER_S,
)
.map_err(value_of("rate of turn"))
})
.transpose()?;
#[allow(clippy::cast_possible_truncation)]
let status = field.bits(25 * 8, 4).map(|code| code as u8);
report.status = status;
Ok(report)
}
pub(crate) fn class_b(payload: &Payload) -> Result<Self, Nmea2000Error> {
let field = Fields::of(payload, Self::CLASS_B_NEEDED)?;
Self::common(&field, StationClass::B)
}
fn common(field: &Fields<'_>, class: StationClass) -> Result<Self, Nmea2000Error> {
Ok(Self {
class,
#[allow(clippy::cast_possible_truncation)]
message_id: field.raw_bits(0, 6) as u8,
#[allow(clippy::cast_possible_truncation)]
mmsi: TargetId::new(field.unsigned(1, 4).unwrap_or(0) as u32),
position: position(
field.signed(9, 4),
field.signed(5, 4),
resolution::POSITION_DEG,
)?,
accurate: field.flag(13 * 8),
raim: field.flag(13 * 8 + 1),
second: second(field.raw_bits(13 * 8 + 2, 6)),
course: direction(field.unsigned(14, 2)),
speed: speed(field.unsigned(16, 2))?,
heading: direction(field.unsigned(21, 2)),
turn: None,
status: None,
})
}
#[must_use]
pub fn ground_track(&self) -> Option<GroundTrack> {
Some(GroundTrack {
course_over_ground: self.course?,
speed_over_ground: self.speed?,
})
}
}