add first Add and AddAssign impl for CUC #11

Merged
muellerr merged 4 commits from cuc_time_extensions into main 2023-01-18 21:53:13 +01:00
3 changed files with 80 additions and 8 deletions
Showing only changes of commit 80e1be676e - Show all commits

View File

@ -18,6 +18,8 @@ The timestamp of `PusTm` is now optional. See Added and Changed section for deta
- New `UnixTimestamp` abstraction which contains the unix seconds as an `i64` - New `UnixTimestamp` abstraction which contains the unix seconds as an `i64`
and an optional subsecond millisecond counter (`u16`) and an optional subsecond millisecond counter (`u16`)
- `MS_PER_DAY` constant. - `MS_PER_DAY` constant.
- CUC: Added `from_date_time` and `from_unix_stamp` constructors for time provider.
- CUC: Add `Add<Duration>` and `AddAssign<Duration>` impl for time provider.
### CDS time module ### CDS time module
@ -42,6 +44,9 @@ The timestamp of `PusTm` is now optional. See Added and Changed section for deta
## Changed ## Changed
- (breaking) `unix_epoch_to_ccsds_epoch`: Expect and return `i64` instead of `u64` now.
- (breaking) `ccsds_epoch_to_unix_epoch`: Expect and return `i64` instead of `u64` now.
- (breaking) `PusTmSecondaryHeader`: Timestamp is optional now, which translates to a - (breaking) `PusTmSecondaryHeader`: Timestamp is optional now, which translates to a
timestamp of size 0. timestamp of size 0.
- (breaking): `PusTm`: Renamed `time_stamp` method to `timestamp`, also returns - (breaking): `PusTm`: Renamed `time_stamp` method to `timestamp`, also returns

View File

@ -3,6 +3,7 @@
//! //!
//! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct. //! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct.
use super::*; use super::*;
use chrono::Datelike;
use core::fmt::Debug; use core::fmt::Debug;
use core::ops::{Add, AddAssign}; use core::ops::{Add, AddAssign};
use core::time::Duration; use core::time::Duration;
@ -93,6 +94,7 @@ pub fn fractional_part_from_subsec_ns(
pub enum CucError { pub enum CucError {
InvalidCounterWidth(u8), InvalidCounterWidth(u8),
InvalidFractionResolution(FractionalResolution), InvalidFractionResolution(FractionalResolution),
/// Invalid counter supplied.
InvalidCounter(u8, u64), InvalidCounter(u8, u64),
InvalidFractions(FractionalResolution, u64), InvalidFractions(FractionalResolution, u64),
} }
@ -234,7 +236,7 @@ impl TimeProviderCcsdsEpoch {
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn from_now(fraction_resolution: FractionalResolution) -> Result<Self, StdTimestampError> { pub fn from_now(fraction_resolution: FractionalResolution) -> Result<Self, StdTimestampError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs()); let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64);
if fraction_resolution == FractionalResolution::Seconds { if fraction_resolution == FractionalResolution::Seconds {
return Ok(Self::new(ccsds_epoch as u32)); return Ok(Self::new(ccsds_epoch as u32));
} }
@ -249,7 +251,7 @@ impl TimeProviderCcsdsEpoch {
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> { pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs()) as u32; self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32;
if self.fractions.is_some() { if self.fractions.is_some() {
self.fractions = fractional_part_from_subsec_ns( self.fractions = fractional_part_from_subsec_ns(
self.fractions.unwrap().0, self.fractions.unwrap().0,
@ -259,6 +261,42 @@ impl TimeProviderCcsdsEpoch {
Ok(()) Ok(())
} }
pub fn from_date_time(
dt: &DateTime<Utc>,
res: FractionalResolution,
) -> Result<Self, TimestampError> {
// Year before CCSDS epoch is invalid.
if dt.year() < 1958 {
return Err(TimestampError::DateBeforeCcsdsEpoch(*dt));
}
Self::new_generic(
WidthCounterPair(4, dt.timestamp() as u32),
fractional_part_from_subsec_ns(res, dt.timestamp_subsec_nanos() as u64),
)
.map_err(|e| e.into())
}
pub fn from_unix_stamp(
unix_stamp: &UnixTimestamp,
res: FractionalResolution,
) -> Result<Self, TimestampError> {
let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds);
// Negative CCSDS epoch is invalid.
if ccsds_epoch < 0 {
return Err(TimestampError::DateBeforeCcsdsEpoch(
unix_stamp.as_date_time().unwrap(),
));
}
if ccsds_epoch > u32::MAX as i64 {
return Err(CucError::InvalidCounter(4, ccsds_epoch as u64).into());
}
let mut fractions = None;
if let Some(subsec_millis) = unix_stamp.subsecond_millis {
fractions = fractional_part_from_subsec_ns(res, subsec_millis as u64 * 10_u64.pow(6));
}
Self::new_generic(WidthCounterPair(4, ccsds_epoch as u32), fractions).map_err(|e| e.into())
}
pub fn new_u16_counter(counter: u16) -> Self { pub fn new_u16_counter(counter: u16) -> Self {
// These values are definitely valid, so it is okay to unwrap here. // These values are definitely valid, so it is okay to unwrap here.
Self::new_generic(WidthCounterPair(2, counter as u32), None).unwrap() Self::new_generic(WidthCounterPair(2, counter as u32), None).unwrap()
@ -357,7 +395,7 @@ impl TimeProviderCcsdsEpoch {
#[inline] #[inline]
fn unix_seconds(&self) -> i64 { fn unix_seconds(&self) -> i64 {
ccsds_epoch_to_unix_epoch(self.counter.1 as u64) as i64 ccsds_epoch_to_unix_epoch(self.counter.1 as i64)
} }
/// This returns the length of the individual components of the CUC timestamp in addition /// This returns the length of the individual components of the CUC timestamp in addition
@ -627,6 +665,7 @@ fn get_provider_values_after_duration_addition(
} else { } else {
None None
}; };
increment_counter(duration.as_secs() as u32);
(new_counter, fractional_part) (new_counter, fractional_part)
} }
@ -691,6 +730,7 @@ mod tests {
let zero_cuc = zero_cuc.unwrap(); let zero_cuc = zero_cuc.unwrap();
let res = zero_cuc.write_to_bytes(&mut buf); let res = zero_cuc.write_to_bytes(&mut buf);
assert!(res.is_ok()); assert!(res.is_ok());
assert!(zero_cuc.subsecond_millis().is_none());
assert_eq!(zero_cuc.len_as_bytes(), 5); assert_eq!(zero_cuc.len_as_bytes(), 5);
assert_eq!(pfield_len(buf[0]), 1); assert_eq!(pfield_len(buf[0]), 1);
let written = res.unwrap(); let written = res.unwrap();
@ -1039,4 +1079,31 @@ mod tests {
// Assert that the maximum resolution can be reached // Assert that the maximum resolution can be reached
assert_eq!(fractions.1, 2_u32.pow(3 * 8) - 2); assert_eq!(fractions.1, 2_u32.pow(3 * 8) - 2);
} }
#[test]
fn add_duration_basic() {
let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200);
cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs);
let duration = Duration::from_millis(2500);
cuc_stamp += duration;
assert_eq!(cuc_stamp.width_counter_pair().1, 202);
let fractions = cuc_stamp.width_fractions_pair().unwrap().1;
let expected_val =
(0.5 * fractional_res_to_div(FractionalResolution::FifteenUs) as f64).floor() as u32;
assert_eq!(fractions, expected_val);
let cuc_stamp2 = cuc_stamp + Duration::from_millis(501);
// What I would roughly expect
assert_eq!(cuc_stamp2.counter.1, 203);
assert!(cuc_stamp2.fractions.unwrap().1 < 100);
assert!(cuc_stamp2.subsecond_millis().unwrap() <= 1);
}
#[test]
fn add_duration_overflow() {
let mut cuc_stamp =
TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 255), None).unwrap();
let duration = Duration::from_secs(10);
cuc_stamp += duration;
assert_eq!(cuc_stamp.counter.1, 10);
}
} }

View File

@ -170,12 +170,12 @@ pub const fn ccsds_to_unix_days(ccsds_days: i64) -> i64 {
/// Similar to [unix_to_ccsds_days] but converts the epoch instead, which is the number of elpased /// Similar to [unix_to_ccsds_days] but converts the epoch instead, which is the number of elpased
/// seconds since the CCSDS and UNIX epoch times. /// seconds since the CCSDS and UNIX epoch times.
pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: u64) -> u64 { pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: i64) -> i64 {
(unix_epoch as i64 - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)) as u64 unix_epoch - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
} }
pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: u64) -> u64 { pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: i64) -> i64 {
(ccsds_epoch as i64 + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)) as u64 ccsds_epoch + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -332,7 +332,7 @@ mod tests {
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
.unwrap(); .unwrap();
let unix_epoch = now.as_secs(); let unix_epoch = now.as_secs();
let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs()); let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u64;
assert!(ccsds_epoch > unix_epoch); assert!(ccsds_epoch > unix_epoch);
assert_eq!((ccsds_epoch - unix_epoch) % SECONDS_PER_DAY as u64, 0); assert_eq!((ccsds_epoch - unix_epoch) % SECONDS_PER_DAY as u64, 0);
let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64; let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64;