Skip to main content

autocore_std/fb/
beeper.rs

1use std::time::Duration;
2use super::r_trig::RTrig;
3use super::simple_timer::SimpleTimer;
4
5/// Minimum allowed on/off duration. Values below this are clamped.
6const MIN_DURATION: Duration = Duration::from_millis(50);
7/// Default on-time used when `t_on` is below the minimum.
8const DEFAULT_ON: Duration = Duration::from_millis(750);
9/// Default off-time used when `t_off` is below the minimum.
10const DEFAULT_OFF: Duration = Duration::from_millis(500);
11
12/// Audible Beeper Controller (FB_Beeper)
13///
14/// Controls an audible beeper through a single boolean output. On the rising
15/// edge of `execute`, emits exactly `preset` beeps with configurable on/off
16/// durations. Call cyclically (once per scan).
17///
18/// # Behavior
19///
20/// - Idle until a rising edge is detected on `execute`
21/// - On trigger: emits `preset` beeps, alternating `q` on for `t_on` then
22///   off for `t_off`
23/// - When all beeps are emitted, `done` becomes `true` and `q` stays `false`
24/// - Setting `execute` to `false` at any time resets the FB and silences output
25/// - `t_on` / `t_off` values below 50 ms are clamped to 750 ms / 500 ms
26///
27/// # Example
28///
29/// ```
30/// use autocore_std::fb::Beeper;
31/// use std::time::Duration;
32///
33/// let mut beeper = Beeper::new();
34///
35/// // Idle — not yet triggered
36/// beeper.call(false, 3, Duration::from_millis(100), Duration::from_millis(100));
37/// assert_eq!(beeper.q, false);
38/// assert_eq!(beeper.done, false);
39///
40/// // Rising edge triggers 3 beeps
41/// beeper.call(true, 3, Duration::from_millis(100), Duration::from_millis(100));
42/// // One more call enters the beeping state
43/// beeper.call(true, 3, Duration::from_millis(100), Duration::from_millis(100));
44/// assert_eq!(beeper.q, true); // First beep started
45/// ```
46///
47/// # Timing Diagram (preset = 2)
48///
49/// ```text
50/// execute: ___|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
51///       q: ____|‾‾‾‾‾|_____|‾‾‾‾‾|____________________
52///   count: 0   0     1     1     2
53///    done: ________________________|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
54///              ←t_on→←t_off→←t_on→←t_off→
55/// ```
56///
57/// # Use Cases
58///
59/// - Audible alarms with a fixed number of beeps
60/// - Confirmation tones (single short beep)
61/// - Warning patterns (rapid multi-beep)
62#[derive(Debug, Clone)]
63pub struct Beeper {
64    /// Output: beeper pulse. Connect to the output controlling the beeper.
65    pub q: bool,
66    /// Output: number of beeps emitted so far.
67    pub count: u16,
68    /// Output: `true` when the preset number of beeps has been emitted.
69    pub done: bool,
70
71    state: i32,
72    trigger: RTrig,
73    timer: SimpleTimer,
74}
75
76impl Beeper {
77    /// Creates a new beeper in the idle state.
78    ///
79    /// # Example
80    ///
81    /// ```
82    /// use autocore_std::fb::Beeper;
83    ///
84    /// let beeper = Beeper::new();
85    /// assert_eq!(beeper.q, false);
86    /// assert_eq!(beeper.count, 0);
87    /// assert_eq!(beeper.done, false);
88    /// ```
89    pub fn new() -> Self {
90        Self {
91            q: false,
92            count: 0,
93            done: false,
94            state: 0,
95            trigger: RTrig::new(),
96            timer: SimpleTimer::new(Duration::ZERO),
97        }
98    }
99
100    /// Executes one scan cycle of the beeper logic.
101    ///
102    /// Call this once per control cycle. On the rising edge of `execute`,
103    /// the beeper begins emitting `preset` beeps. Setting `execute` to
104    /// `false` resets the FB immediately (silences output).
105    ///
106    /// # Arguments
107    ///
108    /// * `execute` - Enable; rising edge triggers the beep sequence
109    /// * `preset` - Number of beeps to emit
110    /// * `t_on`   - Duration the beeper stays on per beep (min 50 ms, clamped to 750 ms)
111    /// * `t_off`  - Duration the beeper stays off between beeps (min 50 ms, clamped to 500 ms)
112    ///
113    /// # Example
114    ///
115    /// ```
116    /// use autocore_std::fb::Beeper;
117    /// use std::time::Duration;
118    ///
119    /// let mut beeper = Beeper::new();
120    /// let t_on  = Duration::from_millis(750);
121    /// let t_off = Duration::from_millis(750);
122    ///
123    /// // Call cyclically in your control loop
124    /// # let some_trigger_condition = false;
125    /// beeper.call(some_trigger_condition, 2, t_on, t_off);
126    /// // Read beeper.q to drive the physical output
127    /// ```
128    pub fn call(&mut self, execute: bool, preset: u16, t_on: Duration, t_off: Duration) {
129        self.trigger.call(execute);
130
131        let t_on = if t_on < MIN_DURATION { DEFAULT_ON } else { t_on };
132        let t_off = if t_off < MIN_DURATION { DEFAULT_OFF } else { t_off };
133
134        if !execute {
135            self.state = 0;
136        }
137
138        match self.state {
139            // Init / reset
140            0 => {
141                self.q = false;
142                self.state = 10;
143                self.timer.set_preset(Duration::from_millis(20));
144                self.timer.start();
145            }
146            // Idle — wait for rising edge
147            10 => {
148                if self.trigger.q {
149                    self.state = 20;
150                    self.count = 0;
151                    self.done = false;
152                }
153            }
154            // Check if sequence complete, otherwise start next beep
155            20 => {
156                if self.count == preset {
157                    self.done = true;
158                    self.state = 10;
159                } else {
160                    self.q = true;
161                    self.done = false;
162                    self.timer.set_preset(t_on);
163                    self.timer.start();
164                    self.state = 30;
165                }
166            }
167            // Beep on — wait for on-timer
168            30 => {
169                if self.timer.is_done() {
170                    self.count += 1;
171                    self.q = false;
172                    self.timer.set_preset(t_off);
173                    self.timer.start();
174                    self.state = 40;
175                }
176            }
177            // Beep off — wait for off-timer
178            40 => {
179                if self.timer.is_done() {
180                    self.state = 20;
181                }
182            }
183            _ => {
184                self.state = 0;
185            }
186        }
187    }
188}
189
190impl Default for Beeper {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    /// Helper: drive the beeper through a full sequence and return it.
201    fn run_sequence(preset: u16, t_on: Duration, t_off: Duration) -> Beeper {
202        let mut b = Beeper::new();
203
204        // Initialize (state 0 → 10)
205        b.call(false, preset, t_on, t_off);
206        assert_eq!(b.q, false);
207
208        // Rising edge (state 10 → 20)
209        b.call(true, preset, t_on, t_off);
210        assert_eq!(b.count, 0);
211        assert_eq!(b.done, false);
212
213        for i in 0..preset {
214            // State 20 → 30: Q goes high
215            b.call(true, preset, t_on, t_off);
216            assert!(b.q, "q should be true at start of beep {}", i);
217
218            // Wait for on-timer
219            std::thread::sleep(t_on + Duration::from_millis(15));
220
221            // State 30 → 40: Q goes low, count increments
222            b.call(true, preset, t_on, t_off);
223            assert!(!b.q, "q should be false after beep {}", i);
224            assert_eq!(b.count, i + 1);
225
226            // Wait for off-timer
227            std::thread::sleep(t_off + Duration::from_millis(15));
228
229            // State 40 → 20
230            b.call(true, preset, t_on, t_off);
231        }
232
233        // State 20: count == preset → done
234        b.call(true, preset, t_on, t_off);
235        assert!(b.done);
236        assert!(!b.q);
237        b
238    }
239
240    #[test]
241    fn test_idle_state() {
242        let mut b = Beeper::new();
243        b.call(false, 3, Duration::from_millis(100), Duration::from_millis(100));
244        assert_eq!(b.q, false);
245        assert_eq!(b.done, false);
246        assert_eq!(b.count, 0);
247    }
248
249    #[test]
250    fn test_single_beep() {
251        run_sequence(1, Duration::from_millis(50), Duration::from_millis(50));
252    }
253
254    #[test]
255    fn test_multiple_beeps() {
256        run_sequence(3, Duration::from_millis(50), Duration::from_millis(50));
257    }
258
259    #[test]
260    fn test_execute_false_resets() {
261        let mut b = Beeper::new();
262        let t = Duration::from_millis(50);
263
264        // Init + trigger
265        b.call(false, 2, t, t);
266        b.call(true, 2, t, t);
267        b.call(true, 2, t, t);
268        assert!(b.q); // Beeping
269
270        // Pull execute low — should silence immediately
271        b.call(false, 2, t, t);
272        assert_eq!(b.q, false);
273        assert_eq!(b.done, false);
274    }
275
276    #[test]
277    fn test_zero_preset_completes_immediately() {
278        let mut b = Beeper::new();
279        let t = Duration::from_millis(100);
280
281        // Init
282        b.call(false, 0, t, t);
283        // Rising edge
284        b.call(true, 0, t, t);
285        // State 20: count(0) == preset(0) → done
286        b.call(true, 0, t, t);
287        assert!(b.done);
288        assert_eq!(b.q, false);
289        assert_eq!(b.count, 0);
290    }
291
292    #[test]
293    fn test_minimum_duration_clamping() {
294        let mut b = Beeper::new();
295        let too_short = Duration::from_millis(10);
296
297        // Init + trigger
298        b.call(false, 1, too_short, too_short);
299        b.call(true, 1, too_short, too_short);
300        // Start beep — internally t_on is clamped to 750ms
301        b.call(true, 1, too_short, too_short);
302        assert!(b.q);
303
304        // 50ms is NOT enough (clamped to 750ms)
305        std::thread::sleep(Duration::from_millis(60));
306        b.call(true, 1, too_short, too_short);
307        assert!(b.q, "timer should still be running (clamped to 750ms)");
308    }
309
310    #[test]
311    fn test_retrigger_after_done() {
312        let mut b = run_sequence(1, Duration::from_millis(50), Duration::from_millis(50));
313        assert!(b.done);
314
315        let t = Duration::from_millis(50);
316
317        // Pull execute low then high again for a new rising edge
318        b.call(false, 1, t, t);
319        b.call(true, 1, t, t);
320        assert_eq!(b.count, 0);
321        assert_eq!(b.done, false);
322
323        // Should start beeping again
324        b.call(true, 1, t, t);
325        assert!(b.q);
326    }
327
328    #[test]
329    fn test_default_trait() {
330        let b = Beeper::default();
331        assert_eq!(b.q, false);
332        assert_eq!(b.count, 0);
333        assert_eq!(b.done, false);
334    }
335}