use alloc::boxed::Box;
use core::ffi::CStr;
use core::marker::PhantomData;
use core::ptr;
use veecle_freertos_sys::bindings::{
TickType_t, TimerHandle_t, pdFALSE, pdTRUE, pvTimerGetTimerID, shim_xTimerChangePeriod,
shim_xTimerDelete, shim_xTimerStart, shim_xTimerStartFromISR, shim_xTimerStop, xTimerCreate,
xTimerPendFunctionCall,
};
use crate::units::Duration;
use crate::{FreeRtosError, InterruptContext};
#[derive(Clone, Copy, Debug)]
pub struct TimerHandle(TimerHandle_t);
impl TimerHandle {
const MS_TIMEOUT: TickType_t = 50;
pub fn start(&self) -> Result<(), FreeRtosError> {
if unsafe { shim_xTimerStart(self.as_ptr(), Self::block_time()) } == pdTRUE() {
Ok(())
} else {
Err(FreeRtosError::Timeout)
}
}
pub fn start_from_isr(&self, context: &mut InterruptContext) -> Result<(), FreeRtosError> {
if unsafe { shim_xTimerStartFromISR(self.as_ptr(), context.get_task_field_mut()) }
== pdTRUE()
{
Ok(())
} else {
Err(FreeRtosError::QueueSendTimeout)
}
}
pub fn stop(&self) -> Result<(), FreeRtosError> {
if unsafe { shim_xTimerStop(self.as_ptr(), Self::block_time()) } == pdTRUE() {
Ok(())
} else {
Err(FreeRtosError::Timeout)
}
}
pub fn change_period(&self, new_period: Duration) -> Result<(), FreeRtosError> {
if new_period.ticks() == 0 {
return Err(FreeRtosError::ZeroDuration);
}
if unsafe { shim_xTimerChangePeriod(self.as_ptr(), new_period.ticks(), Self::block_time()) }
== pdTRUE()
{
Ok(())
} else {
Err(FreeRtosError::Timeout)
}
}
#[inline]
fn as_ptr(&self) -> TimerHandle_t {
self.0
}
fn id(&self) -> *mut core::ffi::c_void {
unsafe { pvTimerGetTimerID(self.as_ptr()) }
}
#[inline]
fn block_time() -> TickType_t {
Duration::from_ms(Self::MS_TIMEOUT).ticks()
}
}
#[derive(Debug)]
pub struct Timer<F>
where
F: Fn(TimerHandle) + Send + 'static,
{
handle: TimerHandle,
callback: PhantomData<F>,
}
impl<F> Timer<F>
where
F: Fn(TimerHandle) + Send + 'static,
{
pub fn periodic(
name: Option<&'static CStr>,
period: Duration,
callback: F,
) -> Result<Self, FreeRtosError> {
Self::spawn(name, period.ticks(), true, callback)
}
pub fn once(
name: Option<&'static CStr>,
period: Duration,
callback: F,
) -> Result<Self, FreeRtosError> {
Self::spawn(name, period.ticks(), false, callback)
}
pub fn handle(&self) -> TimerHandle {
self.handle
}
pub fn detach(self) {
core::mem::forget(self);
}
fn spawn(
name: Option<&'static CStr>,
period: TickType_t,
auto_reload: bool,
callback: F,
) -> Result<Self, FreeRtosError> {
if period == 0 {
return Err(FreeRtosError::ZeroDuration);
}
let callback = Box::into_raw(Box::new(callback));
let name = name.map_or(ptr::null(), |name| name.as_ptr());
let handle = unsafe {
xTimerCreate(
name.cast(),
period,
if auto_reload { pdTRUE() } else { pdFALSE() },
callback.cast(),
Some(Self::callback_bridge),
)
};
if handle.is_null() {
drop(unsafe { Box::from_raw(callback) });
return Err(FreeRtosError::OutOfMemory);
}
Ok(Self {
handle: TimerHandle(handle),
callback: PhantomData,
})
}
extern "C" fn callback_bridge(handle: TimerHandle_t) {
let handle = TimerHandle(handle);
let callback =
unsafe { handle.id().cast::<F>().as_ref() }.expect("callback should not be null");
callback(handle);
}
extern "C" fn drop_callback(timer_id: *mut core::ffi::c_void, _: u32) {
let callback = timer_id.cast::<F>();
assert!(!callback.is_null(), "callback pointer is null");
drop(unsafe { Box::from_raw(callback) });
}
}
impl<F> Drop for Timer<F>
where
F: Fn(TimerHandle) + Send + 'static,
{
fn drop(&mut self) {
let timer_id = self.handle.id();
let result = unsafe { shim_xTimerDelete(self.handle.as_ptr(), TimerHandle::block_time()) };
assert_eq!(result, pdTRUE(), "timer deletion has failed");
let result = unsafe {
xTimerPendFunctionCall(
Some(Self::drop_callback),
timer_id,
0,
TimerHandle::block_time(),
)
};
assert_eq!(result, pdTRUE(), "drop callback scheduling has failed");
}
}