use crate::firmware::ULTRASOUND_PERIOD_COUNT_BITS;
const PERIOD: u16 = 1 << ULTRASOUND_PERIOD_COUNT_BITS;
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
enum PulseWidthInner {
Duty(f32),
Raw(u16),
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct PulseWidth {
inner: PulseWidthInner,
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum PulseWidthError {
PulseWidthOutOfRange(u16),
DutyRatioOutOfRange(f32),
}
impl core::error::Error for PulseWidthError {}
impl core::fmt::Display for PulseWidthError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PulseWidthError::PulseWidthOutOfRange(pulse_width) => {
write!(
f,
"Pulse width ({}) is out of range [0, {})",
pulse_width, PERIOD
)
}
PulseWidthError::DutyRatioOutOfRange(duty) => {
write!(f, "Duty ratio ({}) is out of range [0, 1)", duty)
}
}
}
}
impl PulseWidth {
#[must_use]
pub const fn new(pulse_width: u16) -> Self {
Self {
inner: PulseWidthInner::Raw(pulse_width),
}
}
pub fn from_duty(duty: f32) -> Self {
Self {
inner: PulseWidthInner::Duty(duty),
}
}
pub fn pulse_width(self) -> Result<u16, PulseWidthError> {
let pulse_width = match self.inner {
PulseWidthInner::Duty(duty) => {
if !(0.0..1.0).contains(&duty) {
return Err(PulseWidthError::DutyRatioOutOfRange(duty));
}
(duty * PERIOD as f32).round() as u16
}
PulseWidthInner::Raw(raw) => raw,
};
if pulse_width >= PERIOD {
return Err(PulseWidthError::PulseWidthOutOfRange(pulse_width));
}
Ok(pulse_width)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[rstest::rstest]
#[case(Ok(0), 0)]
#[case(Ok(256), 256)]
#[case(Ok(511), 511)]
#[case(Err(PulseWidthError::PulseWidthOutOfRange(512)), 512)]
#[test]
fn pulse_width_new(#[case] expected: Result<u16, PulseWidthError>, #[case] pulse_width: u16) {
assert_eq!(expected, PulseWidth::new(pulse_width).pulse_width());
}
#[rstest::rstest]
#[case(Ok(0), 0.0)]
#[case(Ok(256), 0.5)]
#[case(Ok(511), 511.0 / 512.0)]
#[case(Err(PulseWidthError::DutyRatioOutOfRange(-0.5)), -0.5)]
#[case(Err(PulseWidthError::DutyRatioOutOfRange(1.0)), 1.0)]
#[case(Err(PulseWidthError::DutyRatioOutOfRange(1.5)), 1.5)]
#[test]
fn pulse_width_from_duty(#[case] expected: Result<u16, PulseWidthError>, #[case] duty: f32) {
assert_eq!(expected, PulseWidth::from_duty(duty).pulse_width());
}
#[rstest::rstest]
#[case(
"Pulse width (512) is out of range [0, 512)",
PulseWidthError::PulseWidthOutOfRange(512)
)]
#[case(
"Duty ratio (1.5) is out of range [0, 1)",
PulseWidthError::DutyRatioOutOfRange(1.5)
)]
fn display(#[case] expected: &str, #[case] error: PulseWidthError) {
assert_eq!(expected, format!("{}", error));
}
}