35 Commits

Author SHA1 Message Date
d86f1e8b88 prep v0.5.4
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-12 17:23:37 +01:00
61c2042e35 Copy and Clone derivations for CDS type level support
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-12 15:48:51 +01:00
7dddeb8743 prepare v0.5.3
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 23:46:07 +01:00
16bd0f0956 use regular UnixTimestamp ctor
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 20:27:50 +01:00
8cf6f72cf3 use proper UnixTimestamp ctor
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 18:58:32 +01:00
ac2936460f minor fix for UnixTimestamp
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 18:47:46 +01:00
0f6595afd7 fix spacepackets no_std build
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-05 01:15:34 +01:00
7aa3432f16 add more HK subservices
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-02-04 15:45:23 +01:00
cfa5f8099c added HK subservices
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-02-04 15:16:57 +01:00
8054f4091d extend ecss module definitions
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-02-04 15:13:59 +01:00
effef4609b added some generic scheduling definitions
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-04 14:41:59 +01:00
5e208e7c23 ecss module as folder
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-02-04 14:06:11 +01:00
a203f49e5d update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 22:03:43 +01:00
6a9bd8135d fixes for clippy 1.67
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:57:45 +01:00
b6df5fb4d1 allow delegate 0.8 and 0.9
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:50:57 +01:00
9108a4ec68 update changelog
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:31:29 +01:00
2b33f811eb no need to deprecate this function
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:30:25 +01:00
5d39cef6a0 Various smaller changes 2023-01-26 21:28:39 +01:00
8970ac7bc5 Merge pull request 'Fixed PartialEq Implementation for PusTm Struct, branched from fix of PusTc Implementation.' (#13) from partial_eq_fix_tm into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #13
Reviewed-by: Robin Müller <muellerr@irs.uni-stuttgart.de>
2023-01-26 21:12:42 +01:00
6c5f454728 clippy fixes
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-26 21:11:03 +01:00
ea4b6c9cba Merge remote-tracking branch 'origin/main' into partial_eq_fix_tm
All checks were successful
Rust/spacepackets/pipeline/pr-main This commit looks good
Rust/spacepackets/pipeline/head This commit looks good
2023-01-26 21:05:13 +01:00
5e9af9c226 Merge pull request 'Fixed PartialEq implementation to PusTc struct' (#12) from partial_eq_fix into main
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Reviewed-on: #12
Reviewed-by: Robin Müller <muellerr@irs.uni-stuttgart.de>
2023-01-26 21:02:39 +01:00
ad8e50c614 CHANGELOG clarification, gitignore comments
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-26 21:00:32 +01:00
db1918e2ca updated changelog
Some checks failed
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main There was a failure building this commit
2023-01-26 19:25:12 +01:00
0079e5d758 fixed implementation of partialeq 2023-01-26 19:24:41 +01:00
34bf9780af fmt and clippy
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good
2023-01-26 19:08:29 +01:00
5b021fec22 updated changelog
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-26 19:07:35 +01:00
be1c97b75a added partial equal implementation to pustc + unit tests
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit
2023-01-26 19:04:16 +01:00
1db64256fc added gitignore 2023-01-26 18:50:50 +01:00
a268903105 update NOTICE file
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
- specify that the code was developed at the IRS
2023-01-24 11:26:47 +01:00
0ce2568028 doc improvements
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 18:26:38 +01:00
b55fe9f443 Merge branch 'main' of https://egit.irs.uni-stuttgart.de/rust/spacepackets
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 16:58:05 +01:00
493a09e1a6 cargo fmt 2023-01-22 16:57:41 +01:00
f54cf69d87 use ISO8601 format in docs
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 13:18:51 +01:00
f634a57f93 funny typo
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
2023-01-22 13:11:43 +01:00
14 changed files with 457 additions and 92 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Rust
/target
/Cargo.lock
# CLion
/.idea/*
!/.idea/runConfigurations

View File

@ -8,6 +8,50 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.5.4] 2023-02-12
## Added
- `Clone` trait requirement for `time::cds::ProvidesDaysLen` trait.
- Added `Copy` and `Clone` derives for `DaysLen16Bits` and `DaysLen24Bits`.
# [v0.5.3] 2023-02-05
## Added
- `num_enum` dependency to avoid boilerplate code for primtive to enum conversions, for example
for the PUS subservices.
- `ecss.event` module containing a `Subservice` enum.
- `ecss.verification` module containing a `Subservice` enum.
- `ecss.scheduling` module containing a `Subservice` enum and some other helper enumerations.
- `ecss.hk` module containing a `Subservice` enum.
## Changed
- Added missing Service IDs to `ecss.PusServiceId` and marked in `#[non_exhaustive]`.
## Fixed
- `time.UnixTimestamp`: All constructors and `From` conversions now use the `new` constructor,
which should cause a correct conversion of 0 subsecond milliseconds to a `None` value.
# [v0.5.2] 2023-01-26
## Added
- Added `.gitignore`.
## Fixed
- Correct implementation of Trait `PartialEq` for `PusTc` and `PusTm`. The previous auto-derivation
were incorrect because they also compared fields unrelated to the raw byte representation.
## Changed
- Renamed `PusTc` `raw` method to `raw_bytes` and add better docs to avoid confusion.
Deprecate `raw` to avoid breaking change.
- Added `raw_bytes` method to `PusTm`.
# [v0.5.1] 2023-01-22
## Added
@ -24,7 +68,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Fixed
- `time::cds::TimeProvider`: Fixed a big where subsecond milliseconds were not accounted for
- `time::cds::TimeProvider`: Fixed a bug where subsecond milliseconds were not accounted for
when the provider has no submillisecond precision.
# [v0.5.0] 2023-01-20

View File

@ -1,6 +1,6 @@
[package]
name = "spacepackets"
version = "0.5.1"
version = "0.5.4"
edition = "2021"
rust-version = "1.60"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
@ -15,7 +15,11 @@ categories = ["aerospace", "aerospace::space-protocols", "no-std", "hardware-sup
[dependencies]
zerocopy = "0.6"
crc = "3"
delegate = "0.8"
delegate = ">=0.8, <0.10"
[dependencies.num_enum]
version = "0.5"
default-features = false
[dependencies.serde]
version = "1"

2
NOTICE
View File

@ -1,3 +1,3 @@
Generic implementations for various CCSDS and ECSS packet standards.
This software contains code developed at the University of Stuttgart.
This software contains code developed at the University of Stuttgart's Institute of Space Systems.

43
src/ecss/event.rs Normal file
View File

@ -0,0 +1,43 @@
//! PUS Service 5 Events
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum Subservice {
TmInfoReport = 1,
TmLowSeverityReport = 2,
TmMediumSeverityReport = 3,
TmHighSeverityReport = 4,
TcEnableEventGeneration = 5,
TcDisableEventGeneration = 6,
TcReportDisabledList = 7,
TmDisabledEventsReport = 8,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conv_into_u8() {
let subservice: u8 = Subservice::TmLowSeverityReport.into();
assert_eq!(subservice, 2);
}
#[test]
fn test_conv_from_u8() {
let subservice: Subservice = 2.try_into().unwrap();
assert_eq!(subservice, Subservice::TmLowSeverityReport);
}
#[test]
fn test_conv_fails() {
let conversion = Subservice::try_from(9);
assert!(conversion.is_err());
let err = conversion.unwrap_err();
assert_eq!(err.number, 9);
}
}

31
src/ecss/hk.rs Normal file
View File

@ -0,0 +1,31 @@
//! PUS Service 3 Housekeeping
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum Subservice {
// Regular HK
TcCreateHkReportStructure = 1,
TcDeleteHkReportStructures = 3,
TcEnableHkGeneration = 5,
TcDisableHkGeneration = 6,
TcReportHkReportStructures = 9,
TmHkPacket = 25,
TcGenerateOneShotHk = 27,
TcModifyHkCollectionInterval = 31,
// Diagnostics HK
TcCreateDiagReportStructure = 2,
TcDeleteDiagReportStructures = 4,
TcEnableDiagGeneration = 7,
TcDisableDiagGeneration = 8,
TmHkStructuresReport = 10,
TcReportDiagReportStructures = 11,
TmDiagStructuresReport = 12,
TmDiagPacket = 26,
TcGenerateOneShotDiag = 28,
TcModifyDiagCollectionInterval = 32,
}

View File

@ -1,33 +1,74 @@
//! Common definitions and helpers required to create PUS TMTC packets according to
//! [ECSS-E-ST-70-41C](https://ecss.nl/standard/ecss-e-st-70-41c-space-engineering-telemetry-and-telecommand-packet-utilization-15-april-2016/)
//!
//! You can find the PUS telecommand definitions in the [crate::tc] module and ithe PUS telemetry definitions
//! inside the [crate::tm] module.
use crate::{ByteConversionError, CcsdsPacket, SizeMissmatch};
use core::fmt::{Debug, Display, Formatter};
use core::mem::size_of;
use crc::{Crc, CRC_16_IBM_3740};
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::error::Error;
pub mod event;
pub mod hk;
pub mod scheduling;
pub mod verification;
pub type CrcType = u16;
/// CRC algorithm used by the PUS standard.
pub const CRC_CCITT_FALSE: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
pub const CCSDS_HEADER_LEN: usize = size_of::<crate::zc::SpHeader>();
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
#[non_exhaustive]
pub enum PusServiceId {
/// Service 1
Verification = 1,
/// Service 2
DeviceAccess = 2,
/// Service 3
Housekeeping = 3,
/// Service 4
ParameterStatistics = 4,
/// Service 5
Event = 5,
/// Service 6
MemoryManagement = 6,
/// Service 8
Action = 8,
/// Service 9
TimeManagement = 9,
/// Service 11
Scheduling = 11,
/// Service 12
OnBoardMonitoring = 12,
/// Service 13
LargePacketTransfer = 13,
/// Service 14
RealTimeForwardingControl = 14,
/// Service 15
StorageAndRetrival = 15,
/// Service 17
Test = 17,
/// Service 18
OpsAndProcedures = 18,
/// Service 19
EventAction = 19,
/// Service 20
Parameter = 20,
/// Service 21
RequestSequencing = 21,
/// Service 22
PositionBasedScheduling = 22,
/// Service 23
FileManagement = 23,
}
/// All PUS versions. Only PUS C is supported by this library.
@ -119,16 +160,15 @@ impl Display for PusError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
PusError::VersionNotSupported(v) => {
write!(f, "PUS version {:?} not supported", v)
write!(f, "PUS version {v:?} not supported")
}
PusError::IncorrectCrc(crc) => {
write!(f, "crc16 {:#04x} is incorrect", crc)
write!(f, "crc16 {crc:#04x} is incorrect")
}
PusError::RawDataTooShort(size) => {
write!(
f,
"deserialization error, provided raw data with size {} too short",
size
"deserialization error, provided raw data with size {size} too short"
)
}
PusError::NoRawData => {
@ -138,7 +178,7 @@ impl Display for PusError {
write!(f, "crc16 was not calculated")
}
PusError::ByteConversionError(e) => {
write!(f, "low level byte conversion error: {}", e)
write!(f, "low level byte conversion error: {e}")
}
}
}

105
src/ecss/scheduling.rs Normal file
View File

@ -0,0 +1,105 @@
//! PUS Service 11 Scheduling
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum Subservice {
// Core subservices
TcEnableScheduling = 1,
TcDisableScheduling = 2,
TcResetScheduling = 3,
TcInsertActivity = 4,
TcDeleteActivityByRequestId = 5,
TcDeleteActivitiesByFilter = 6,
// Time shift subservices
TcTimeShiftActivityWithRequestId = 7,
TcTimeShiftActivitiesByFilter = 8,
TcTimeShiftAll = 15,
// Reporting subservices
TcDetailReportByRequestId = 9,
TmDetailReport = 10,
TcDetailReportByFilter = 11,
TcSummaryReportByRequestId = 12,
TmSummaryReport = 13,
TcSummaryReportByFilter = 14,
TcDetailReportAll = 16,
TcSummaryReportAll = 17,
// Subschedule subservices
TcReportSubscheduleStatus = 18,
TmReportSubscheduleStatus = 19,
TcEnableSubschedule = 20,
TcDisableSubschedule = 21,
// Group subservices
TcCreateScheduleGroup = 22,
TcDeleteScheduleGroup = 23,
TcEnableScheduleGroup = 24,
TcDisableScheduleGroup = 25,
TcReportAllGroupsStatus = 26,
TmReportAllGroupsStatus = 27,
}
/// This status applies to sub-schedules and groups as well as specified in ECSS-E-ST-70-41C 8.11.3
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SchedStatus {
Disabled = 0,
Enabled = 1,
}
impl From<bool> for SchedStatus {
fn from(value: bool) -> Self {
if value {
SchedStatus::Enabled
} else {
SchedStatus::Disabled
}
}
}
/// Time window types as specified in ECSS-E-ST-70-41C 8.11.3
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TimeWindowType {
SelectAll = 0,
TimeTagToTimeTag = 1,
FromTimeTag = 2,
ToTimeTag = 3,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bool_conv_0() {
let enabled = true;
let status: SchedStatus = enabled.into();
assert_eq!(status, SchedStatus::Enabled)
}
#[test]
fn test_bool_conv_1() {
let enabled = false;
let status: SchedStatus = enabled.into();
assert_eq!(status, SchedStatus::Disabled)
}
#[test]
fn test_conv_into_u8() {
let subservice: u8 = Subservice::TcCreateScheduleGroup.into();
assert_eq!(subservice, 22);
}
#[test]
fn test_conv_from_u8() {
let subservice: Subservice = 22u8.try_into().unwrap();
assert_eq!(subservice, Subservice::TcCreateScheduleGroup);
}
}

35
src/ecss/verification.rs Normal file
View File

@ -0,0 +1,35 @@
//! PUS Service 1 Verification
use num_enum::{IntoPrimitive, TryFromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum Subservice {
TmAcceptanceSuccess = 1,
TmAcceptanceFailure = 2,
TmStartSuccess = 3,
TmStartFailure = 4,
TmStepSuccess = 5,
TmStepFailure = 6,
TmCompletionSuccess = 7,
TmCompletionFailure = 8,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conv_into_u8() {
let subservice: u8 = Subservice::TmCompletionSuccess.into();
assert_eq!(subservice, 7);
}
#[test]
fn test_conv_from_u8() {
let subservice: Subservice = 7.try_into().unwrap();
assert_eq!(subservice, Subservice::TmCompletionSuccess);
}
}

View File

@ -205,29 +205,35 @@ impl PusTcSecondaryHeader {
}
}
/// This class models a PUS telecommand. It is the primary data structure to generate the raw byte
/// representation of a PUS telecommand or to deserialize from one from raw bytes.
/// This class models the PUS C telecommand packet. It is the primary data structure to generate the
/// raw byte representation of a PUS telecommand or to deserialize from one from raw bytes.
///
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the
/// [serde] feature is used, which allows to send around TC packets in a raw byte format using a
/// serde provider like [postcard](https://docs.rs/postcard/latest/postcard/).
///
/// There is no spare bytes support yet.
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
///
/// # Lifetimes
///
/// * `'raw_data` - If the TC is not constructed from a raw slice, this will be the life time of
/// a buffer where the user provided application data will be serialized into. If it
/// is, this is the lifetime of the raw byte slice it is constructed from.
#[derive(Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTc<'app_data> {
pub struct PusTc<'raw_data> {
sp_header: SpHeader,
pub sec_header: PusTcSecondaryHeader,
/// If this is set to false, a manual call to [PusTc::calc_own_crc16] or
/// [PusTc::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
pub calc_crc_on_serialization: bool,
#[cfg_attr(feature = "serde", serde(skip))]
raw_data: Option<&'app_data [u8]>,
app_data: Option<&'app_data [u8]>,
raw_data: Option<&'raw_data [u8]>,
app_data: Option<&'raw_data [u8]>,
crc16: Option<u16>,
}
impl<'app_data> PusTc<'app_data> {
impl<'raw_data> PusTc<'raw_data> {
/// Generates a new struct instance.
///
/// # Arguments
@ -243,7 +249,7 @@ impl<'app_data> PusTc<'app_data> {
pub fn new(
sp_header: &mut SpHeader,
sec_header: PusTcSecondaryHeader,
app_data: Option<&'app_data [u8]>,
app_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool,
) -> Self {
sp_header.set_packet_type(PacketType::Tc);
@ -268,7 +274,7 @@ impl<'app_data> PusTc<'app_data> {
sph: &mut SpHeader,
service: u8,
subservice: u8,
app_data: Option<&'app_data [u8]>,
app_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool,
) -> Self {
Self::new(
@ -405,7 +411,7 @@ impl<'app_data> PusTc<'app_data> {
/// Create a [PusTc] instance from a raw slice. On success, it returns a tuple containing
/// the instance and the found byte length of the packet.
pub fn from_bytes(slice: &'app_data [u8]) -> Result<(Self, usize), PusError> {
pub fn from_bytes(slice: &'raw_data [u8]) -> Result<(Self, usize), PusError> {
let raw_data_len = slice.len();
if raw_data_len < PUS_TC_MIN_LEN_WITHOUT_APP_DATA {
return Err(PusError::RawDataTooShort(raw_data_len));
@ -435,11 +441,26 @@ impl<'app_data> PusTc<'app_data> {
Ok((pus_tc, total_len))
}
pub fn raw(&self) -> Option<&'app_data [u8]> {
#[deprecated(since = "0.5.2", note = "use raw_bytes() instead")]
pub fn raw(&self) -> Option<&'raw_data [u8]> {
self.raw_bytes()
}
/// If [Self] was constructed [Self::from_bytes], this function will return the slice it was
/// constructed from. Otherwise, [None] will be returned.
pub fn raw_bytes(&self) -> Option<&'raw_data [u8]> {
self.raw_data
}
}
impl PartialEq for PusTc<'_> {
fn eq(&self, other: &Self) -> bool {
self.sp_header == other.sp_header
&& self.sec_header == other.sec_header
&& self.app_data == other.app_data
}
}
//noinspection RsTraitImplementation
impl CcsdsPacket for PusTc<'_> {
ccsds_impl!();
@ -736,4 +757,20 @@ mod tests {
assert_eq!(slice[11], 0xee);
assert_eq!(slice[12], 0x63);
}
#[test]
fn partial_eq_pus_tc() {
// new vs new simple
let pus_tc_1 = base_ping_tc_simple_ctor();
let pus_tc_2 = base_ping_tc_full_ctor();
assert_eq!(pus_tc_1, pus_tc_2);
}
#[test]
fn partial_eq_serialized_vs_derialized() {
let pus_tc = base_ping_tc_simple_ctor();
let mut buf = [0; 32];
pus_tc.write_to_bytes(&mut buf).unwrap();
assert_eq!(pus_tc, PusTc::from_bytes(&buf).unwrap().0);
}
}

View File

@ -24,7 +24,7 @@ pub const MAX_DAYS_24_BITS: u32 = 2_u32.pow(24) - 1;
/// Generic trait implemented by token structs to specify the length of day field at type
/// level. This trait is only meant to be implemented in this crate and therefore sealed.
pub trait ProvidesDaysLength: Sealed {
pub trait ProvidesDaysLength: Sealed + Clone {
type FieldType: Debug
+ Copy
+ Clone
@ -38,7 +38,7 @@ pub trait ProvidesDaysLength: Sealed {
}
/// Type level token to be used as a generic parameter to [TimeProvider].
#[derive(Debug, PartialEq, Eq, Default)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct DaysLen16Bits {}
impl Sealed for DaysLen16Bits {}
@ -47,7 +47,7 @@ impl ProvidesDaysLength for DaysLen16Bits {
}
/// Type level token to be used as a generic parameter to [TimeProvider].
#[derive(Debug, PartialEq, Eq, Default)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct DaysLen24Bits {}
impl Sealed for DaysLen24Bits {}
impl ProvidesDaysLength for DaysLen24Bits {
@ -84,13 +84,12 @@ impl Display for CdsError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
CdsError::InvalidCcsdsDays(days) => {
write!(f, "invalid ccsds days {}", days)
write!(f, "invalid ccsds days {days}")
}
CdsError::InvalidCtorForDaysOfLenInPreamble(length_of_day) => {
write!(
f,
"wrong constructor for length of day {:?} detected in preamble",
length_of_day
"wrong constructor for length of day {length_of_day:?} detected in preamble",
)
}
}
@ -578,15 +577,14 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
}
#[inline]
fn calc_unix_seconds(&mut self, unix_days_seconds: i64, ms_of_day: u32) {
self.unix_stamp.unix_seconds = unix_days_seconds;
self.unix_stamp.subsecond_millis = Some((ms_of_day % 1000) as u16);
fn calc_unix_seconds(&mut self, mut unix_days_seconds: i64, ms_of_day: u32) {
let seconds_of_day = (ms_of_day / 1000) as i64;
if self.unix_stamp.unix_seconds < 0 {
self.unix_stamp.unix_seconds -= seconds_of_day;
if unix_days_seconds < 0 {
unix_days_seconds -= seconds_of_day;
} else {
self.unix_stamp.unix_seconds += seconds_of_day;
unix_days_seconds += seconds_of_day;
}
self.unix_stamp = UnixTimestamp::const_new(unix_days_seconds, (ms_of_day % 1000) as u16);
}
fn calc_date_time(&self, ns_since_last_second: u32) -> Option<DateTime<Utc>> {
@ -790,19 +788,19 @@ impl TimeProvider<DaysLen24Bits> {
/// ## Errors
///
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or
/// the CCSDS days value exceeds the allowed bit width (24 bits).
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
/// or the CCSDS days value exceeds the allowed bit width (24 bits).
pub fn from_dt_with_u24_days(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits)
}
/// Create a provider from a generic UNIX timestamp (seconds since 01-01-1970 00:00:00).
/// Create a provider from a generic UNIX timestamp (seconds since 1970-01-01T00:00:00+00:00).
///
/// ## Errors
///
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or
/// the CCSDS days value exceeds the allowed bit width (24 bits).
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
/// or the CCSDS days value exceeds the allowed bit width (24 bits).
pub fn from_unix_secs_with_u24_days(
unix_stamp: &UnixTimestamp,
) -> Result<Self, TimestampError> {
@ -879,13 +877,13 @@ impl TimeProvider<DaysLen16Bits> {
Self::from_now_generic(LengthOfDaySegment::Short16Bits)
}
/// Create a provider from a generic UNIX timestamp (seconds since 01-01-1970 00:00:00).
/// Create a provider from a generic UNIX timestamp (seconds since 1970-01-01T00:00:00+00:00).
///
/// ## Errors
///
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or
/// the CCSDS days value exceeds the allowed bit width (24 bits).
/// [TimestampError::CdsError] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00)
/// or the CCSDS days value exceeds the allowed bit width (24 bits).
pub fn from_unix_secs_with_u16_days(
unix_stamp: &UnixTimestamp,
) -> Result<Self, TimestampError> {
@ -1320,12 +1318,9 @@ mod tests {
(DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64
);
let subsecond_millis = unix_stamp.subsecond_millis;
assert!(subsecond_millis.is_some());
assert_eq!(subsecond_millis.unwrap(), 0);
assert!(subsecond_millis.is_none());
assert_eq!(time_stamper.submillis_precision(), None);
assert!(time_stamper.subsecond_millis().is_some());
assert_eq!(time_stamper.subsecond_millis().unwrap(), 0);
assert!(time_stamper.subsecond_millis().is_none());
assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds);
assert_eq!(
time_stamper.p_field(),

View File

@ -103,16 +103,16 @@ impl Display for CucError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
CucError::InvalidCounterWidth(w) => {
write!(f, "invalid cuc counter byte width {}", w)
write!(f, "invalid cuc counter byte width {w}")
}
CucError::InvalidFractionResolution(w) => {
write!(f, "invalid cuc fractional part byte width {:?}", w)
write!(f, "invalid cuc fractional part byte width {w:?}")
}
CucError::InvalidCounter(w, c) => {
write!(f, "invalid cuc counter {} for width {}", c, w)
write!(f, "invalid cuc counter {c} for width {w}")
}
CucError::InvalidFractions(w, c) => {
write!(f, "invalid cuc fractional part {} for width {:?}", c, w)
write!(f, "invalid cuc fractional part {c} for width {w:?}")
}
}
}
@ -134,8 +134,8 @@ pub struct FractionalPart(FractionalResolution, u32);
/// It has the capability to generate and read timestamps as specified in the CCSDS 301.0-B-4
/// section 3.2 . The preamble field only has one byte, which allows a time code representation
/// through the year 2094. The time is represented as a simple binary counter starting from the
/// fixed CCSDS epoch (1958-01-01 00:00:00). It is possible to provide subsecond accuracy using the
/// fractional field with various available [resolutions][FractionalResolution].
/// fixed CCSDS epoch (1958-01-01T00:00:00+00:00). It is possible to provide subsecond accuracy
/// using the fractional field with various available [resolutions][FractionalResolution].
///
/// Having a preamble field of one byte limits the width of the counter
/// type (generally seconds) to 4 bytes and the width of the fractions type to 3 bytes. This limits

View File

@ -111,21 +111,20 @@ impl Display for TimestampError {
TimestampError::InvalidTimeCode(time_code, raw_val) => {
write!(
f,
"invalid raw time code value {} for time code {:?}",
raw_val, time_code
"invalid raw time code value {raw_val} for time code {time_code:?}"
)
}
TimestampError::CdsError(e) => {
write!(f, "cds error {}", e)
write!(f, "cds error {e}")
}
TimestampError::CucError(e) => {
write!(f, "cuc error {}", e)
write!(f, "cuc error {e}")
}
TimestampError::ByteConversionError(e) => {
write!(f, "byte conversion error {}", e)
write!(f, "byte conversion error {e}")
}
TimestampError::DateBeforeCcsdsEpoch(e) => {
write!(f, "datetime with date before ccsds epoch: {}", e)
write!(f, "datetime with date before ccsds epoch: {e}")
}
TimestampError::CustomEpochNotSupported => {
write!(f, "custom epochs are not supported")
@ -157,16 +156,16 @@ pub fn seconds_since_epoch() -> f64 {
/// Convert UNIX days to CCSDS days
///
/// - CCSDS epoch: 1958 January 1
/// - UNIX Epoch: 1970 January 1
/// - CCSDS epoch: 1958-01-01T00:00:00+00:00
/// - UNIX Epoch: 1970-01-01T00:00:00+00:00
pub const fn unix_to_ccsds_days(unix_days: i64) -> i64 {
unix_days - DAYS_CCSDS_TO_UNIX as i64
}
/// Convert CCSDS days to UNIX days
///
/// - CCSDS epoch: 1958 January 1
/// - UNIX Epoch: 1970 January 1
/// - CCSDS epoch: 1958-01-01T00:00:00+00:00
/// - UNIX Epoch: 1970-01-01T00:00:00+00:00
pub const fn ccsds_to_unix_days(ccsds_days: i64) -> i64 {
ccsds_days + DAYS_CCSDS_TO_UNIX as i64
}
@ -224,16 +223,16 @@ pub trait CcsdsTimeProvider {
fn unix_seconds(&self) -> i64;
fn subsecond_millis(&self) -> Option<u16>;
fn unix_stamp(&self) -> UnixTimestamp {
UnixTimestamp {
unix_seconds: self.unix_seconds(),
subsecond_millis: self.subsecond_millis(),
if self.subsecond_millis().is_none() {
return UnixTimestamp::new_only_seconds(self.unix_seconds());
}
UnixTimestamp::const_new(self.unix_seconds(), self.subsecond_millis().unwrap())
}
fn date_time(&self) -> Option<DateTime<Utc>>;
}
/// UNIX timestamp: Elapsed seconds since 01-01-1970 00:00:00.
/// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00:00+00:00.
///
/// Also can optionally include subsecond millisecond for greater accuracy. Please note that a
/// subsecond millisecond value of 0 gets converted to [None].
@ -286,10 +285,7 @@ impl UnixTimestamp {
pub fn from_now() -> Result<Self, SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let epoch = now.as_secs();
Ok(UnixTimestamp {
unix_seconds: epoch as i64,
subsecond_millis: Some(now.subsec_millis() as u16),
})
Ok(Self::const_new(epoch as i64, now.subsec_millis() as u16))
}
#[inline]
@ -311,10 +307,7 @@ impl UnixTimestamp {
impl From<DateTime<Utc>> for UnixTimestamp {
fn from(value: DateTime<Utc>) -> Self {
Self {
unix_seconds: value.timestamp(),
subsecond_millis: Some(value.timestamp_subsec_millis() as u16),
}
Self::const_new(value.timestamp(), value.timestamp_subsec_millis() as u16)
}
}

View File

@ -189,9 +189,8 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
}
}
/// This class models a PUS telemetry and which can also be used. It is the primary data
/// structure to generate the raw byte representation of PUS telemetry or to
/// deserialize from one from raw bytes.
/// This class models the PUS C telemetry packet. It is the primary data structure to generate the
/// raw byte representation of PUS telemetry or to deserialize from one from raw bytes.
///
/// This class also derives the [serde::Serialize] and [serde::Deserialize] trait if the [serde]
/// feature is used which allows to send around TM packets in a raw byte format using a serde
@ -201,23 +200,24 @@ impl<'slice> TryFrom<zc::PusTmSecHeader<'slice>> for PusTmSecondaryHeader<'slice
///
/// # Lifetimes
///
/// * `'src_data` - Life time of a buffer where the user provided time stamp and source data will
/// be serialized into.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
/// * `'raw_data` - If the TM is not constructed from a raw slice, this will be the life time of
/// a buffer where the user provided time stamp and source data will be serialized into. If it
/// is, this is the lifetime of the raw byte slice it is constructed from.
#[derive(Eq, Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PusTm<'src_data> {
pub struct PusTm<'raw_data> {
pub sp_header: SpHeader,
pub sec_header: PusTmSecondaryHeader<'src_data>,
pub sec_header: PusTmSecondaryHeader<'raw_data>,
/// If this is set to false, a manual call to [PusTm::calc_own_crc16] or
/// [PusTm::update_packet_fields] is necessary for the serialized or cached CRC16 to be valid.
pub calc_crc_on_serialization: bool,
#[cfg_attr(feature = "serde", serde(skip))]
raw_data: Option<&'src_data [u8]>,
source_data: Option<&'src_data [u8]>,
raw_data: Option<&'raw_data [u8]>,
source_data: Option<&'raw_data [u8]>,
crc16: Option<u16>,
}
impl<'src_data> PusTm<'src_data> {
impl<'raw_data> PusTm<'raw_data> {
/// Generates a new struct instance.
///
/// # Arguments
@ -232,8 +232,8 @@ impl<'src_data> PusTm<'src_data> {
/// the correct value to this field manually
pub fn new(
sp_header: &mut SpHeader,
sec_header: PusTmSecondaryHeader<'src_data>,
source_data: Option<&'src_data [u8]>,
sec_header: PusTmSecondaryHeader<'raw_data>,
source_data: Option<&'raw_data [u8]>,
set_ccsds_len: bool,
) -> Self {
sp_header.set_packet_type(PacketType::Tm);
@ -263,11 +263,11 @@ impl<'src_data> PusTm<'src_data> {
length
}
pub fn timestamp(&self) -> Option<&'src_data [u8]> {
pub fn timestamp(&self) -> Option<&'raw_data [u8]> {
self.sec_header.timestamp
}
pub fn source_data(&self) -> Option<&'src_data [u8]> {
pub fn source_data(&self) -> Option<&'raw_data [u8]> {
self.source_data
}
@ -402,7 +402,7 @@ impl<'src_data> PusTm<'src_data> {
/// the instance and the found byte length of the packet. The timestamp length needs to be
/// known beforehand.
pub fn from_bytes(
slice: &'src_data [u8],
slice: &'raw_data [u8],
timestamp_len: usize,
) -> Result<(Self, usize), PusError> {
let raw_data_len = slice.len();
@ -442,6 +442,20 @@ impl<'src_data> PusTm<'src_data> {
verify_crc16_from_raw(raw_data, pus_tm.crc16.expect("CRC16 invalid"))?;
Ok((pus_tm, total_len))
}
/// If [Self] was constructed [Self::from_bytes], this function will return the slice it was
/// constructed from. Otherwise, [None] will be returned.
pub fn raw_bytes(&self) -> Option<&'raw_data [u8]> {
self.raw_data
}
}
impl PartialEq for PusTm<'_> {
fn eq(&self, other: &Self) -> bool {
self.sp_header == other.sp_header
&& self.sec_header == other.sec_header
&& self.source_data == other.source_data
}
}
//noinspection RsTraitImplementation
@ -486,8 +500,8 @@ mod tests {
fn base_ping_reply_full_ctor(timestamp: &[u8]) -> PusTm {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &timestamp);
PusTm::new(&mut sph, tc_header, None, true)
let tm_header = PusTmSecondaryHeader::new_simple(17, 2, &timestamp);
PusTm::new(&mut sph, tm_header, None, true)
}
fn base_hk_reply<'a>(timestamp: &'a [u8], src_data: &'a [u8]) -> PusTm<'a> {
@ -681,4 +695,21 @@ mod tests {
assert_eq!(tm.msg_counter(), 0x0000);
assert_eq!(tm.sc_time_ref_status(), 0b0000);
}
#[test]
fn partial_eq_pus_tm() {
let timestamp = dummy_timestamp();
let pus_tm_1 = base_ping_reply_full_ctor(timestamp);
let pus_tm_2 = base_ping_reply_full_ctor(timestamp);
assert_eq!(pus_tm_1, pus_tm_2);
}
#[test]
fn partial_eq_serialized_vs_derialized() {
let timestamp = dummy_timestamp();
let pus_tm = base_ping_reply_full_ctor(timestamp);
let mut buf = [0; 32];
pus_tm.write_to_bytes(&mut buf).unwrap();
assert_eq!(pus_tm, PusTm::from_bytes(&buf, timestamp.len()).unwrap().0);
}
}