Skip to main content

autocore_std/fb/
blink.rs

1use std::time::{Duration, Instant};
2
3/// Blink Oscillator
4///
5/// A simple oscillator that toggles its output `q` on and off at a fixed
6/// frequency (0.5 seconds on, 0.5 seconds off) while the enable input `en` is `true`.
7///
8/// When `en` is `false`, the output `q` is `false` and the internal timer resets.
9///
10/// # Behavior
11///
12/// - When `en` becomes `true`, `q` immediately becomes `true`.
13/// - Every 500ms thereafter, `q` toggles its state.
14/// - When `en` becomes `false`, `q` immediately becomes `false`.
15///
16/// # Example
17///
18/// ```
19/// use autocore_std::fb::Blink;
20/// use std::time::Duration;
21///
22/// let mut blink = Blink::new();
23///
24/// // Disabled - output is false
25/// assert_eq!(blink.call(false), false);
26///
27/// // Enabled - output is immediately true
28/// assert_eq!(blink.call(true), true);
29///
30/// // ... after 500ms ...
31/// // assert_eq!(blink.call(true), false);
32/// ```
33#[derive(Debug, Clone)]
34pub struct Blink {
35    /// Input: Enable the oscillator
36    pub en: bool,
37    /// Output: Toggles every 500ms while enabled
38    pub q: bool,
39
40    last_toggle: Option<Instant>,
41}
42
43impl Blink {
44    /// Creates a new Blink function block with default values.
45    ///
46    /// # Example
47    ///
48    /// ```
49    /// use autocore_std::fb::Blink;
50    ///
51    /// let blink = Blink::new();
52    /// assert_eq!(blink.q, false);
53    /// ```
54    pub fn new() -> Self {
55        Self {
56            en: false,
57            q: false,
58            last_toggle: None,
59        }
60    }
61
62    /// Executes the blink logic.
63    ///
64    /// Call this method once per control cycle. It uses real elapsed time,
65    /// so the toggling is independent of the scan rate.
66    ///
67    /// # Arguments
68    ///
69    /// * `en` - Enable input: `true` to oscillate, `false` to reset and turn off
70    ///
71    /// # Returns
72    ///
73    /// The current state of the output `q`.
74    pub fn call(&mut self, en: bool) -> bool {
75        self.en = en;
76
77        if !self.en {
78            self.q = false;
79            self.last_toggle = None;
80        } else {
81            let now = Instant::now();
82            match self.last_toggle {
83                None => {
84                    // First cycle enabled: turn on immediately
85                    self.q = true;
86                    self.last_toggle = Some(now);
87                }
88                Some(last) => {
89                    if now.duration_since(last) >= Duration::from_millis(500) {
90                        self.q = !self.q;
91                        
92                        // Add exactly 500ms to avoid drift, instead of resetting to `now`
93                        // (unless we somehow missed multiple cycles, then fallback to now)
94                        let expected_next = last + Duration::from_millis(500);
95                        if now.duration_since(expected_next) > Duration::from_millis(500) {
96                            self.last_toggle = Some(now);
97                        } else {
98                            self.last_toggle = Some(expected_next);
99                        }
100                    }
101                }
102            }
103        }
104        
105        self.q
106    }
107}
108
109impl Default for Blink {
110    fn default() -> Self {
111        Self::new()
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_blink_basic() {
121        let mut blink = Blink::new();
122
123        // Disabled
124        assert_eq!(blink.call(false), false);
125
126        // Enable - goes true immediately
127        assert_eq!(blink.call(true), true);
128
129        // Still true shortly after
130        std::thread::sleep(Duration::from_millis(50));
131        assert_eq!(blink.call(true), true);
132
133        // Wait past 500ms
134        std::thread::sleep(Duration::from_millis(460));
135        assert_eq!(blink.call(true), false);
136
137        // Disable resets it
138        assert_eq!(blink.call(false), false);
139
140        // Re-enable goes true immediately again
141        assert_eq!(blink.call(true), true);
142    }
143}