use core::ops::{Add, Sub};
use crate::sys;
pub struct HeadingData {
magnetic_heading: Heading,
true_heading: Heading,
status: CompassStatus,
declination_valid: bool,
}
impl HeadingData {
pub fn magnetic_heading(&self) -> Heading {
self.magnetic_heading
}
pub fn true_heading(&self) -> Option<Heading> {
self.declination_valid.then_some(self.true_heading)
}
pub fn heading(&self) -> Heading {
self.true_heading().unwrap_or(self.magnetic_heading)
}
pub fn status(&self) -> CompassStatus {
self.status
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Heading {
inner: i32,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct DeltaAngle {
inner: i32,
}
const _: () = assert!(sys::TRIG_MAX_ANGLE < i32::MAX as u32);
const _: () = assert!(sys::TRIG_MAX_ANGLE == 0x10000);
impl Heading {
const FULL_SCALE: u32 = sys::TRIG_MAX_ANGLE;
const HALF_SCALE: i32 = (sys::TRIG_MAX_ANGLE / 2) as i32;
pub fn from_degrees(val: i16) -> Option<Self> {
match val {
0..360 => Some(Self {
inner: i32::from(val) * Heading::FULL_SCALE as i32 / 360,
}),
_ => None,
}
}
pub fn from_raw(val: i32) -> Option<Self> {
match val as u32 {
0..Self::FULL_SCALE => Some(Self { inner: val }),
_ => None,
}
}
pub fn from_raw_wrapping(val: i32) -> Self {
Self {
inner: val.rem_euclid(Self::FULL_SCALE as i32),
}
}
pub fn inner(&self) -> sys::CompassHeading {
self.inner
}
fn wrap_scale(self) -> u32 {
self.inner.rem_euclid(Self::FULL_SCALE as i32) as u32
}
pub fn as_degrees_cw(&self) -> u16 {
(360 - self.as_degrees_ccw()) % 360
}
pub fn as_degrees_ccw(&self) -> u16 {
(self.wrap_scale() * 360 / Self::FULL_SCALE) as u16
}
pub fn cw(&self) -> u16 {
(Self::FULL_SCALE - u32::from(self.ccw())) as u16 }
pub fn ccw(&self) -> u16 {
self.wrap_scale() as u16
}
}
impl DeltaAngle {
pub fn from_degrees(val: i16) -> Option<Self> {
match val {
-180..180 => Some(Self {
inner: i32::from(val) * Heading::FULL_SCALE as i32 / 360,
}),
_ => None,
}
}
pub fn from_raw(val: i32) -> Option<Self> {
let half_scale_range = -Heading::HALF_SCALE..Heading::HALF_SCALE;
if half_scale_range.contains(&val) {
Some(DeltaAngle { inner: val })
} else {
None
}
}
pub fn from_raw_wrapping(val: i32) -> Self {
Self {
inner: (val + Heading::HALF_SCALE).rem_euclid(Heading::FULL_SCALE as i32)
- Heading::HALF_SCALE,
}
}
pub fn inner(&self) -> sys::CompassHeading {
self.inner
}
fn wrap_scale(self) -> i32 {
(self.inner + Heading::HALF_SCALE).rem_euclid(Heading::FULL_SCALE as i32)
- Heading::HALF_SCALE
}
pub fn as_degrees_ccw(&self) -> i16 {
(self.wrap_scale() * 360 / Heading::FULL_SCALE as i32) as i16
}
pub fn as_degrees_cw(&self) -> i16 {
360 - self.as_degrees_ccw()
}
}
impl Add<DeltaAngle> for Heading {
type Output = Heading;
fn add(self, rhs: DeltaAngle) -> Self::Output {
let mut out = Heading {
inner: self.inner + rhs.inner(),
};
out.inner = out.wrap_scale() as i32;
out
}
}
impl Sub<DeltaAngle> for Heading {
type Output = Heading;
fn sub(self, rhs: DeltaAngle) -> Self::Output {
let mut out = Heading {
inner: self.inner - rhs.inner(),
};
out.inner = out.wrap_scale() as i32;
out
}
}
impl Sub<Heading> for Heading {
type Output = DeltaAngle;
fn sub(self, rhs: Heading) -> Self::Output {
let mut out = DeltaAngle {
inner: self.inner - rhs.inner(),
};
out.inner = out.wrap_scale();
out
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompassStatus {
Unavailable,
DataInvalid,
Calibrating,
Calibrated,
}
impl From<sys::CompassHeadingData> for HeadingData {
fn from(value: sys::CompassHeadingData) -> Self {
HeadingData {
magnetic_heading: Heading::from_raw_wrapping(value.magnetic_heading),
true_heading: Heading::from_raw_wrapping(value.true_heading),
status: CompassStatus::from(value.compass_status),
declination_valid: value.is_declination_valid,
}
}
}
impl From<sys::CompassStatus> for CompassStatus {
fn from(value: sys::CompassStatus) -> Self {
match value {
sys::CompassStatus_CompassStatusUnavailable => Self::Unavailable,
sys::CompassStatus_CompassStatusDataInvalid => Self::DataInvalid,
sys::CompassStatus_CompassStatusCalibrating => Self::Calibrating,
sys::CompassStatus_CompassStatusCalibrated => Self::Calibrated,
#[expect(
clippy::panic,
reason = "FFI Boundary: Value outside of known variants"
)]
_other => panic!("Invalid CompassStatus Variant"),
}
}
}
impl From<CompassStatus> for sys::CompassStatus {
fn from(value: CompassStatus) -> Self {
match value {
CompassStatus::Unavailable => sys::CompassStatus_CompassStatusUnavailable,
CompassStatus::DataInvalid => sys::CompassStatus_CompassStatusDataInvalid,
CompassStatus::Calibrating => sys::CompassStatus_CompassStatusCalibrating,
CompassStatus::Calibrated => sys::CompassStatus_CompassStatusCalibrated,
}
}
}