Skip to main content

leodos_libcfs/cfe/
time.rs

1//! CFE Time Services (TIME) interface.
2//!
3//! This module provides safe wrappers for the cFE Time Services API, which is
4//! the primary source for mission-synchronized time in a cFS system. It handles
5//! spacecraft time, Mission Elapsed Time (MET), and conversions between them.
6
7use crate::cfe::duration::Duration;
8use crate::error::Result;
9use crate::ffi;
10use crate::status::check;
11use core::fmt;
12use core::ops::{Add, Sub};
13use core::str;
14
15/// A wrapper around `CFE_TIME_SysTime_t` representing a specific time.
16#[derive(Debug, Clone, Copy)]
17#[repr(transparent)]
18pub struct SysTime(pub(crate) ffi::CFE_TIME_SysTime_t);
19
20// Manual implementation of PartialEq because bindgen didn't derive it
21impl PartialEq for SysTime {
22    fn eq(&self, other: &Self) -> bool {
23        self.0.Seconds == other.0.Seconds && self.0.Subseconds == other.0.Subseconds
24    }
25}
26
27impl Eq for SysTime {}
28
29impl PartialOrd for SysTime {
30    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
31        Some(self.cmp(other))
32    }
33}
34
35impl Ord for SysTime {
36    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
37        use core::cmp::Ordering;
38        match unsafe { ffi::CFE_TIME_Compare(self.0, other.0) } {
39            ffi::CFE_TIME_Compare_CFE_TIME_A_LT_B => Ordering::Less,
40            ffi::CFE_TIME_Compare_CFE_TIME_A_GT_B => Ordering::Greater,
41            _ => Ordering::Equal,
42        }
43    }
44}
45
46impl fmt::Display for SysTime {
47    /// Formats the time as a `yyyy-ddd-hh:mm:ss.xxxxx` string.
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        let mut buffer = [0u8; ffi::CFE_TIME_PRINTED_STRING_SIZE as usize];
50        unsafe {
51            ffi::CFE_TIME_Print(buffer.as_mut_ptr() as *mut libc::c_char, self.0);
52        }
53        // Find the null terminator to determine string length
54        let len = buffer.iter().position(|&b| b == 0).unwrap_or(0);
55
56        // CFE_TIME_Print is guaranteed to produce valid ASCII/UTF-8.
57        let s = str::from_utf8(&buffer[..len]).map_err(|_| fmt::Error)?;
58        f.write_str(s)
59    }
60}
61
62impl From<Duration> for SysTime {
63    fn from(duration: Duration) -> Self {
64        let subseconds = microseconds_to_subseconds(duration.nanos() / 1000);
65        SysTime(ffi::CFE_TIME_SysTime_t {
66            Seconds: duration.secs(),
67            Subseconds: subseconds,
68        })
69    }
70}
71
72impl SysTime {
73    /// Returns the seconds component of the time.
74    pub fn seconds(&self) -> u32 {
75        self.0.Seconds
76    }
77
78    /// Returns the subseconds component of the time.
79    /// The unit is 1/2^32 seconds.
80    pub fn subseconds(&self) -> u32 {
81        self.0.Subseconds
82    }
83
84    /// Returns the current spacecraft time in the mission-defined default format (TAI or UTC).
85    /// This is the most common time function to use.
86    pub fn now() -> Self {
87        Self(unsafe { ffi::CFE_TIME_GetTime() })
88    }
89
90    /// Returns the current TAI (International Atomic Time).
91    ///
92    /// Not portable to all missions. TAI maintenance in flight is
93    /// not guaranteed — prefer [`now`](Self::now) when possible.
94    pub fn now_tai() -> Self {
95        Self(unsafe { ffi::CFE_TIME_GetTAI() })
96    }
97
98    /// Returns the current UTC (Coordinated Universal Time).
99    ///
100    /// Not portable to all missions. UTC can jump backward on
101    /// leap second events — prefer [`now`](Self::now) when
102    /// possible.
103    pub fn now_utc() -> Self {
104        Self(unsafe { ffi::CFE_TIME_GetUTC() })
105    }
106
107    /// Returns the current Mission Elapsed Time (MET).
108    pub fn now_met() -> Self {
109        Self(unsafe { ffi::CFE_TIME_GetMET() })
110    }
111
112    /// Returns the current value of the spacecraft time correction factor (STCF).
113    pub fn now_stcf() -> SysTime {
114        SysTime(unsafe { ffi::CFE_TIME_GetSTCF() })
115    }
116
117    /// Converts a specified MET into Spacecraft Time (UTC or TAI).
118    pub fn to_sc(&self) -> Self {
119        Self(unsafe { ffi::CFE_TIME_MET2SCTime(self.0) })
120    }
121
122    /// Converts a CFE SysTime into the 6-byte CCSDS Day Segmented time format.
123    ///
124    /// This is a crucial utility function for creating telemetry.
125    ///
126    /// Format:
127    /// - Bytes 0-3: Seconds since epoch (Big Endian)
128    /// - Bytes 4-5: Subseconds, representing fractions of a second in 1/2^16 increments (Big Endian)
129    pub fn to_ccsds(self) -> [u8; 6] {
130        let mut ccsds_time = [0u8; 6];
131
132        let seconds = self.seconds();
133
134        // 2. Write the seconds into the first 4 bytes of the array in Big Endian format.
135        ccsds_time[0..4].copy_from_slice(&seconds.to_be_bytes());
136
137        // 3. Get the 32-bit subseconds field. The unit is 1/(2^32) seconds.
138        let subseconds = self.subseconds();
139
140        // 4. Convert the 32-bit subseconds into 16-bit subseconds. The CCSDS format
141        //    divides a second into 2^16 parts. We can do this by right-shifting
142        //    the 32-bit value by 16, effectively taking the most significant 16 bits.
143        let subseconds_16bit = (subseconds >> 16) as u16;
144
145        // 5. Write the 16-bit subseconds into the last 2 bytes of the array in Big Endian format.
146        ccsds_time[4..6].copy_from_slice(&subseconds_16bit.to_be_bytes());
147
148        ccsds_time
149    }
150}
151
152impl Add for SysTime {
153    type Output = Self;
154
155    /// Adds two time values.
156    fn add(self, other: Self) -> Self::Output {
157        Self(unsafe { ffi::CFE_TIME_Add(self.0, other.0) })
158    }
159}
160
161impl Sub for SysTime {
162    type Output = Self;
163
164    /// Subtracts `other` from `self`.
165    fn sub(self, other: Self) -> Self::Output {
166        Self(unsafe { ffi::CFE_TIME_Subtract(self.0, other.0) })
167    }
168}
169
170/// Converts a subseconds value (1/2^32 seconds) to microseconds.
171pub fn subseconds_to_microseconds(subseconds: u32) -> u32 {
172    unsafe { ffi::CFE_TIME_Sub2MicroSecs(subseconds) }
173}
174
175/// Converts microseconds to a subseconds value.
176///
177/// Returns `0xFFFFFFFF` if `microseconds` exceeds 999,999
178/// (saturation).
179pub fn microseconds_to_subseconds(microseconds: u32) -> u32 {
180    unsafe { ffi::CFE_TIME_Micro2SubSecs(microseconds) }
181}
182
183/// A type alias for the callback function used for time synchronization events.
184pub type SynchCallback = unsafe extern "C" fn() -> i32;
185
186/// Registers a synchronization callback to be called on time
187/// synchronization events.
188///
189/// Only one callback per application. Should only be called from
190/// the app's main thread; distribute timing to child tasks
191/// internally.
192pub fn register_synch_callback(callback: SynchCallback) -> Result<()> {
193    check(unsafe { ffi::CFE_TIME_RegisterSynchCallback(Some(callback)) })?;
194    Ok(())
195}
196
197/// Unregisters a previously registered synchronization callback.
198pub fn unregister_synch_callback(callback: SynchCallback) -> Result<()> {
199    check(unsafe { ffi::CFE_TIME_UnregisterSynchCallback(Some(callback)) })?;
200    Ok(())
201}
202
203/// Returns the current seconds count of the mission-elapsed time.
204pub fn get_met_seconds() -> u32 {
205    unsafe { ffi::CFE_TIME_GetMETseconds() }
206}
207
208/// Returns the current sub-seconds count of the mission-elapsed time.
209pub fn get_met_subseconds() -> u32 {
210    unsafe { ffi::CFE_TIME_GetMETsubsecs() }
211}
212
213/// Returns the current value of the leap seconds counter.
214pub fn get_leap_seconds() -> i16 {
215    unsafe { ffi::CFE_TIME_GetLeapSeconds() }
216}
217
218/// Returns the current state of the spacecraft clock.
219pub fn get_clock_state() -> ffi::CFE_TIME_ClockState_Enum_t {
220    unsafe { ffi::CFE_TIME_GetClockState() }
221}
222
223/// Provides information about the spacecraft clock as a bitmask.
224pub fn get_clock_info() -> u16 {
225    unsafe { ffi::CFE_TIME_GetClockInfo() }
226}
227
228/// Provides the 1 Hz signal from an external source.
229///
230/// This may be called from an interrupt handler context.
231pub fn external_tone() {
232    unsafe { ffi::CFE_TIME_ExternalTone() };
233}
234
235/// Provides the Mission Elapsed Time from an external source.
236pub fn external_met(new_met: SysTime) {
237    unsafe { ffi::CFE_TIME_ExternalMET(new_met.0) };
238}
239
240/// Provides time from an external source like a GPS receiver.
241pub fn external_gps(new_time: SysTime, new_leaps: i16) {
242    unsafe { ffi::CFE_TIME_ExternalGPS(new_time.0, new_leaps) };
243}
244
245/// Provides time from an external source relative to a known epoch.
246pub fn external_time(new_time: SysTime) {
247    unsafe { ffi::CFE_TIME_ExternalTime(new_time.0) };
248}