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}