Skip to main content

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}