use core::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DutyCycle {
active: Duration,
sleep: Duration,
}
impl DutyCycle {
pub fn new(active: Duration, sleep: Duration) -> Self {
Self { active, sleep }
}
pub fn from_fraction(period: Duration, fraction: f32) -> Self {
let active = period.mul_f32(unit_interval(fraction));
Self {
active,
sleep: period - active,
}
}
pub fn active(&self) -> Duration {
self.active
}
pub fn sleep(&self) -> Duration {
self.sleep
}
pub fn period(&self) -> Duration {
self.active + self.sleep
}
pub fn fraction(&self) -> f32 {
let period = self.period();
if period.is_zero() {
0.0
} else {
self.active.as_secs_f32() / period.as_secs_f32()
}
}
}
#[allow(clippy::manual_clamp)]
fn unit_interval(value: f32) -> f32 {
if value < 0.0 {
0.0
} else if value > 1.0 {
1.0
} else {
value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn period_is_awake_plus_asleep() {
let cycle = DutyCycle::new(Duration::from_secs(2), Duration::from_secs(8));
assert_eq!(cycle.period(), Duration::from_secs(10));
}
#[test]
fn fraction_is_the_awake_share() {
let cycle = DutyCycle::new(Duration::from_secs(2), Duration::from_secs(8));
assert!((cycle.fraction() - 0.2).abs() < 1e-6);
}
#[test]
fn a_zero_period_has_zero_fraction() {
let cycle = DutyCycle::new(Duration::ZERO, Duration::ZERO);
assert_eq!(cycle.fraction(), 0.0);
}
#[test]
fn from_fraction_splits_the_period() {
let cycle = DutyCycle::from_fraction(Duration::from_secs(10), 0.25);
assert!((cycle.active().as_secs_f32() - 2.5).abs() < 1e-3);
assert!((cycle.fraction() - 0.25).abs() < 1e-6);
}
#[test]
fn from_fraction_clamps_out_of_range_values() {
let all_awake = DutyCycle::from_fraction(Duration::from_secs(10), 5.0);
assert_eq!(all_awake.active(), Duration::from_secs(10));
assert_eq!(all_awake.sleep(), Duration::ZERO);
let all_asleep = DutyCycle::from_fraction(Duration::from_secs(10), -1.0);
assert_eq!(all_asleep.active(), Duration::ZERO);
}
}