use core::ffi::c_void;
use core::fmt::{Debug, Display};
use core::mem::forget;
use core::ops::Deref;
use core::ptr::null_mut;
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use alloc::boxed::Box;
use alloc::sync::{Arc, Weak};
use crate::freertos::ffi::pdPASS;
use crate::traits::{MAX_TASK_NAME_LEN, ToTick, TimerParam, TimerFn, TimerFnPtr};
use crate::utils::{Bytes, Error, OsalRsBool, Result};
use super::ffi::{TimerHandle, pvTimerGetTimerID, vTimerSetTimerID, xTimerCreate, osal_rs_timer_start, osal_rs_timer_change_period, osal_rs_timer_delete, osal_rs_timer_reset, osal_rs_timer_stop};
use super::types::{TickType};
#[derive(Clone)]
pub struct Timer {
pub handle: TimerHandle,
name: Bytes<MAX_TASK_NAME_LEN>,
shared: Option<Arc<TimerShared>>,
}
unsafe impl Send for Timer {}
unsafe impl Sync for Timer {}
struct TimerShared {
handle: AtomicPtr<c_void>,
ready: AtomicBool,
name: Bytes<MAX_TASK_NAME_LEN>,
callback: Option<Arc<TimerFnPtr>>,
param: AtomicPtr<c_void>,
}
impl TimerShared {
fn take_param(&self) -> Option<TimerParam> {
let raw = self.param.swap(null_mut(), Ordering::AcqRel);
if raw.is_null() {
None
} else {
Some(*unsafe { Box::from_raw(raw as *mut TimerParam) })
}
}
fn store_param(&self, param: Option<TimerParam>) {
let raw = match param {
Some(param) => Box::into_raw(Box::new(param)) as *mut c_void,
None => null_mut(),
};
let previous = self.param.swap(raw, Ordering::AcqRel);
if !previous.is_null() {
drop(unsafe { Box::from_raw(previous as *mut TimerParam) });
}
}
fn destroy(&self, ticks_to_wait: TickType) -> bool {
if !self.ready.swap(false, Ordering::AcqRel) {
return false;
}
let handle = self.handle.swap(null_mut(), Ordering::AcqRel) as TimerHandle;
unsafe {
vTimerSetTimerID(handle, null_mut());
osal_rs_timer_delete(handle, ticks_to_wait);
}
self.store_param(None);
true
}
}
impl Drop for TimerShared {
fn drop(&mut self) {
if self.ready.swap(false, Ordering::AcqRel) {
let handle = self.handle.swap(null_mut(), Ordering::AcqRel) as TimerHandle;
if unsafe { osal_rs_timer_change_period(handle, 1, 0) } != pdPASS {
unsafe {
vTimerSetTimerID(handle, null_mut());
osal_rs_timer_delete(handle, 0);
}
}
}
self.store_param(None);
}
}
impl Timer {
#[inline]
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>
where
F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static {
Self::new(name, timer_period_in_ticks.to_ticks(), auto_reload, param, callback)
}
#[inline]
pub fn start_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
self.start(ticks_to_wait.to_ticks())
}
#[inline]
pub fn stop_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
self.stop(ticks_to_wait.to_ticks())
}
#[inline]
pub fn reset_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
self.reset(ticks_to_wait.to_ticks())
}
#[inline]
pub fn change_period_with_to_tick(&self, new_period_in_ticks: impl ToTick, new_period_ticks: impl ToTick) -> OsalRsBool {
self.change_period(new_period_in_ticks.to_ticks(), new_period_ticks.to_ticks())
}
#[inline]
pub fn delete_with_to_tick(&mut self, ticks_to_wait: impl ToTick) -> OsalRsBool {
self.delete(ticks_to_wait.to_ticks())
}
}
extern "C" fn callback_c_wrapper(handle: TimerHandle) {
if handle.is_null() {
return;
}
let id = unsafe { pvTimerGetTimerID(handle) } as *const TimerShared;
if id.is_null() {
return;
}
let weak = unsafe { Weak::from_raw(id) };
let Some(shared) = weak.upgrade() else {
unsafe {
vTimerSetTimerID(handle, null_mut());
osal_rs_timer_delete(handle, 0);
}
drop(weak);
return;
};
forget(weak);
if !shared.ready.load(Ordering::Acquire) {
return;
}
let Some(callback) = shared.callback.clone() else {
return;
};
let timer_self = Timer {
handle,
name: shared.name,
shared: Some(shared.clone()),
};
let current = shared.take_param();
match callback(Box::new(timer_self), current.clone()) {
Ok(next) => shared.store_param(Some(next)),
Err(_) => shared.store_param(current),
}
}
impl Timer {
pub fn new<F>(name: &str, timer_period_in_ticks: TickType, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
where
F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static {
let name = Bytes::<MAX_TASK_NAME_LEN>::from_str(name);
let shared = Arc::new(TimerShared {
handle: AtomicPtr::new(null_mut()),
ready: AtomicBool::new(false),
name,
callback: Some(Arc::new(callback)),
param: AtomicPtr::new(null_mut()),
});
shared.store_param(param);
let id = Weak::into_raw(Arc::downgrade(&shared));
let handle = unsafe {
xTimerCreate( shared.name.as_cstr().as_ptr(),
timer_period_in_ticks,
if auto_reload { 1 } else { 0 },
id as *mut c_void,
Some(super::timer::callback_c_wrapper)
)
};
if handle.is_null() {
drop(unsafe { Weak::from_raw(id) });
return Err(Error::NullPtr);
}
shared.handle.store(handle as *mut c_void, Ordering::Release);
shared.ready.store(true, Ordering::Release);
Ok(Self { handle, name, shared: Some(shared) })
}
fn live_handle(&self) -> Option<TimerHandle> {
let shared = self.shared.as_ref()?;
if !shared.ready.load(Ordering::Acquire) {
return None;
}
Some(shared.handle.load(Ordering::Acquire) as TimerHandle)
}
}
impl TimerFn for Timer {
fn is_null(&self) -> bool {
match &self.shared {
Some(shared) => !shared.ready.load(Ordering::Acquire),
None => true,
}
}
fn start(&self, ticks_to_wait: TickType) -> OsalRsBool {
let Some(handle) = self.live_handle() else {
return OsalRsBool::False;
};
if unsafe {
osal_rs_timer_start(handle, ticks_to_wait)
} != pdPASS {
OsalRsBool::False
} else {
OsalRsBool::True
}
}
fn stop(&self, ticks_to_wait: TickType) -> OsalRsBool {
let Some(handle) = self.live_handle() else {
return OsalRsBool::False;
};
if unsafe {
osal_rs_timer_stop(handle, ticks_to_wait)
} != pdPASS {
OsalRsBool::False
} else {
OsalRsBool::True
}
}
fn reset(&self, ticks_to_wait: TickType) -> OsalRsBool {
let Some(handle) = self.live_handle() else {
return OsalRsBool::False;
};
if unsafe {
osal_rs_timer_reset(handle, ticks_to_wait)
} != pdPASS {
OsalRsBool::False
} else {
OsalRsBool::True
}
}
fn change_period(&self, new_period_in_ticks: TickType, new_period_ticks: TickType) -> OsalRsBool {
let Some(handle) = self.live_handle() else {
return OsalRsBool::False;
};
if unsafe {
osal_rs_timer_change_period(handle, new_period_in_ticks, new_period_ticks)
} != pdPASS {
OsalRsBool::False
} else {
OsalRsBool::True
}
}
fn delete(&mut self, ticks_to_wait: TickType) -> OsalRsBool {
let Some(shared) = self.shared.take() else {
return OsalRsBool::False;
};
shared.destroy(ticks_to_wait);
self.handle = null_mut();
OsalRsBool::True
}
}
impl Deref for Timer {
type Target = TimerHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl Debug for Timer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Timer")
.field("handle", &self.handle)
.field("name", &self.name)
.field("is_null", &self.is_null())
.finish()
}
}
impl Display for Timer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Timer {{ name: {}, handle: {:?} }}", self.name, self.handle)
}
}