Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
d86f1e8b88 | |||
61c2042e35 | |||
7dddeb8743 | |||
16bd0f0956 | |||
8cf6f72cf3 | |||
ac2936460f | |||
0f6595afd7 | |||
7aa3432f16 | |||
cfa5f8099c | |||
8054f4091d | |||
effef4609b | |||
5e208e7c23 | |||
a203f49e5d | |||
6a9bd8135d | |||
b6df5fb4d1 | |||
9108a4ec68 | |||
2b33f811eb | |||
5d39cef6a0 | |||
8970ac7bc5 | |||
6c5f454728 | |||
ea4b6c9cba | |||
5e9af9c226 | |||
ad8e50c614 | |||
db1918e2ca | |||
0079e5d758 | |||
34bf9780af | |||
5b021fec22 | |||
be1c97b75a | |||
1db64256fc | |||
a268903105 | |||
0ce2568028 | |||
b55fe9f443 | |||
493a09e1a6 | |||
f54cf69d87 | |||
f634a57f93 |
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
# Rust
|
||||
/target
|
||||
/Cargo.lock
|
||||
|
||||
# CLion
|
||||
/.idea/*
|
||||
!/.idea/runConfigurations
|
46
CHANGELOG.md
46
CHANGELOG.md
@ -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
|
||||
|
@ -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
2
NOTICE
@ -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
43
src/ecss/event.rs
Normal 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
31
src/ecss/hk.rs
Normal 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,
|
||||
}
|
@ -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
105
src/ecss/scheduling.rs
Normal 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
35
src/ecss/verification.rs
Normal 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);
|
||||
}
|
||||
}
|
59
src/tc.rs
59
src/tc.rs
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
67
src/tm.rs
67
src/tm.rs
@ -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, ×tamp);
|
||||
PusTm::new(&mut sph, tc_header, None, true)
|
||||
let tm_header = PusTmSecondaryHeader::new_simple(17, 2, ×tamp);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user