From a92f0c45f2878866f879d9469b6dddb95e3bdb69 Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 3 Nov 2024 15:50:17 +0100 Subject: [PATCH] Implement logic utilities Implement timers for use in the logic code. --- src/main.rs | 1 + src/util.rs | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 src/util.rs diff --git a/src/main.rs b/src/main.rs index 773ae77..2c8b662 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use process_image as pi; mod display; mod fieldbus; +mod util; // Parameters const CYCLE_TIME: u64 = 5000; diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..ab608f7 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,262 @@ +use profirust::time; + +#[derive(Default, Debug)] +struct BaseTimer { + change: Option, + last: T, +} + +impl BaseTimer { + pub fn new(start: T) -> Self { + Self { + change: None, + last: start, + } + } + + pub fn run(&mut self, now: time::Instant, value: T) { + if self.last != value { + self.change = Some(now); + } + self.last = value; + } + + /// Reset the "last" value without triggering change detection + pub fn reset_value(&mut self, value: T) { + self.last = value; + } + + pub fn timer(&self, now: time::Instant, preset: time::Duration) -> bool { + self.change.map(|tt| now >= tt + preset).unwrap_or(true) + } +} + +pub struct TimerResult { + pub done: bool, + pub timing: bool, +} + +#[derive(Default, Debug)] +pub struct TimerOn { + base: BaseTimer, +} + +impl TimerOn { + pub fn new() -> Self { + Self::default() + } + + pub fn run(&mut self, now: time::Instant, value: bool, preset: time::Duration) -> TimerResult { + self.base.run(now, value); + TimerResult { + done: value && self.base.timer(now, preset), + timing: value && !self.base.timer(now, preset), + } + } +} + +#[derive(Default, Debug)] +pub struct TimerOff { + base: BaseTimer, +} + +impl TimerOff { + pub fn new() -> Self { + Self::default() + } + + pub fn run(&mut self, now: time::Instant, value: bool, preset: time::Duration) -> TimerResult { + self.base.run(now, value); + TimerResult { + done: value || !self.base.timer(now, preset), + timing: !value && !self.base.timer(now, preset), + } + } +} + +#[derive(Default, Debug)] +pub struct PulseTimer { + base: BaseTimer, +} + +impl PulseTimer { + pub fn new() -> Self { + Self::default() + } + + pub fn run(&mut self, now: time::Instant, value: bool, preset: time::Duration) -> TimerResult { + let already_timing = !self.base.timer(now, preset); + + if already_timing || value { + self.base.run(now, true) + } else { + self.base.reset_value(false); + } + + TimerResult { + done: !self.base.timer(now, preset), + timing: !self.base.timer(now, preset), + } + } +} + +pub trait TimeExt { + fn millis(&self) -> time::Duration; + fn secs(&self) -> time::Duration; +} + +impl TimeExt for i32 { + fn millis(&self) -> time::Duration { + time::Duration::from_millis((*self).try_into().unwrap()) + } + + fn secs(&self) -> time::Duration { + time::Duration::from_secs((*self).try_into().unwrap()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const TIMESTEP: time::Duration = time::Duration::from_millis(10); + const PRESET: time::Duration = time::Duration::from_millis(20); + + #[test] + fn timer_on() { + let inp = [0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0]; + let done = [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0]; + let timing = [0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0]; + + let mut timer = TimerOn::new(); + let mut now = time::Instant::now(); + + for (i, ((inp, done), timing)) in inp + .iter() + .map(|i| *i != 0) + .zip(done.iter().map(|i| *i != 0)) + .zip(timing.iter().map(|i| *i != 0)) + .enumerate() + { + let res = timer.run(now, inp, PRESET); + assert_eq!(res.done, done, "`done` mismatch at timestep #{i}"); + assert_eq!(res.timing, timing, "`timing` mismatch at timestep #{i}"); + now += TIMESTEP; + } + } + + #[test] + fn timer_on_start() { + let inp = [1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0]; + let done = [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0]; + let timing = [1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0]; + + let mut timer = TimerOn::new(); + let mut now = time::Instant::now(); + + for (i, ((inp, done), timing)) in inp + .iter() + .map(|i| *i != 0) + .zip(done.iter().map(|i| *i != 0)) + .zip(timing.iter().map(|i| *i != 0)) + .enumerate() + { + let res = timer.run(now, inp, PRESET); + assert_eq!(res.done, done, "`done` mismatch at timestep #{i}"); + assert_eq!(res.timing, timing, "`timing` mismatch at timestep #{i}"); + now += TIMESTEP; + } + } + + #[test] + fn timer_off() { + let inp = [0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0]; + let done = [0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]; + let timing = [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0]; + + let mut timer = TimerOff::new(); + let mut now = time::Instant::now(); + + for (i, ((inp, done), timing)) in inp + .iter() + .map(|i| *i != 0) + .zip(done.iter().map(|i| *i != 0)) + .zip(timing.iter().map(|i| *i != 0)) + .enumerate() + { + let res = timer.run(now, inp, PRESET); + assert_eq!(res.done, done, "`done` mismatch at timestep #{i}"); + assert_eq!(res.timing, timing, "`timing` mismatch at timestep #{i}"); + now += TIMESTEP; + } + } + + #[test] + fn timer_off_start() { + let inp = [1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0]; + let done = [1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]; + let timing = [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0]; + + let mut timer = TimerOff::new(); + let mut now = time::Instant::now(); + + for (i, ((inp, done), timing)) in inp + .iter() + .map(|i| *i != 0) + .zip(done.iter().map(|i| *i != 0)) + .zip(timing.iter().map(|i| *i != 0)) + .enumerate() + { + let res = timer.run(now, inp, PRESET); + assert_eq!(res.done, done, "`done` mismatch at timestep #{i}"); + assert_eq!(res.timing, timing, "`timing` mismatch at timestep #{i}"); + now += TIMESTEP; + } + } + + #[test] + fn timer_pulse() { + let inp = [0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0]; + let done = [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0]; + let timing = done.clone(); + + let mut timer = PulseTimer::new(); + let mut now = time::Instant::now(); + + for (i, ((inp, done), timing)) in inp + .iter() + .map(|i| *i != 0) + .zip(done.iter().map(|i| *i != 0)) + .zip(timing.iter().map(|i| *i != 0)) + .enumerate() + { + let res = timer.run(now, inp, time::Duration::from_millis(30)); + assert_eq!(res.done, done, "`done` mismatch at timestep #{i}"); + assert_eq!(res.timing, timing, "`timing` mismatch at timestep #{i}"); + now += TIMESTEP; + } + } + + #[test] + fn timer_pulse_start() { + let inp = [1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0]; + let done = [1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0]; + let timing = done.clone(); + + let mut timer = PulseTimer::new(); + let mut now = time::Instant::now(); + + for (i, ((inp, done), timing)) in inp + .iter() + .map(|i| *i != 0) + .zip(done.iter().map(|i| *i != 0)) + .zip(timing.iter().map(|i| *i != 0)) + .enumerate() + { + let res = timer.run(now, inp, time::Duration::from_millis(30)); + assert_eq!(res.done, done, "`done` mismatch at timestep #{i}"); + assert_eq!(res.timing, timing, "`timing` mismatch at timestep #{i}"); + now += TIMESTEP; + } + } +}