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}