diff --git a/src/ecss/scheduling.rs b/src/ecss/scheduling.rs index d9b50b1..39f1bd9 100644 --- a/src/ecss/scheduling.rs +++ b/src/ecss/scheduling.rs @@ -1,8 +1,18 @@ //! PUS Service 11 Scheduling +use core::fmt::Display; + use num_enum::{IntoPrimitive, TryFromPrimitive}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use crate::{ + time::{TimeWriter, TimestampError}, + util::ToBeBytes, + ByteConversionError, +}; + +use super::{PusError, WritablePusPacket}; + #[derive(Debug, PartialEq, Eq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(u8)] @@ -73,11 +83,103 @@ pub enum TimeWindowType { ToTimeTag = 3, } +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum ScheduleTcGenerationError { + ByteConversionError(ByteConversionError), + TimestampError(TimestampError), + PusError(PusError), +} + +impl From for ScheduleTcGenerationError { + fn from(error: ByteConversionError) -> Self { + Self::ByteConversionError(error) + } +} + +impl From for ScheduleTcGenerationError { + fn from(error: TimestampError) -> Self { + Self::TimestampError(error) + } +} + +impl From for ScheduleTcGenerationError { + fn from(error: PusError) -> Self { + Self::PusError(error) + } +} + +impl Display for ScheduleTcGenerationError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let const_str = "pus schedule tc generation:"; + match self { + Self::PusError(e) => { + write!(f, "{const_str} {e}") + } + Self::ByteConversionError(e) => { + write!(f, "{const_str} {e}") + } + Self::TimestampError(e) => { + write!(f, "{const_str} {e}") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ScheduleTcGenerationError {} + +/// Helper function to generate the application data for a PUS telecommand to insert an +/// activity into a time-based schedule according to ECSS-E-ST-70-41C 8.11.2.4 +/// +/// Please note that the N field is set to a [u16] unsigned bytefield with the value 1. +pub fn generate_insert_telecommand_app_data( + buf: &mut [u8], + release_time: &impl TimeWriter, + request: &impl WritablePusPacket, +) -> Result { + let required_len = 2 + release_time.len_written() + request.len_written(); + if required_len > buf.len() { + return Err(ByteConversionError::ToSliceTooSmall { + found: buf.len(), + expected: required_len, + } + .into()); + } + let mut current_len = 0; + let n = 1_u16; + buf[current_len..current_len + 2].copy_from_slice(&n.to_be_bytes()); + current_len += 2; + current_len += release_time + .write_to_bytes(&mut buf[current_len..current_len + release_time.len_written()])?; + current_len += + request.write_to_bytes(&mut buf[current_len..current_len + request.len_written()])?; + Ok(current_len) +} + +/// This function is similar to [generate_insert_telecommand_app_data] but returns the application +/// data as a [alloc::vec::Vec]. +#[cfg(feature = "alloc")] +pub fn generate_insert_telecommand_app_data_as_vec( + release_time: &impl TimeWriter, + request: &impl WritablePusPacket, +) -> Result, ScheduleTcGenerationError> { + let mut vec = alloc::vec::Vec::new(); + vec.extend_from_slice(&1_u16.to_be_bytes()); + vec.append(&mut release_time.to_vec()?); + vec.append(&mut request.to_vec()?); + Ok(vec) +} + #[cfg(test)] mod tests { use super::*; #[cfg(feature = "serde")] use crate::tests::generic_serde_test; + use crate::{ + ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader}, + time::cds, + PacketId, PacketSequenceCtrl, PacketType, SpHeader, + }; #[test] fn test_bool_conv_0() { @@ -122,4 +224,43 @@ mod tests { fn test_serde_time_window_type() { generic_serde_test(TimeWindowType::SelectAll); } + + #[test] + fn test_generic_insert_app_data_test() { + let time_writer = cds::TimeProvider::new_with_u16_days(1, 1); + let mut sph = SpHeader::new( + PacketId::const_new(PacketType::Tc, true, 0x002), + PacketSequenceCtrl::const_new(crate::SequenceFlags::Unsegmented, 5), + 0, + ); + let sec_header = PusTcSecondaryHeader::new_simple(17, 1); + let ping_tc = PusTcCreator::new_no_app_data(&mut sph, sec_header, true); + let mut buf: [u8; 64] = [0; 64]; + let result = generate_insert_telecommand_app_data(&mut buf, &time_writer, &ping_tc); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 2 + 7 + ping_tc.len_written()); + let n = u16::from_be_bytes(buf[0..2].try_into().unwrap()); + assert_eq!(n, 1); + let time_reader = cds::TimeProvider::from_bytes_with_u16_days(&buf[2..2 + 7]).unwrap(); + assert_eq!(time_reader, time_writer); + let pus_tc_reader = PusTcReader::new(&buf[9..]).unwrap().0; + assert_eq!(pus_tc_reader, ping_tc); + } + + #[test] + fn test_generic_insert_app_data_test_as_vec() { + let time_writer = cds::TimeProvider::new_with_u16_days(1, 1); + let mut sph = SpHeader::new( + PacketId::const_new(PacketType::Tc, true, 0x002), + PacketSequenceCtrl::const_new(crate::SequenceFlags::Unsegmented, 5), + 0, + ); + let sec_header = PusTcSecondaryHeader::new_simple(17, 1); + let ping_tc = PusTcCreator::new_no_app_data(&mut sph, sec_header, true); + let mut buf: [u8; 64] = [0; 64]; + generate_insert_telecommand_app_data(&mut buf, &time_writer, &ping_tc).unwrap(); + let vec = generate_insert_telecommand_app_data_as_vec(&time_writer, &ping_tc) + .expect("vec generation failed"); + assert_eq!(&buf[..vec.len()], vec); + } }