use core::fmt;
use kinavis_kernel::angle::{Deviation, MagneticCourse, TrueCourse, Variation};
use kinavis_kernel::math;
use kinavis_kernel::position::Position;
use kinavis_kernel::units::{Distance, Speed};
use crate::error::Nmea2000Error;
use crate::fields::{direction, distance, position, resolution, speed, value_of, Fields};
use crate::frame::Payload;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Referenced {
True(TrueCourse),
Magnetic(MagneticCourse),
}
impl Referenced {
#[must_use]
pub fn degrees(&self) -> f64 {
match self {
Self::True(course) => course.degrees(),
Self::Magnetic(course) => course.degrees(),
}
}
#[must_use]
pub const fn as_true(&self) -> Option<TrueCourse> {
match self {
Self::True(course) => Some(*course),
Self::Magnetic(_) => None,
}
}
#[must_use]
pub const fn as_magnetic(&self) -> Option<MagneticCourse> {
match self {
Self::Magnetic(course) => Some(*course),
Self::True(_) => None,
}
}
pub(crate) fn from_fields(reference: u32, angle: Option<u64>) -> Option<Self> {
match reference {
0 => direction(angle).map(Self::True),
1 => direction(angle).map(Self::Magnetic),
_ => None,
}
}
}
impl fmt::Display for Referenced {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let precision = f.precision().unwrap_or(1);
match self {
Self::True(course) => write!(f, "{:.*}°T", precision, course.degrees()),
Self::Magnetic(course) => write!(f, "{:.*}°M", precision, course.degrees()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PositionRapidUpdate {
pub position: Option<Position>,
}
impl PositionRapidUpdate {
const NEEDED: usize = 8;
pub(crate) fn decode(payload: &Payload) -> Result<Self, Nmea2000Error> {
let field = Fields::of(payload, Self::NEEDED)?;
Ok(Self {
position: position(
field.signed(0, 4),
field.signed(4, 4),
resolution::POSITION_DEG,
)?,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CourseAndSpeed {
pub sid: Option<u8>,
pub course: Option<Referenced>,
pub speed: Option<Speed>,
}
impl CourseAndSpeed {
const NEEDED: usize = 8;
pub(crate) fn decode(payload: &Payload) -> Result<Self, Nmea2000Error> {
let field = Fields::of(payload, Self::NEEDED)?;
Ok(Self {
sid: field.u8(0),
course: Referenced::from_fields(field.raw_bits(8, 2), field.unsigned(2, 2)),
speed: speed(field.unsigned(4, 2))?,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VesselHeading {
pub sid: Option<u8>,
pub heading: Option<Referenced>,
pub deviation: Option<Deviation>,
pub variation: Option<Variation>,
}
impl VesselHeading {
const NEEDED: usize = 8;
pub(crate) fn decode(payload: &Payload) -> Result<Self, Nmea2000Error> {
let field = Fields::of(payload, Self::NEEDED)?;
Ok(Self {
sid: field.u8(0),
heading: Referenced::from_fields(field.raw_bits(56, 2), field.unsigned(1, 2)),
deviation: field
.signed(3, 2)
.map(|value| {
Deviation::new(small_angle_degrees(value)).map_err(value_of("deviation"))
})
.transpose()?,
variation: field
.signed(5, 2)
.map(|value| {
Variation::new(small_angle_degrees(value)).map_err(value_of("variation"))
})
.transpose()?,
})
}
}
fn small_angle_degrees(field: i64) -> f64 {
#[allow(clippy::cast_precision_loss)]
math::to_degrees(field as f64 * resolution::ANGLE_RAD)
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WaterDepth {
pub sid: Option<u8>,
pub depth: Option<Distance>,
pub offset: Option<Distance>,
pub range: Option<Distance>,
}
impl WaterDepth {
const NEEDED: usize = 8;
pub(crate) fn decode(payload: &Payload) -> Result<Self, Nmea2000Error> {
let field = Fields::of(payload, Self::NEEDED)?;
Ok(Self {
sid: field.u8(0),
depth: distance(
field.unsigned(1, 4).and_then(|v| i64::try_from(v).ok()),
resolution::CENTIMETRE_M,
"depth",
)?,
offset: distance(
field.signed(5, 2),
resolution::MILLIMETRE_M,
"transducer offset",
)?,
range: distance(
field.u8(7).map(i64::from),
resolution::DEPTH_RANGE_M,
"range",
)?,
})
}
#[must_use]
pub fn depth_from_reference(&self) -> Option<Distance> {
Some(self.depth? + self.offset?)
}
}