use core::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PowerMode {
Active,
Saver,
Critical,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PowerPlan {
active_interval: Duration,
saver_interval: Duration,
critical_interval: Duration,
saver_below: f32,
critical_below: f32,
}
impl PowerPlan {
pub fn new(active: Duration, saver: Duration, critical: Duration) -> Self {
Self {
active_interval: active,
saver_interval: saver,
critical_interval: critical,
saver_below: 0.5,
critical_below: 0.2,
}
}
pub fn thresholds(mut self, saver_below: f32, critical_below: f32) -> Self {
self.saver_below = saver_below;
self.critical_below = critical_below;
self
}
pub fn saver_below(&self) -> f32 {
self.saver_below
}
pub fn critical_below(&self) -> f32 {
self.critical_below
}
pub fn mode(&self, soc: f32) -> PowerMode {
if soc < self.critical_below {
PowerMode::Critical
} else if soc < self.saver_below {
PowerMode::Saver
} else {
PowerMode::Active
}
}
pub fn mode_while_charging(&self, soc: f32, charging: bool) -> PowerMode {
let mode = self.mode(soc);
if charging {
match mode {
PowerMode::Critical => PowerMode::Saver,
PowerMode::Saver | PowerMode::Active => PowerMode::Active,
}
} else {
mode
}
}
pub fn interval_for(&self, mode: PowerMode) -> Duration {
match mode {
PowerMode::Active => self.active_interval,
PowerMode::Saver => self.saver_interval,
PowerMode::Critical => self.critical_interval,
}
}
pub fn interval(&self, soc: f32) -> Duration {
self.interval_for(self.mode(soc))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn plan() -> PowerPlan {
PowerPlan::new(
Duration::from_secs(60),
Duration::from_secs(600),
Duration::from_secs(3600),
)
}
#[test]
fn mode_steps_down_as_charge_falls() {
let plan = plan();
assert_eq!(plan.mode(0.9), PowerMode::Active);
assert_eq!(plan.mode(0.4), PowerMode::Saver);
assert_eq!(plan.mode(0.1), PowerMode::Critical);
}
#[test]
fn thresholds_are_the_lower_bound_of_each_mode() {
let plan = plan();
assert_eq!(plan.mode(0.5), PowerMode::Active);
assert_eq!(plan.mode(0.2), PowerMode::Saver);
}
#[test]
fn interval_follows_the_mode() {
let plan = plan();
assert_eq!(plan.interval(0.9), Duration::from_secs(60));
assert_eq!(plan.interval(0.4), Duration::from_secs(600));
assert_eq!(plan.interval(0.1), Duration::from_secs(3600));
}
#[test]
fn charging_eases_off_by_one_mode() {
let plan = plan();
assert_eq!(plan.mode_while_charging(0.1, true), PowerMode::Saver);
assert_eq!(plan.mode_while_charging(0.4, true), PowerMode::Active);
assert_eq!(plan.mode_while_charging(0.9, true), PowerMode::Active);
assert_eq!(plan.mode_while_charging(0.1, false), PowerMode::Critical);
}
#[test]
fn custom_thresholds_apply() {
let plan = plan().thresholds(0.7, 0.3);
assert_eq!(plan.mode(0.65), PowerMode::Saver);
assert_eq!(plan.mode(0.25), PowerMode::Critical);
}
}