use kinavis_kernel::angle::{Direction, Frame as AngleFrame};
use kinavis_kernel::math;
use kinavis_kernel::position::Position;
use kinavis_kernel::units::{Distance, Speed};
use kinavis_kernel::KernelError;
use crate::error::Nmea2000Error;
use crate::frame::Payload;
pub(crate) mod resolution {
pub(crate) const POSITION_DEG: f64 = 1e-7;
pub(crate) const PRECISE_POSITION_DEG: f64 = 1e-16;
pub(crate) const ALTITUDE_M: f64 = 1e-6;
pub(crate) const ANGLE_RAD: f64 = 1e-4;
pub(crate) const SPEED_M_PER_S: f64 = 0.01;
pub(crate) const CENTIMETRE_M: f64 = 0.01;
pub(crate) const MILLIMETRE_M: f64 = 0.001;
pub(crate) const DEPTH_RANGE_M: f64 = 10.0;
pub(crate) const DOP: f64 = 0.01;
pub(crate) const RATE_OF_TURN_RAD_PER_S: f64 = 3.125e-5;
}
pub(crate) struct Fields<'a> {
payload: &'a Payload,
}
impl<'a> Fields<'a> {
pub(crate) fn of(payload: &'a Payload, needed: usize) -> Result<Self, Nmea2000Error> {
if payload.len() < needed {
return Err(Nmea2000Error::TooShort {
pgn: payload.pgn(),
bytes: payload.len(),
needed,
});
}
Ok(Self { payload })
}
pub(crate) fn u8(&self, offset: usize) -> Option<u8> {
self.payload.u8(offset).filter(|&value| value < 0xFE)
}
pub(crate) fn unsigned(&self, offset: usize, width: usize) -> Option<u64> {
let value = self.payload.unsigned(offset, width)?;
let max = if width >= 8 {
u64::MAX
} else {
(1_u64 << (8 * width.min(7))) - 1
};
(value < max - 1).then_some(value)
}
pub(crate) fn signed(&self, offset: usize, width: usize) -> Option<i64> {
let value = self.payload.signed(offset, width)?;
let max = if width >= 8 {
i64::MAX
} else {
(1_i64 << (8 * width.clamp(1, 7) - 1)) - 1
};
(value < max - 1).then_some(value)
}
pub(crate) fn bits(&self, bit: usize, width: usize) -> Option<u32> {
let value = self.payload.bits(bit, width)?;
let all_ones = u32::MAX >> (32 - width.clamp(1, 32));
(value != all_ones).then_some(value)
}
pub(crate) fn raw_bits(&self, bit: usize, width: usize) -> u32 {
self.payload.bits(bit, width).unwrap_or(0)
}
pub(crate) fn flag(&self, bit: usize) -> bool {
self.payload.bits(bit, 1) == Some(1)
}
}
pub(crate) fn value_of(field: &'static str) -> impl Fn(KernelError) -> Nmea2000Error {
move |error| Nmea2000Error::Value { field, error }
}
pub(crate) fn position(
latitude: Option<i64>,
longitude: Option<i64>,
scale: f64,
) -> Result<Option<Position>, Nmea2000Error> {
let (Some(latitude), Some(longitude)) = (latitude, longitude) else {
return Ok(None);
};
#[allow(clippy::cast_precision_loss)]
Position::from_degrees(latitude as f64 * scale, longitude as f64 * scale)
.map(Some)
.map_err(value_of("position"))
}
pub(crate) fn direction<F: AngleFrame>(field: Option<u64>) -> Option<Direction<F>> {
#[allow(clippy::cast_precision_loss)]
let degrees = math::to_degrees(field? as f64 * resolution::ANGLE_RAD);
Some(Direction::from_degrees_wrapped(degrees))
}
pub(crate) fn speed(field: Option<u64>) -> Result<Option<Speed>, Nmea2000Error> {
let Some(field) = field else {
return Ok(None);
};
#[allow(clippy::cast_precision_loss)]
Speed::from_metres_per_second(field as f64 * resolution::SPEED_M_PER_S)
.map(Some)
.map_err(value_of("speed"))
}
pub(crate) fn distance(
field: Option<i64>,
resolution: f64,
name: &'static str,
) -> Result<Option<Distance>, Nmea2000Error> {
let Some(field) = field else {
return Ok(None);
};
#[allow(clippy::cast_precision_loss)]
Distance::from_metres(field as f64 * resolution)
.map(Some)
.map_err(value_of(name))
}
pub(crate) fn second(field: u32) -> Option<u8> {
#[allow(clippy::cast_possible_truncation)]
(field <= 59).then_some(field as u8)
}