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