osal_rs/traits/timer.rs
1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20
21//! Software timer trait for delayed and periodic callbacks.
22//!
23//! Timers execute callback functions in the context of a timer service task,
24//! enabling delayed operations and periodic tasks without dedicated threads.
25//!
26//! # Overview
27//!
28//! Software timers provide a way to execute callback functions at specified
29//! intervals without creating dedicated tasks. All timer callbacks run in
30//! the context of a single timer service daemon task.
31//!
32//! # Timer Types
33//!
34//! - **One-shot**: Expires once after the period elapses
35//! - **Auto-reload (Periodic)**: Automatically restarts after expiring
36//!
37//! # Timer Service Task
38//!
39//! All timer callbacks execute in a dedicated timer service task that:
40//! - Has a configurable priority
41//! - Processes timer commands from a queue
42//! - Executes callbacks sequentially (not in parallel)
43//!
44//! # Important Constraints
45//!
46//! - Timer callbacks should be short and non-blocking
47//! - Callbacks should not call blocking RTOS APIs (may cause deadlock)
48//! - Long callbacks delay other timer expirations
49//! - Use task notifications or queues to defer work to other tasks
50//!
51//! # Accuracy
52//!
53//! Timer accuracy depends on:
54//! - System tick rate (e.g., 1ms for 1000 Hz)
55//! - Timer service task priority
56//! - Duration of other timer callbacks
57//! - System load
58//!
59//! # Examples
60//!
61//! ```
62//! use osal_rs::os::*;
63//! use std::sync::Arc;
64//! use core::time::Duration;
65//!
66//! // One-shot timer
67//! let once = Timer::new_with_to_tick(
68//! "timeout",
69//! Duration::from_millis(50),
70//! false, // Not auto-reload
71//! None,
72//! |_timer, _param| {
73//! println!("Timeout!");
74//! Ok(Arc::new(()))
75//! }
76//! ).unwrap();
77//! once.start(0);
78//!
79//! // Periodic timer
80//! let periodic = Timer::new_with_to_tick(
81//! "heartbeat",
82//! Duration::from_millis(500),
83//! true, // Auto-reload
84//! None,
85//! |_timer, _param| {
86//! println!("Blink!");
87//! Ok(Arc::new(()))
88//! }
89//! ).unwrap();
90//! periodic.start(0);
91//! ```
92
93use core::any::Any;
94
95use alloc::{boxed::Box, sync::Arc};
96
97use crate::os::types::TickType;
98use crate::utils::{OsalRsBool, Result};
99
100/// Type-erased parameter for timer callbacks.
101///
102/// Allows passing arbitrary data to timer callback functions in a type-safe
103/// manner. The parameter is wrapped in an `Arc` for safe sharing and can be
104/// downcast to its original type.
105///
106/// # Thread Safety
107///
108/// The inner type must implement `Any + Send + Sync` since timer callbacks
109/// execute in the timer service task context.
110///
111/// # Examples
112///
113/// ```
114/// use osal_rs::os::*;
115/// use std::sync::Arc;
116///
117/// // Create a parameter
118/// let param: TimerParam = Arc::new(7u32);
119///
120/// // In the timer callback, downcast to access it
121/// let count = param.downcast_ref::<u32>().copied();
122/// assert_eq!(count, Some(7));
123///
124/// // A downcast to the wrong type simply reports `None`.
125/// assert!(param.downcast_ref::<i8>().is_none());
126/// ```
127pub type TimerParam = Arc<dyn Any + Send + Sync>;
128
129/// Timer callback function pointer type.
130///
131/// Callbacks receive the timer handle and optional parameter,
132/// and can return an updated parameter value.
133///
134/// # Parameters
135///
136/// - `Box<dyn Timer>` - Handle to the timer that expired
137/// - `Option<TimerParam>` - Optional parameter passed at creation
138///
139/// # Returns
140///
141/// `Result<TimerParam>` - Updated parameter or error
142///
143/// # Execution Context
144///
145/// Callbacks execute in the timer service task, not ISR context.
146/// They should be short and avoid blocking operations.
147///
148/// # Trait Bounds
149///
150/// The function must be `Send + Sync + 'static` to safely execute
151/// in the timer service task.
152///
153/// # Examples
154///
155/// ```
156/// use osal_rs::os::*;
157/// use std::sync::Arc;
158///
159/// let callback: Box<TimerFnPtr> = Box::new(|_timer, param| {
160/// if let Some(p) = param {
161/// if let Some(count) = p.downcast_ref::<u32>() {
162/// // Timer expired: hand the next invocation an updated count.
163/// return Ok(Arc::new(*count + 1));
164/// }
165/// }
166/// Ok(Arc::new(0u32))
167/// });
168///
169/// // This is what the timer service does on every expiration: it passes the
170/// // expired timer and the current parameter, and keeps whatever comes back.
171/// let timer = Timer::new("counter", 50, true, None, |_t, _p| Ok(Arc::new(()))).unwrap();
172/// let updated = callback(Box::new(timer), Some(Arc::new(41u32))).unwrap();
173///
174/// assert_eq!(updated.downcast_ref::<u32>(), Some(&42));
175/// ```
176pub type TimerFnPtr = dyn Fn(Box<dyn Timer>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + 'static;
177
178/// Software timer for delayed and periodic callbacks.
179///
180/// Timers run callbacks in the timer service task context, not ISR context.
181/// They can be one-shot or auto-reloading (periodic).
182///
183/// # Timer Lifecycle
184///
185/// 1. **Creation**: `Timer::new()` with name, period, auto-reload flag, and callback
186/// 2. **Start**: `start()` begins the timer countdown
187/// 3. **Expiration**: Callback executes when period elapses
188/// 4. **Auto-reload**: If enabled, timer automatically restarts
189/// 5. **Management**: Use `stop()`, `reset()`, `change_period()` to control
190/// 6. **Cleanup**: `delete()` frees resources
191///
192/// # Command Queue
193///
194/// Timer operations (start, stop, etc.) send commands to a queue processed
195/// by the timer service task. The `ticks_to_wait` parameter controls how
196/// long to wait if the queue is full.
197///
198/// # Callback Constraints
199///
200/// - Keep callbacks short (< 1ms ideally)
201/// - Avoid blocking operations (delays, mutex waits, etc.)
202/// - Don't call APIs that might block indefinitely
203/// - Use task notifications or queues to defer work to tasks
204///
205/// # Examples
206///
207/// ## One-shot Timer
208///
209/// ```
210/// use osal_rs::os::*;
211/// use std::sync::Arc;
212/// use std::sync::atomic::{AtomicU32, Ordering};
213/// use core::time::Duration;
214///
215/// static ALARMS: AtomicU32 = AtomicU32::new(0);
216///
217/// let timer = Timer::new_with_to_tick(
218/// "alarm",
219/// Duration::from_millis(20),
220/// false, // One-shot
221/// None,
222/// |_timer, _param| {
223/// ALARMS.fetch_add(1, Ordering::SeqCst);
224/// Ok(Arc::new(()))
225/// }
226/// ).unwrap();
227///
228/// timer.start(0);
229///
230/// // Expires once, then stays quiet however long we wait.
231/// System::delay(120);
232/// assert_eq!(ALARMS.load(Ordering::SeqCst), 1);
233/// ```
234///
235/// ## Periodic Timer
236///
237/// ```
238/// use osal_rs::os::*;
239/// use std::sync::Arc;
240/// use std::sync::atomic::{AtomicU32, Ordering};
241/// use core::time::Duration;
242///
243/// // The parameter is handed to each invocation and replaced by whatever
244/// // that invocation returns, so the count carries across expirations.
245/// let counter: TimerParam = Arc::new(AtomicU32::new(0));
246///
247/// let periodic = Timer::new_with_to_tick(
248/// "counter",
249/// Duration::from_millis(10),
250/// true, // Auto-reload
251/// Some(counter.clone()),
252/// |_timer, param| {
253/// let param = param.unwrap();
254/// if let Some(count) = param.downcast_ref::<AtomicU32>() {
255/// count.fetch_add(1, Ordering::SeqCst);
256/// }
257/// Ok(param)
258/// }
259/// ).unwrap();
260///
261/// periodic.start(0);
262///
263/// // Runs every 10ms until stopped
264/// System::delay(120);
265/// periodic.stop(0);
266///
267/// assert!(counter.downcast_ref::<AtomicU32>().unwrap().load(Ordering::SeqCst) > 1);
268/// ```
269pub trait Timer {
270
271 /// Returns `true` if the underlying OS handle is null, i.e. the mutex
272 /// has not been created yet or has already been deleted.
273 fn is_null(&self) -> bool;
274
275
276 /// Starts or restarts the timer.
277 ///
278 /// If the timer is already running, this command resets it to its full
279 /// period (equivalent to calling `reset()`). If stopped, the timer begins
280 /// counting down from its period.
281 ///
282 /// # Parameters
283 ///
284 /// * `ticks_to_wait` - Maximum ticks to wait if command queue is full:
285 /// - `0`: Return immediately if queue full
286 /// - `n`: Wait up to n ticks
287 /// - `TickType::MAX`: Wait forever
288 ///
289 /// # Returns
290 ///
291 /// * `True` - Command sent successfully to timer service
292 /// * `False` - Failed to send command (queue full, timeout)
293 ///
294 /// # Timing
295 ///
296 /// The timer begins counting after the command is processed by the
297 /// timer service task, not immediately when this function returns.
298 ///
299 /// # Examples
300 ///
301 /// ```
302 /// use osal_rs::os::*;
303 /// use osal_rs::utils::OsalRsBool;
304 /// use std::sync::Arc;
305 /// use std::sync::atomic::{AtomicBool, Ordering};
306 ///
307 /// static FIRED: AtomicBool = AtomicBool::new(false);
308 ///
309 /// let timer = Timer::new("alarm", 20, false, None, |_timer, _param| {
310 /// FIRED.store(true, Ordering::SeqCst);
311 /// Ok(Arc::new(()))
312 /// }).unwrap();
313 ///
314 /// // Start immediately, don't wait
315 /// assert_eq!(timer.start(0), OsalRsBool::True);
316 ///
317 /// // Restarting it resets the countdown to the full period
318 /// timer.start(100); // wait up to 100 ticks for the command queue
319 ///
320 /// System::delay(120);
321 /// assert!(FIRED.load(Ordering::SeqCst));
322 /// ```
323 fn start(&self, ticks_to_wait: TickType) -> OsalRsBool;
324
325 /// Stops the timer.
326 ///
327 /// The timer will not expire until started again with `start()` or `reset()`.
328 /// For periodic timers, this stops the automatic reloading.
329 ///
330 /// # Parameters
331 ///
332 /// * `ticks_to_wait` - Maximum ticks to wait if command queue is full:
333 /// - `0`: Return immediately if queue full
334 /// - `n`: Wait up to n ticks
335 /// - `TickType::MAX`: Wait forever
336 ///
337 /// # Returns
338 ///
339 /// * `True` - Command sent successfully to timer service
340 /// * `False` - Failed to send command (queue full, timeout)
341 ///
342 /// # State
343 ///
344 /// If the timer is already stopped, this command has no effect but
345 /// still returns `True`.
346 ///
347 /// # Examples
348 ///
349 /// ```
350 /// use osal_rs::os::*;
351 /// use osal_rs::utils::OsalRsBool;
352 /// use std::sync::Arc;
353 /// use std::sync::atomic::{AtomicU32, Ordering};
354 ///
355 /// static FIRINGS: AtomicU32 = AtomicU32::new(0);
356 ///
357 /// let timer = Timer::new("heartbeat", 20, true, None, |_timer, _param| {
358 /// FIRINGS.fetch_add(1, Ordering::SeqCst);
359 /// Ok(Arc::new(()))
360 /// }).unwrap();
361 ///
362 /// timer.start(0);
363 ///
364 /// // Stop the timer, wait up to 100 ticks
365 /// assert_eq!(timer.stop(100), OsalRsBool::True);
366 ///
367 /// // Nothing fires while it is stopped.
368 /// let stopped_at = FIRINGS.load(Ordering::SeqCst);
369 /// System::delay(60);
370 /// assert_eq!(FIRINGS.load(Ordering::SeqCst), stopped_at);
371 ///
372 /// // Later, restart it
373 /// timer.start(100);
374 /// System::delay(60);
375 /// assert!(FIRINGS.load(Ordering::SeqCst) > stopped_at);
376 /// ```
377 fn stop(&self, ticks_to_wait: TickType) -> OsalRsBool;
378
379 /// Resets the timer to its full period.
380 ///
381 /// If the timer is running, this restarts it from the beginning of its
382 /// period. If the timer is stopped, this starts it. This is useful for
383 /// implementing watchdog-style timers that must be periodically reset.
384 ///
385 /// # Parameters
386 ///
387 /// * `ticks_to_wait` - Maximum ticks to wait if command queue is full:
388 /// - `0`: Return immediately if queue full
389 /// - `n`: Wait up to n ticks
390 /// - `TickType::MAX`: Wait forever
391 ///
392 /// # Returns
393 ///
394 /// * `True` - Command sent successfully to timer service
395 /// * `False` - Failed to send command (queue full, timeout)
396 ///
397 /// # Use Cases
398 ///
399 /// - Watchdog timer: Reset timer to prevent timeout
400 /// - Activity timer: Reset when activity detected
401 /// - Timeout extension: Give more time before expiration
402 ///
403 /// # Examples
404 ///
405 /// ```
406 /// use osal_rs::os::*;
407 /// use std::sync::Arc;
408 /// use std::sync::atomic::{AtomicBool, Ordering};
409 /// use core::time::Duration;
410 ///
411 /// static TIMED_OUT: AtomicBool = AtomicBool::new(false);
412 ///
413 /// // Watchdog timer pattern
414 /// let watchdog = Timer::new_with_to_tick(
415 /// "watchdog",
416 /// Duration::from_millis(50),
417 /// false,
418 /// None,
419 /// |_timer, _param| {
420 /// TIMED_OUT.store(true, Ordering::SeqCst);
421 /// Ok(Arc::new(()))
422 /// }
423 /// ).unwrap();
424 ///
425 /// watchdog.start(0);
426 ///
427 /// // In main loop: reset watchdog to prevent timeout
428 /// for _ in 0..5 {
429 /// System::delay(20); // do work
430 /// watchdog.reset(0); // "Feed" the watchdog
431 /// }
432 ///
433 /// // Fed often enough, it never expired.
434 /// assert!(!TIMED_OUT.load(Ordering::SeqCst));
435 ///
436 /// // Stop feeding it and it fires.
437 /// System::delay(120);
438 /// assert!(TIMED_OUT.load(Ordering::SeqCst));
439 /// ```
440 fn reset(&self, ticks_to_wait: TickType) -> OsalRsBool;
441
442 /// Changes the timer period.
443 ///
444 /// Updates the timer period. The new period takes effect immediately:
445 /// - If the timer is running, it continues with the new period
446 /// - The remaining time is adjusted proportionally
447 /// - For periodic timers, future expirations use the new period
448 ///
449 /// # Parameters
450 ///
451 /// * `new_period_in_ticks` - New timer period in ticks
452 /// * `ticks_to_wait` - Maximum ticks to wait if command queue is full:
453 /// - `0`: Return immediately if queue full
454 /// - `n`: Wait up to n ticks
455 /// - `TickType::MAX`: Wait forever
456 ///
457 /// # Returns
458 ///
459 /// * `True` - Command sent successfully to timer service
460 /// * `False` - Failed to send command (queue full, timeout)
461 ///
462 /// # Behavior
463 ///
464 /// - If timer has already expired and is auto-reload, the new period
465 /// applies to the next expiration
466 /// - If timer is stopped, the new period will be used when started
467 ///
468 /// # Examples
469 ///
470 /// ```
471 /// use osal_rs::os::*;
472 /// use std::sync::Arc;
473 /// use std::sync::atomic::{AtomicBool, Ordering};
474 /// use core::time::Duration;
475 ///
476 /// static FIRED: AtomicBool = AtomicBool::new(false);
477 ///
478 /// let timer = Timer::new_with_to_tick(
479 /// "adaptive",
480 /// Duration::from_secs(10),
481 /// true,
482 /// None,
483 /// |_timer, _param| {
484 /// FIRED.store(true, Ordering::SeqCst);
485 /// Ok(Arc::new(()))
486 /// }
487 /// ).unwrap();
488 ///
489 /// timer.start(0);
490 ///
491 /// // Later, adjust the period based on system load. The new period takes
492 /// // effect immediately: the original 10s would never elapse in time here.
493 /// let system_busy = false;
494 /// if system_busy {
495 /// timer.change_period(500, 100); // slow down to 500ms
496 /// } else {
497 /// timer.change_period(10, 100); // speed up to 10ms
498 /// }
499 ///
500 /// System::delay(60);
501 /// assert!(FIRED.load(Ordering::SeqCst));
502 /// ```
503 fn change_period(&self, new_period_in_ticks: TickType, ticks_to_wait: TickType) -> OsalRsBool;
504
505 /// Deletes the timer and frees its resources.
506 ///
507 /// Terminates the timer and releases its resources. After deletion,
508 /// the timer handle becomes invalid and should not be used.
509 ///
510 /// # Parameters
511 ///
512 /// * `ticks_to_wait` - Maximum ticks to wait if command queue is full:
513 /// - `0`: Return immediately if queue full
514 /// - `n`: Wait up to n ticks
515 /// - `TickType::MAX`: Wait forever
516 ///
517 /// # Returns
518 ///
519 /// * `True` - Command sent successfully to timer service
520 /// * `False` - Failed to send command (queue full, timeout)
521 ///
522 /// # Safety
523 ///
524 /// - The timer should be stopped before deletion (recommended)
525 /// - Do not use the timer handle after calling this
526 /// - The timer is deleted asynchronously by the timer service task
527 ///
528 /// # Best Practice
529 ///
530 /// Stop the timer before deleting it to ensure clean shutdown:
531 ///
532 /// ```
533 /// # use osal_rs::os::*;
534 /// # use std::sync::Arc;
535 /// # let mut timer = Timer::new("temporary", 50, false, None, |_t, _p| Ok(Arc::new(()))).unwrap();
536 /// timer.stop(100);
537 /// timer.delete(100);
538 /// ```
539 ///
540 /// # Examples
541 ///
542 /// ```
543 /// use osal_rs::os::*;
544 /// use osal_rs::utils::OsalRsBool;
545 /// use std::sync::Arc;
546 /// use core::time::Duration;
547 ///
548 /// let mut timer = Timer::new_with_to_tick(
549 /// "temporary",
550 /// Duration::from_secs(1),
551 /// false,
552 /// None,
553 /// |_timer, _param| Ok(Arc::new(()))
554 /// ).unwrap();
555 ///
556 /// timer.start(0);
557 /// // ... use timer ...
558 ///
559 /// // Clean shutdown
560 /// timer.stop(100);
561 /// assert_eq!(timer.delete(100), OsalRsBool::True);
562 /// assert!(timer.is_null());
563 /// ```
564 fn delete(&mut self, ticks_to_wait: TickType) -> OsalRsBool;
565}