Skip to main content

osal_rs/freertos/
timer.rs

1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 *
18 ***************************************************************************/
19
20//! Software timer support for FreeRTOS.
21//!
22//! This module provides software timers that run callbacks at specified intervals.
23//! Timers can be one-shot or auto-reloading (periodic) and execute their callbacks
24//! in the timer daemon task context.
25
26use core::any::Any;
27use core::fmt::{Debug, Display};
28use core::ops::Deref;
29use core::ptr::null_mut;
30
31use alloc::boxed::Box;
32use alloc::string::{String, ToString};
33use alloc::sync::Arc;
34
35use crate::freertos::ffi::pdPASS;
36use crate::to_c_str;
37use crate::traits::{ToTick, TimerParam, TimerFn, TimerFnPtr};
38use crate::utils::{OsalRsBool, Result, Error};
39use super::ffi::{TimerHandle, pvTimerGetTimerID, xTimerCreate, osal_rs_timer_start, osal_rs_timer_change_period, osal_rs_timer_delete, osal_rs_timer_reset, osal_rs_timer_stop};
40use super::types::{TickType};
41
42/// A software timer that executes a callback at regular intervals.
43///
44/// Timers can be configured as:
45/// - **One-shot**: Executes once after the specified period
46/// - **Auto-reload**: Executes repeatedly at the specified interval
47///
48/// Timer callbacks execute in the context of the timer daemon task, not in
49/// interrupt context. This means they can call most RTOS functions safely.
50///
51/// # Important Notes
52///
53/// - Timer callbacks should complete quickly to avoid delaying other timers
54/// - Callbacks must not block indefinitely
55/// - Requires `configUSE_TIMERS = 1` in FreeRTOSConfig.h
56///
57/// # Examples
58///
59/// ## One-shot timer
60///
61/// ```ignore
62/// use osal_rs::os::{Timer, TimerFn};
63/// use core::time::Duration;
64/// 
65/// let timer = Timer::new_with_to_tick(
66///     "oneshot",
67///     Duration::from_secs(1),
68///     false,  // Not auto-reload (one-shot)
69///     None,
70///     |timer, param| {
71///         println!("Timer fired once!");
72///         Ok(param)
73///     }
74/// ).unwrap();
75/// 
76/// timer.start_with_to_tick(Duration::from_millis(10)).unwrap();
77/// ```
78///
79/// ## Periodic timer
80///
81/// ```ignore
82/// use osal_rs::os::{Timer, TimerFn};
83/// use core::time::Duration;
84/// 
85/// let timer = Timer::new_with_to_tick(
86///     "periodic",
87///     Duration::from_millis(500),
88///     true,  // Auto-reload (periodic)
89///     None,
90///     |timer, param| {
91///         println!("Tick every 500ms");
92///         Ok(param)
93///     }
94/// ).unwrap();
95/// 
96/// timer.start_with_to_tick(Duration::from_millis(10)).unwrap();
97/// 
98/// // Stop after some time
99/// Duration::from_secs(5).sleep();
100/// timer.stop_with_to_tick(Duration::from_millis(10));
101/// ```
102///
103/// ## Timer with custom parameters
104///
105/// ```ignore
106/// use osal_rs::os::{Timer, TimerFn, TimerParam};
107/// use alloc::sync::Arc;
108/// use core::time::Duration;
109/// 
110/// struct CounterData {
111///     count: u32,
112/// }
113/// 
114/// let data = Arc::new(CounterData { count: 0 });
115/// let param: TimerParam = data.clone();
116/// 
117/// let timer = Timer::new_with_to_tick(
118///     "counter",
119///     Duration::from_secs(1),
120///     true,
121///     Some(param),
122///     |timer, param| {
123///         if let Some(param_arc) = param {
124///             if let Some(data) = param_arc.downcast_ref::<CounterData>() {
125///                 println!("Counter: {}", data.count);
126///             }
127///         }
128///         Ok(None)
129///     }
130/// ).unwrap();
131/// 
132/// timer.start_with_to_tick(Duration::from_millis(10));
133/// ```
134///
135/// ## Changing timer period
136///
137/// ```ignore
138/// use osal_rs::os::{Timer, TimerFn};
139/// use core::time::Duration;
140/// 
141/// let timer = Timer::new_with_to_tick(
142///     "adjustable",
143///     Duration::from_millis(100),
144///     true,
145///     None,
146///     |_, _| { println!("Tick"); Ok(None) }
147/// ).unwrap();
148/// 
149/// timer.start_with_to_tick(Duration::from_millis(10));
150/// 
151/// // Change period to 500ms
152/// Duration::from_secs(2).sleep();
153/// timer.change_period_with_to_tick(
154///     Duration::from_millis(500),
155///     Duration::from_millis(10)
156/// );
157/// ```
158///
159/// ## Resetting a timer
160///
161/// ```ignore
162/// use osal_rs::os::{Timer, TimerFn};
163/// use core::time::Duration;
164/// 
165/// let timer = Timer::new_with_to_tick(
166///     "watchdog",
167///     Duration::from_secs(5),
168///     false,
169///     None,
170///     |_, _| { println!("Timeout!"); Ok(None) }
171/// ).unwrap();
172/// 
173/// timer.start_with_to_tick(Duration::from_millis(10));
174/// 
175/// // Reset timer before it expires (like a watchdog)
176/// Duration::from_secs(2).sleep();
177/// timer.reset_with_to_tick(Duration::from_millis(10));  // Restart the 5s countdown
178/// ```
179#[derive(Clone)]
180pub struct Timer {
181    /// FreeRTOS timer handle
182    pub handle: TimerHandle,
183    /// Timer name for debugging
184    name: String, 
185    /// Callback function to execute when timer expires
186    callback: Option<Arc<TimerFnPtr>>,
187    /// Optional parameter passed to callback
188    param: Option<TimerParam>, 
189}
190
191unsafe impl Send for Timer {}
192unsafe impl Sync for Timer {}
193
194impl Timer {
195    /// Creates a new software timer with tick conversion.
196    /// 
197    /// This is a convenience method that accepts any type implementing `ToTick`
198    /// (like `Duration`) for the timer period.
199    /// 
200    /// # Parameters
201    /// 
202    /// * `name` - Timer name for debugging
203    /// * `timer_period_in_ticks` - Timer period (e.g., `Duration::from_secs(1)`)
204    /// * `auto_reload` - `true` for periodic, `false` for one-shot
205    /// * `param` - Optional parameter passed to callback
206    /// * `callback` - Function called when timer expires
207    /// 
208    /// # Returns
209    /// 
210    /// * `Ok(Self)` - Successfully created timer
211    /// * `Err(Error)` - Creation failed
212    /// 
213    /// # Examples
214    /// 
215    /// ```ignore
216    /// use osal_rs::os::{Timer, TimerFn};
217    /// use core::time::Duration;
218    /// 
219    /// let timer = Timer::new_with_to_tick(
220    ///     "periodic",
221    ///     Duration::from_secs(1),
222    ///     true,
223    ///     None,
224    ///     |_timer, _param| { println!("Tick"); Ok(None) }
225    /// ).unwrap();
226    /// ```
227    #[inline]
228    pub fn new_with_to_tick<F>(name: &str, timer_period_in_ticks: impl ToTick, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
229    where
230        F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static {
231            Self::new(name, timer_period_in_ticks.to_ticks(), auto_reload, param, callback)
232        }
233
234    /// Starts the timer with tick conversion.
235    /// 
236    /// Convenience method that accepts any type implementing `ToTick`.
237    /// 
238    /// # Parameters
239    /// 
240    /// * `ticks_to_wait` - Maximum time to wait for the command to be sent to timer daemon
241    /// 
242    /// # Returns
243    /// 
244    /// * `OsalRsBool::True` - Timer started successfully
245    /// * `OsalRsBool::False` - Failed to start timer
246    /// 
247    /// # Examples
248    /// 
249    /// ```ignore
250    /// use osal_rs::os::{Timer, TimerFn};
251    /// use core::time::Duration;
252    /// 
253    /// timer.start_with_to_tick(Duration::from_millis(10));
254    /// ```
255    #[inline]
256    pub fn start_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
257        self.start(ticks_to_wait.to_ticks())
258    }
259
260    /// Stops the timer with tick conversion.
261    /// 
262    /// Convenience method that accepts any type implementing `ToTick`.
263    /// 
264    /// # Parameters
265    /// 
266    /// * `ticks_to_wait` - Maximum time to wait for the command to be sent to timer daemon
267    /// 
268    /// # Returns
269    /// 
270    /// * `OsalRsBool::True` - Timer stopped successfully
271    /// * `OsalRsBool::False` - Failed to stop timer
272    /// 
273    /// # Examples
274    /// 
275    /// ```ignore
276    /// use osal_rs::os::{Timer, TimerFn};
277    /// use core::time::Duration;
278    /// 
279    /// timer.stop_with_to_tick(Duration::from_millis(10));
280    /// ```
281    #[inline]
282    pub fn stop_with_to_tick(&self, ticks_to_wait: impl ToTick)  -> OsalRsBool {
283        self.stop(ticks_to_wait.to_ticks())
284    }
285
286    /// Resets the timer with tick conversion.
287    /// 
288    /// Resets the timer to restart its period. For one-shot timers, this
289    /// restarts them. For periodic timers, this resets the period.
290    /// 
291    /// # Parameters
292    /// 
293    /// * `ticks_to_wait` - Maximum time to wait for the command to be sent to timer daemon
294    /// 
295    /// # Returns
296    /// 
297    /// * `OsalRsBool::True` - Timer reset successfully
298    /// * `OsalRsBool::False` - Failed to reset timer
299    /// 
300    /// # Examples
301    /// 
302    /// ```ignore
303    /// use osal_rs::os::{Timer, TimerFn};
304    /// use core::time::Duration;
305    /// 
306    /// // Reset watchdog timer before it expires
307    /// timer.reset_with_to_tick(Duration::from_millis(10));
308    /// ```
309    #[inline]
310    pub fn reset_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
311        self.reset(ticks_to_wait.to_ticks())
312    }
313
314    /// Changes the timer period with tick conversion.
315    /// 
316    /// Convenience method that accepts any type implementing `ToTick`.
317    /// 
318    /// # Parameters
319    /// 
320    /// * `new_period_in_ticks` - New timer period
321    /// * `new_period_ticks` - Maximum time to wait for the command to be sent to timer daemon
322    /// 
323    /// # Returns
324    /// 
325    /// * `OsalRsBool::True` - Period changed successfully
326    /// * `OsalRsBool::False` - Failed to change period
327    /// 
328    /// # Examples
329    /// 
330    /// ```ignore
331    /// use osal_rs::os::{Timer, TimerFn};
332    /// use core::time::Duration;
333    /// 
334    /// // Change from 1 second to 500ms
335    /// timer.change_period_with_to_tick(
336    ///     Duration::from_millis(500),
337    ///     Duration::from_millis(10)
338    /// );
339    /// ```
340    #[inline]
341    pub fn change_period_with_to_tick(&self, new_period_in_ticks: impl ToTick, new_period_ticks: impl ToTick) -> OsalRsBool {
342        self.change_period(new_period_in_ticks.to_ticks(), new_period_ticks.to_ticks())
343    }
344
345    /// Deletes the timer with tick conversion.
346    /// 
347    /// Convenience method that accepts any type implementing `ToTick`.
348    /// 
349    /// # Parameters
350    /// 
351    /// * `ticks_to_wait` - Maximum time to wait for the command to be sent to timer daemon
352    /// 
353    /// # Returns
354    /// 
355    /// * `OsalRsBool::True` - Timer deleted successfully
356    /// * `OsalRsBool::False` - Failed to delete timer
357    /// 
358    /// # Examples
359    /// 
360    /// ```ignore
361    /// use osal_rs::os::{Timer, TimerFn};
362    /// use core::time::Duration;
363    /// 
364    /// timer.delete_with_to_tick(Duration::from_millis(10));
365    /// ```
366    #[inline]
367    pub fn delete_with_to_tick(&mut self, ticks_to_wait: impl ToTick) -> OsalRsBool {
368        self.delete(ticks_to_wait.to_ticks())
369    }
370}
371
372/// Internal C-compatible wrapper for timer callbacks.
373/// 
374/// This function bridges between FreeRTOS C API and Rust closures.
375/// It retrieves the timer instance from the timer ID, extracts the callback
376/// and parameters, and executes the user-provided callback.
377/// 
378/// # Safety
379/// 
380/// This function is marked extern "C" because it:
381/// - Is called from FreeRTOS C code (timer daemon task)
382/// - Performs raw pointer conversions
383/// - Expects a valid timer handle with associated timer instance
384extern "C" fn callback_c_wrapper(handle: TimerHandle) {
385
386    if handle.is_null() {
387        return;
388    }
389
390    let param_ptr = unsafe {
391        pvTimerGetTimerID(handle) 
392    };
393    
394    let mut timer_instance: Box<Timer> = unsafe { Box::from_raw(param_ptr as *mut _) };
395
396    timer_instance.as_mut().handle = handle;
397
398    let param_arc: Option<Arc<dyn Any + Send + Sync>> = timer_instance
399        .param
400        .clone();
401
402    if let Some(callback) = &timer_instance.callback.clone() {
403        let _ = callback(timer_instance, param_arc);
404    }
405}
406
407
408
409impl Timer {
410    /// Creates a new software timer.
411    ///
412    /// # Parameters
413    ///
414    /// * `name` - Timer name for debugging
415    /// * `timer_period_in_ticks` - Timer period in ticks
416    /// * `auto_reload` - `true` for periodic, `false` for one-shot
417    /// * `param` - Optional parameter passed to callback
418    /// * `callback` - Function called when timer expires
419    ///
420    /// # Returns
421    ///
422    /// * `Ok(Self)` - Successfully created timer
423    /// * `Err(Error)` - Creation failed
424    ///
425    /// # Examples
426    ///
427    /// ```ignore
428    /// use osal_rs::os::{Timer, TimerFn};
429    /// 
430    /// let timer = Timer::new(
431    ///     "my_timer",
432    ///     1000,
433    ///     false,
434    ///     None,
435    ///     |_timer, _param| Ok(None)
436    /// ).unwrap();
437    /// ``
438    
439    pub fn new<F>(name: &str, timer_period_in_ticks: TickType, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
440    where
441        F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static {
442
443            let mut boxed_timer = Box::new(Self {
444                handle: core::ptr::null_mut(),
445                name: name.to_string(),
446                callback: Some(Arc::new(callback.clone())),
447                param: param.clone(),
448            });
449
450            let handle = unsafe {
451                xTimerCreate( to_c_str!(name), 
452                    timer_period_in_ticks, 
453                    if auto_reload { 1 } else { 0 }, 
454                    Box::into_raw(boxed_timer.clone()) as *mut _, 
455                    Some(super::timer::callback_c_wrapper)
456                )
457            };
458
459            if handle.is_null() {
460                Err(Error::NullPtr)
461            } else {
462                boxed_timer.as_mut().handle = handle;
463                Ok(*boxed_timer)
464            }
465
466    }
467    
468}
469
470impl TimerFn for Timer {
471
472    /// Starts the timer.
473    /// 
474    /// Sends a command to the timer daemon to start the timer. If the timer
475    /// was already running, this has no effect.
476    /// 
477    /// # Parameters
478    /// 
479    /// * `ticks_to_wait` - Maximum time to wait for command to be sent to timer daemon
480    /// 
481    /// # Returns
482    /// 
483    /// * `OsalRsBool::True` - Timer started successfully
484    /// * `OsalRsBool::False` - Failed to start (command queue full)
485    /// 
486    /// # Examples
487    /// 
488    /// ```ignore
489    /// use osal_rs::os::{Timer, TimerFn};
490    /// 
491    /// let timer = Timer::new("my_timer", 1000, true, None, |_, _| Ok(None)).unwrap();
492    /// timer.start(10);  // Wait up to 10 ticks
493    /// ```
494    fn start(&self, ticks_to_wait: TickType) -> OsalRsBool {
495        if unsafe {
496            osal_rs_timer_start(self.handle, ticks_to_wait)
497        } != pdPASS {
498            OsalRsBool::False
499        } else {
500            OsalRsBool::True
501        }
502    }
503
504    /// Stops the timer.
505    /// 
506    /// Sends a command to the timer daemon to stop the timer. The timer will not
507    /// fire again until it is restarted.
508    /// 
509    /// # Parameters
510    /// 
511    /// * `ticks_to_wait` - Maximum time to wait for command to be sent to timer daemon
512    /// 
513    /// # Returns
514    /// 
515    /// * `OsalRsBool::True` - Timer stopped successfully
516    /// * `OsalRsBool::False` - Failed to stop (command queue full)
517    /// 
518    /// # Examples
519    /// 
520    /// ```ignore
521    /// use osal_rs::os::{Timer, TimerFn};
522    /// 
523    /// timer.stop(10);  // Wait up to 10 ticks to stop
524    /// ```
525    fn stop(&self, ticks_to_wait: TickType)  -> OsalRsBool {
526        if unsafe {
527            osal_rs_timer_stop(self.handle, ticks_to_wait)
528        } != pdPASS {
529            OsalRsBool::False
530        } else {
531            OsalRsBool::True
532        }
533    }
534
535    /// Resets the timer.
536    /// 
537    /// Resets the timer's period. For a one-shot timer that has already expired,
538    /// this will restart it. For a periodic timer, this resets the period.
539    /// 
540    /// # Parameters
541    /// 
542    /// * `ticks_to_wait` - Maximum time to wait for command to be sent to timer daemon
543    /// 
544    /// # Returns
545    /// 
546    /// * `OsalRsBool::True` - Timer reset successfully
547    /// * `OsalRsBool::False` - Failed to reset (command queue full)
548    /// 
549    /// # Examples
550    /// 
551    /// ```ignore
552    /// use osal_rs::os::{Timer, TimerFn};
553    /// 
554    /// // Reset a watchdog timer before it expires
555    /// timer.reset(10);
556    /// ```
557    fn reset(&self, ticks_to_wait: TickType) -> OsalRsBool {
558        if unsafe {
559            osal_rs_timer_reset(self.handle, ticks_to_wait)
560        } != pdPASS {
561            OsalRsBool::False
562        } else {
563            OsalRsBool::True
564        }
565    }
566
567    /// Changes the timer period.
568    /// 
569    /// Changes the period of a timer that was previously created. The timer
570    /// must be stopped, or the period will be changed when it next expires.
571    /// 
572    /// # Parameters
573    /// 
574    /// * `new_period_in_ticks` - New period for the timer in ticks
575    /// * `new_period_ticks` - Maximum time to wait for command to be sent to timer daemon
576    /// 
577    /// # Returns
578    /// 
579    /// * `OsalRsBool::True` - Period changed successfully
580    /// * `OsalRsBool::False` - Failed to change period (command queue full)
581    /// 
582    /// # Examples
583    /// 
584    /// ```ignore
585    /// use osal_rs::os::{Timer, TimerFn};
586    /// 
587    /// // Change period from 1000 ticks to 500 ticks
588    /// timer.change_period(500, 10);
589    /// ```
590    fn change_period(&self, new_period_in_ticks: TickType, new_period_ticks: TickType) -> OsalRsBool {
591        if unsafe {
592            osal_rs_timer_change_period(self.handle, new_period_in_ticks, new_period_ticks)
593        } != pdPASS {
594            OsalRsBool::False
595        } else {
596            OsalRsBool::True
597        }
598    }
599
600    /// Deletes the timer.
601    /// 
602    /// Sends a command to the timer daemon to delete the timer.
603    /// The timer handle becomes invalid after this call.
604    /// 
605    /// # Parameters
606    /// 
607    /// * `ticks_to_wait` - Maximum time to wait for command to be sent to timer daemon
608    /// 
609    /// # Returns
610    /// 
611    /// * `OsalRsBool::True` - Timer deleted successfully
612    /// * `OsalRsBool::False` - Failed to delete (command queue full)
613    /// 
614    /// # Safety
615    /// 
616    /// After calling this function, the timer handle is set to null and should not be used.
617    /// 
618    /// # Examples
619    /// 
620    /// ```ignore
621    /// use osal_rs::os::{Timer, TimerFn};
622    /// 
623    /// let mut timer = Timer::new("temp", 1000, false, None, |_, _| Ok(None)).unwrap();
624    /// timer.delete(10);
625    /// ```
626    fn delete(&mut self, ticks_to_wait: TickType) -> OsalRsBool {
627        if unsafe {
628            osal_rs_timer_delete(self.handle, ticks_to_wait)
629        } != pdPASS {
630            self.handle = null_mut();
631            OsalRsBool::False
632        } else {
633            self.handle = null_mut();
634            OsalRsBool::True
635        }
636    }
637}
638
639/// Automatically deletes the timer when it goes out of scope.
640/// 
641/// This ensures proper cleanup of FreeRTOS resources by calling
642/// `delete(0)` when the timer is dropped.
643impl Drop for Timer {
644    fn drop(&mut self) {
645        self.delete(0);
646    }
647}
648
649/// Allows dereferencing to the underlying FreeRTOS timer handle.
650impl Deref for Timer {
651    type Target = TimerHandle;
652
653    fn deref(&self) -> &Self::Target {
654        &self.handle
655    }
656}
657
658/// Formats the timer for debugging purposes.
659/// 
660/// Shows the timer handle and name.
661impl Debug for Timer {
662    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
663        f.debug_struct("Timer")
664            .field("handle", &self.handle)
665            .field("name", &self.name)
666            .finish()
667    }
668}
669
670/// Formats the timer for display purposes.
671/// 
672/// Shows a concise representation with name and handle.
673impl Display for Timer {
674    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
675        write!(f, "Timer {{ name: {}, handle: {:?} }}", self.name, self.handle)
676    }
677}