First PUS handler abstraction with request mapping
Some checks are pending
Rust/sat-rs/pipeline/head Build started...

This is the first attempt of a generic PUS service abstraction where
the PUS telecommands need to be converted into targetted requests.
This commit is contained in:
2024-02-13 18:19:45 +01:00
parent e426a7cef1
commit a5e5a1a5a0
20 changed files with 884 additions and 458 deletions

View File

@ -1,21 +1,38 @@
use crate::{pool::StoreAddr, tmtc::TargetId};
use crate::{pool::StoreAddr, TargetId};
pub type ActionId = u32;
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum ActionRequest {
ActionIdAndStoreData((ActionId, StoreAddr)),
ActionIdAndVecData((ActionId, alloc::vec::Vec<u8>)),
StringIdAndVecData((alloc::string::String, alloc::vec::Vec<u8>)),
StringIdAndStoreData((alloc::string::String, StoreAddr)),
ActionIdAndStoreData {
action_id: ActionId,
data_addr: StoreAddr,
},
ActionIdAndVecData {
action_id: ActionId,
data: alloc::vec::Vec<u8>,
},
StringIdAndVecData {
action_id: alloc::string::String,
data: alloc::vec::Vec<u8>,
},
StringIdAndStoreData {
action_id: alloc::string::String,
data: StoreAddr,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TargetedActionRequest {
target: TargetId,
hk_request: ActionRequest,
action_request: ActionRequest,
}
pub trait ActionRequestProvider {
fn route_action_request(&self, targeted_request: TargetedActionRequest);
impl TargetedActionRequest {
pub fn new(target: TargetId, action_request: ActionRequest) -> Self {
Self {
target,
action_request,
}
}
}

View File

@ -1,4 +1,7 @@
use crate::tmtc::TargetId;
use crate::{
pus::verification::{TcStateAccepted, VerificationToken},
TargetId,
};
pub type CollectionIntervalFactor = u32;
pub type UniqueId = u32;
@ -13,10 +16,25 @@ pub enum HkRequest {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TargetedHkRequest {
target: TargetId,
hk_request: HkRequest,
pub target_id: TargetId,
pub hk_request: HkRequest,
}
pub trait HkRequestProvider {
fn route_hk_request(&self, targeted_request: TargetedHkRequest);
impl TargetedHkRequest {
pub fn new(target_id: TargetId, hk_request: HkRequest) -> Self {
Self {
target_id,
hk_request,
}
}
}
pub trait PusHkRequestRouter {
type Error;
fn route(
&self,
target_id: TargetId,
hk_request: HkRequest,
token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error>;
}

View File

@ -43,6 +43,7 @@ pub mod params;
pub mod pool;
pub mod power;
pub mod pus;
pub mod queue;
pub mod request;
pub mod res_code;
pub mod seq_count;
@ -50,5 +51,8 @@ pub mod tmtc;
pub use spacepackets;
// Generic channel ID type.
/// Generic channel ID type.
pub type ChannelId = u32;
/// Generic target ID type.
pub type TargetId = u64;

View File

@ -1,9 +1,10 @@
use crate::tmtc::TargetId;
use core::mem::size_of;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use spacepackets::ByteConversionError;
use crate::TargetId;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModeAndSubmode {

View File

@ -51,7 +51,6 @@
//! assert_eq!(example_obj.id, obj_id);
//! assert_eq!(example_obj.dummy, 42);
//! ```
use crate::tmtc::TargetId;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
@ -63,6 +62,8 @@ use hashbrown::HashMap;
#[cfg(feature = "std")]
use std::error::Error;
use crate::TargetId;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct ObjectId {
pub id: TargetId,

115
satrs/src/pus/action.rs Normal file
View File

@ -0,0 +1,115 @@
use spacepackets::ecss::tc::PusTcReader;
use crate::{action::ActionRequest, TargetId};
use super::verification::{TcStateAccepted, VerificationReporterWithSender, VerificationToken};
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub use std_mod::*;
pub trait PusActionToRequestConverter {
type Error;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &mut VerificationReporterWithSender,
) -> Result<(TargetId, ActionRequest), Self::Error>;
}
pub trait PusActionRequestRouter {
type Error;
fn route(
&self,
target_id: TargetId,
hk_request: ActionRequest,
token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error>;
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std_mod {
use crate::pus::{
EcssTcInMemConverter, GenericRoutingError, PusPacketHandlerResult, PusPacketHandlingError,
PusRoutingErrorHandler, PusServiceBase, PusServiceHelper,
};
use super::*;
pub struct PusService8ActionHandler<
TcInMemConverter: EcssTcInMemConverter,
RequestConverter: PusActionToRequestConverter,
RequestRouter: PusActionRequestRouter,
RoutingErrorHandler: PusRoutingErrorHandler,
> {
service_helper: PusServiceHelper<TcInMemConverter>,
request_converter: RequestConverter,
request_router: RequestRouter,
routing_error_handler: RoutingErrorHandler,
}
impl<
TcInMemConverter: EcssTcInMemConverter,
RequestConverter: PusActionToRequestConverter<Error = PusPacketHandlingError>,
RequestRouter: PusActionRequestRouter<Error = GenericRoutingError>,
RoutingErrorHandler: PusRoutingErrorHandler<Error = GenericRoutingError>,
>
PusService8ActionHandler<
TcInMemConverter,
RequestConverter,
RequestRouter,
RoutingErrorHandler,
>
{
pub fn new(
service_helper: PusServiceHelper<TcInMemConverter>,
request_converter: RequestConverter,
request_router: RequestRouter,
routing_error_handler: RoutingErrorHandler,
) -> Self {
Self {
service_helper,
request_converter,
request_router,
routing_error_handler,
}
}
pub fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
}
let ecss_tc_and_token = possible_packet.unwrap();
let tc = self
.service_helper
.tc_in_mem_converter
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
let mut partial_error = None;
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
let (target_id, action_request) = self.request_converter.convert(
ecss_tc_and_token.token,
&tc,
&time_stamp,
&mut self.service_helper.common.verification_handler.borrow_mut(),
)?;
if let Err(e) =
self.request_router
.route(target_id, action_request, ecss_tc_and_token.token)
{
self.routing_error_handler.handle_error(
target_id,
ecss_tc_and_token.token,
&tc,
e,
&time_stamp,
&mut self.service_helper.common.verification_handler.borrow_mut(),
);
}
Ok(PusPacketHandlerResult::RequestHandled)
}
}
}

View File

@ -1 +1,110 @@
pub use spacepackets::ecss::hk::*;
use spacepackets::ecss::tc::PusTcReader;
use crate::{hk::HkRequest, TargetId};
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub use std_mod::*;
use super::{
verification::{TcStateAccepted, VerificationReporterWithSender, VerificationToken},
EcssTcInMemConverter, GenericRoutingError, PusPacketHandlerResult, PusPacketHandlingError,
PusRoutingErrorHandler, PusServiceBase, PusServiceHelper,
};
pub trait PusHkToRequestConverter {
type Error;
fn convert(
&mut self,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
time_stamp: &[u8],
verif_reporter: &mut VerificationReporterWithSender,
) -> Result<(TargetId, HkRequest), Self::Error>;
}
pub trait PusHkRequestRouter {
type Error;
fn route(
&self,
target_id: TargetId,
hk_request: HkRequest,
token: VerificationToken<TcStateAccepted>,
) -> Result<(), Self::Error>;
}
#[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
pub mod std_mod {
use super::*;
pub struct PusService3HkHandler<
TcInMemConverter: EcssTcInMemConverter,
RequestConverter: PusHkToRequestConverter,
RequestRouter: PusHkRequestRouter,
RoutingErrorHandler: PusRoutingErrorHandler,
> {
service_helper: PusServiceHelper<TcInMemConverter>,
request_converter: RequestConverter,
request_router: RequestRouter,
routing_error_handler: RoutingErrorHandler,
}
impl<
TcInMemConverter: EcssTcInMemConverter,
RequestConverter: PusHkToRequestConverter<Error = PusPacketHandlingError>,
RequestRouter: PusHkRequestRouter<Error = GenericRoutingError>,
RoutingErrorHandler: PusRoutingErrorHandler<Error = GenericRoutingError>,
>
PusService3HkHandler<TcInMemConverter, RequestConverter, RequestRouter, RoutingErrorHandler>
{
pub fn new(
service_helper: PusServiceHelper<TcInMemConverter>,
request_converter: RequestConverter,
request_router: RequestRouter,
routing_error_handler: RoutingErrorHandler,
) -> Self {
Self {
service_helper,
request_converter,
request_router,
routing_error_handler,
}
}
pub fn handle_one_tc(&mut self) -> Result<PusPacketHandlerResult, PusPacketHandlingError> {
let possible_packet = self.service_helper.retrieve_and_accept_next_packet()?;
if possible_packet.is_none() {
return Ok(PusPacketHandlerResult::Empty);
}
let ecss_tc_and_token = possible_packet.unwrap();
let tc = self
.service_helper
.tc_in_mem_converter
.convert_ecss_tc_in_memory_to_reader(&ecss_tc_and_token.tc_in_memory)?;
let mut partial_error = None;
let time_stamp = PusServiceBase::get_current_timestamp(&mut partial_error);
let (target_id, hk_request) = self.request_converter.convert(
ecss_tc_and_token.token,
&tc,
&time_stamp,
&mut self.service_helper.common.verification_handler.borrow_mut(),
)?;
if let Err(e) =
self.request_router
.route(target_id, hk_request, ecss_tc_and_token.token)
{
self.routing_error_handler.handle_error(
target_id,
ecss_tc_and_token.token,
&tc,
e,
&time_stamp,
&mut self.service_helper.common.verification_handler.borrow_mut(),
);
}
Ok(PusPacketHandlerResult::RequestHandled)
}
}
}

View File

@ -2,7 +2,8 @@
//!
//! This module contains structures to make working with the PUS C standard easier.
//! The satrs-example application contains various usage examples of these components.
use crate::ChannelId;
use crate::queue::{GenericRecvError, GenericSendError};
use crate::{ChannelId, TargetId};
use core::fmt::{Display, Formatter};
#[cfg(feature = "alloc")]
use downcast_rs::{impl_downcast, Downcast};
@ -16,6 +17,7 @@ use spacepackets::ecss::tm::PusTmCreator;
use spacepackets::ecss::PusError;
use spacepackets::{ByteConversionError, SpHeader};
pub mod action;
pub mod event;
pub mod event_man;
#[cfg(feature = "std")]
@ -37,6 +39,8 @@ use crate::pus::verification::{TcStateAccepted, TcStateToken, VerificationToken}
#[cfg(feature = "std")]
pub use std_mod::*;
use self::verification::VerificationReporterWithSender;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum PusTmWrapper<'tm> {
InStore(StoreAddr),
@ -55,52 +59,6 @@ impl<'tm> From<PusTmCreator<'tm>> for PusTmWrapper<'tm> {
}
}
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone)]
pub enum GenericSendError {
RxDisconnected,
QueueFull(Option<u32>),
}
impl Display for GenericSendError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
GenericSendError::RxDisconnected => {
write!(f, "rx side has disconnected")
}
GenericSendError::QueueFull(max_cap) => {
write!(f, "queue with max capacity of {max_cap:?} is full")
}
}
}
}
#[cfg(feature = "std")]
impl Error for GenericSendError {}
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone)]
pub enum GenericRecvError {
Empty,
TxDisconnected,
}
impl Display for GenericRecvError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::TxDisconnected => {
write!(f, "tx side has disconnected")
}
Self::Empty => {
write!(f, "nothing to receive")
}
}
}
}
#[cfg(feature = "std")]
impl Error for GenericRecvError {}
#[derive(Debug, Clone)]
pub enum EcssTmtcError {
StoreLock,
@ -385,6 +343,19 @@ mod alloc_mod {
impl<T> EcssTcReceiver for T where T: EcssTcReceiverCore + 'static {}
impl_downcast!(EcssTcReceiver);
pub trait PusRoutingErrorHandler {
type Error;
fn handle_error(
&self,
target_id: TargetId,
token: VerificationToken<TcStateAccepted>,
tc: &PusTcReader,
error: GenericRoutingError,
time_stamp: &[u8],
verif_reporter: &mut VerificationReporterWithSender,
);
}
}
#[cfg(feature = "std")]
@ -400,7 +371,7 @@ pub mod std_mod {
TryRecvTmtcError,
};
use crate::tmtc::tm_helper::SharedTmPool;
use crate::ChannelId;
use crate::{ChannelId, TargetId};
use alloc::boxed::Box;
use alloc::vec::Vec;
use crossbeam_channel as cb;
@ -662,6 +633,20 @@ pub mod std_mod {
}
}
// TODO: All these types could probably be no_std if we implemented error handling ourselves..
// but thiserror is really nice, so keep it like this for simplicity for now. Maybe thiserror
// will be no_std soon, see https://github.com/rust-lang/rust/issues/103765 .
#[derive(Debug, Clone, Error)]
pub enum GenericRoutingError {
#[error("Not enough application data, expected at least 4 bytes for target ID")]
NotEnoughAppData,
#[error("Unknown target ID {0}")]
UnknownTargetId(TargetId),
#[error("Sending action request failed: {0}")]
SendError(GenericSendError),
}
#[derive(Debug, Clone, Error)]
pub enum PusPacketHandlingError {
#[error("generic PUS error: {0}")]
@ -682,6 +667,8 @@ pub mod std_mod {
EcssTmtc(#[from] EcssTmtcError),
#[error("invalid verification token")]
InvalidVerificationToken,
#[error("request routing error: {0}")]
RequestRoutingError(#[from] GenericRoutingError),
#[error("other error {0}")]
Other(String),
}

49
satrs/src/queue.rs Normal file
View File

@ -0,0 +1,49 @@
use core::fmt::{Display, Formatter};
#[cfg(feature = "std")]
use std::error::Error;
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone)]
pub enum GenericSendError {
RxDisconnected,
QueueFull(Option<u32>),
}
impl Display for GenericSendError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
GenericSendError::RxDisconnected => {
write!(f, "rx side has disconnected")
}
GenericSendError::QueueFull(max_cap) => {
write!(f, "queue with max capacity of {max_cap:?} is full")
}
}
}
}
#[cfg(feature = "std")]
impl Error for GenericSendError {}
/// Generic error type for sending something via a message queue.
#[derive(Debug, Copy, Clone)]
pub enum GenericRecvError {
Empty,
TxDisconnected,
}
impl Display for GenericRecvError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::TxDisconnected => {
write!(f, "tx side has disconnected")
}
Self::Empty => {
write!(f, "nothing to receive")
}
}
}
}
#[cfg(feature = "std")]
impl Error for GenericRecvError {}

View File

@ -1,3 +1,85 @@
pub trait RequestRouter {
//fn route_request(&self, request: Request);
use core::fmt;
use spacepackets::{
ecss::{tc::IsPusTelecommand, PusPacket},
ByteConversionError, CcsdsPacket,
};
use thiserror::Error;
use crate::TargetId;
pub type Apid = u16;
#[derive(Debug, Error)]
pub enum TargetIdCreationError {
#[error("byte conversion")]
ByteConversion(#[from] ByteConversionError),
#[error("not enough app data to generate target ID")]
NotEnoughAppData(usize),
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct TargetAndApidId {
pub apid: Apid,
pub target: u32,
}
impl TargetAndApidId {
pub fn new(apid: Apid, target: u32) -> Self {
Self { apid, target }
}
pub fn apid(&self) -> Apid {
self.apid
}
pub fn target(&self) -> u32 {
self.target
}
pub fn raw(&self) -> TargetId {
((self.apid as u64) << 32) | (self.target as u64)
}
pub fn target_id(&self) -> TargetId {
self.raw()
}
pub fn from_pus_tc(
tc: &(impl CcsdsPacket + PusPacket + IsPusTelecommand),
) -> Result<Self, TargetIdCreationError> {
if tc.user_data().len() < 4 {
return Err(ByteConversionError::FromSliceTooSmall {
found: tc.user_data().len(),
expected: 8,
}
.into());
}
Ok(Self {
apid: tc.apid(),
target: u32::from_be_bytes(tc.user_data()[0..4].try_into().unwrap()),
})
}
}
impl From<u64> for TargetAndApidId {
fn from(raw: u64) -> Self {
Self {
apid: (raw >> 32) as u16,
target: raw as u32,
}
}
}
impl From<TargetAndApidId> for u64 {
fn from(target_and_apid_id: TargetAndApidId) -> Self {
target_and_apid_id.raw()
}
}
impl fmt::Display for TargetAndApidId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}, {}", self.apid, self.target)
}
}

View File

@ -20,8 +20,6 @@ pub use ccsds_distrib::{CcsdsDistributor, CcsdsError, CcsdsPacketHandler};
#[cfg(feature = "alloc")]
pub use pus_distrib::{PusDistributor, PusServiceProvider};
pub type TargetId = u32;
/// Generic trait for object which can receive any telecommands in form of a raw bytestream, with
/// no assumptions about the received protocol.
///