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}