Merge branch 'main' of https://egit.irs.uni-stuttgart.de/rust/ops-sat-rs
This commit is contained in:
53
src/ccsds.rs
53
src/ccsds.rs
@ -1,53 +0,0 @@
|
||||
use ops_sat_rs::config::components::Apid;
|
||||
use ops_sat_rs::config::APID_VALIDATOR;
|
||||
use satrs::pus::ReceivesEcssPusTc;
|
||||
use satrs::spacepackets::{CcsdsPacket, SpHeader};
|
||||
use satrs::tmtc::{CcsdsPacketHandler, ReceivesCcsdsTc};
|
||||
use satrs::ValidatorU16Id;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CcsdsReceiver<
|
||||
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone,
|
||||
E,
|
||||
> {
|
||||
pub tc_source: TcSource,
|
||||
}
|
||||
|
||||
impl<
|
||||
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone + 'static,
|
||||
E: 'static,
|
||||
> ValidatorU16Id for CcsdsReceiver<TcSource, E>
|
||||
{
|
||||
fn validate(&self, apid: u16) -> bool {
|
||||
APID_VALIDATOR.contains(&apid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
TcSource: ReceivesCcsdsTc<Error = E> + ReceivesEcssPusTc<Error = E> + Clone + 'static,
|
||||
E: 'static,
|
||||
> CcsdsPacketHandler for CcsdsReceiver<TcSource, E>
|
||||
{
|
||||
type Error = E;
|
||||
|
||||
fn handle_packet_with_valid_apid(
|
||||
&mut self,
|
||||
sp_header: &SpHeader,
|
||||
tc_raw: &[u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
if sp_header.apid() == Apid::Cfdp as u16 {
|
||||
} else {
|
||||
return self.tc_source.pass_ccsds(sp_header, tc_raw);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_packet_with_unknown_apid(
|
||||
&mut self,
|
||||
sp_header: &SpHeader,
|
||||
_tc_raw: &[u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
log::warn!("unknown APID 0x{:x?} detected", sp_header.apid());
|
||||
Ok(())
|
||||
}
|
||||
}
|
114
src/config.rs
114
src/config.rs
@ -1,30 +1,26 @@
|
||||
use lazy_static::lazy_static;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use satrs::spacepackets::{PacketId, PacketType};
|
||||
use satrs::spacepackets::PacketId;
|
||||
use satrs_mib::res_code::ResultU16Info;
|
||||
use satrs_mib::resultcode;
|
||||
use std::{collections::HashSet, net::Ipv4Addr};
|
||||
use strum::IntoEnumIterator;
|
||||
use std::env;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const STOP_FILE_NAME: &str = "stop-experiment";
|
||||
pub const HOME_FOLER_EXPERIMENT: &str = "/home/exp278";
|
||||
|
||||
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
|
||||
pub const SERVER_PORT: u16 = 7301;
|
||||
pub const TCP_SPP_SERVER_PORT: u16 = 4096;
|
||||
pub const EXPERIMENT_ID: u32 = 278;
|
||||
pub const EXPERIMENT_APID: u16 = 1024 + EXPERIMENT_ID as u16;
|
||||
pub const EXPERIMENT_PACKET_ID: PacketId = PacketId::new_for_tc(true, EXPERIMENT_APID);
|
||||
pub const VALID_PACKET_ID_LIST: &[PacketId] = &[PacketId::new_for_tc(true, EXPERIMENT_APID)];
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PACKET_ID_VALIDATOR: HashSet<PacketId> = {
|
||||
let mut set = HashSet::new();
|
||||
for id in components::Apid::iter() {
|
||||
set.insert(PacketId::new(PacketType::Tc, true, id as u16));
|
||||
}
|
||||
set
|
||||
};
|
||||
pub static ref APID_VALIDATOR: HashSet<u16> = {
|
||||
let mut set = HashSet::new();
|
||||
for id in components::Apid::iter() {
|
||||
set.insert(id as u16);
|
||||
}
|
||||
set
|
||||
};
|
||||
}
|
||||
// TODO: Would be nice if this can be commanded as well..
|
||||
/// Can be enabled to print all SPP packets received from the SPP server on port 4096.
|
||||
pub const SPP_CLIENT_WIRETAPPING_RX: bool = false;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
||||
#[repr(u8)]
|
||||
@ -38,6 +34,20 @@ pub enum GroupId {
|
||||
Tmtc = 0,
|
||||
Hk = 1,
|
||||
Mode = 2,
|
||||
Action = 3,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref HOME_PATH: PathBuf = {
|
||||
let home_path_default = env::var("HOME").expect("HOME env variable not set");
|
||||
let mut home_path = PathBuf::new();
|
||||
home_path.push(if Path::new(HOME_FOLER_EXPERIMENT).exists() {
|
||||
HOME_FOLER_EXPERIMENT
|
||||
} else {
|
||||
&home_path_default
|
||||
});
|
||||
home_path
|
||||
};
|
||||
}
|
||||
|
||||
pub mod tmtc_err {
|
||||
@ -76,47 +86,56 @@ pub mod tmtc_err {
|
||||
];
|
||||
}
|
||||
|
||||
pub mod action_err {
|
||||
use super::*;
|
||||
use satrs::res_code::ResultU16;
|
||||
|
||||
#[resultcode]
|
||||
pub const INVALID_ACTION_ID: ResultU16 = ResultU16::new(GroupId::Action as u8, 0);
|
||||
|
||||
pub const ACTION_RESULTS: &[ResultU16Info] = &[INVALID_ACTION_ID_EXT];
|
||||
}
|
||||
|
||||
pub mod components {
|
||||
use satrs::request::UniqueApidTargetId;
|
||||
use strum::EnumIter;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, EnumIter)]
|
||||
pub enum Apid {
|
||||
Sched = 1,
|
||||
GenericPus = 2,
|
||||
Cfdp = 4,
|
||||
}
|
||||
use super::EXPERIMENT_APID;
|
||||
|
||||
// Component IDs for components with the PUS APID.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PusId {
|
||||
PusEventManagement = 0,
|
||||
PusRouting = 1,
|
||||
PusTest = 2,
|
||||
PusAction = 3,
|
||||
PusMode = 4,
|
||||
PusHk = 5,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum AcsId {
|
||||
Mgm0 = 0,
|
||||
pub enum UniqueId {
|
||||
Controller = 0,
|
||||
PusEventManagement = 1,
|
||||
PusRouting = 2,
|
||||
PusTest = 3,
|
||||
PusAction = 4,
|
||||
PusMode = 5,
|
||||
PusHk = 6,
|
||||
UdpServer = 7,
|
||||
TcpServer = 8,
|
||||
TcpSppClient = 9,
|
||||
}
|
||||
|
||||
pub const CONTROLLER_ID: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::Controller as u32);
|
||||
pub const PUS_ACTION_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusAction as u32);
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::PusAction as u32);
|
||||
pub const PUS_EVENT_MANAGEMENT: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, 0);
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::PusEventManagement as u32);
|
||||
pub const PUS_ROUTING_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusRouting as u32);
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::PusRouting as u32);
|
||||
pub const PUS_TEST_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusTest as u32);
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::PusTest as u32);
|
||||
pub const PUS_MODE_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusMode as u32);
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::PusMode as u32);
|
||||
pub const PUS_HK_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusHk as u32);
|
||||
pub const PUS_SCHED_SERVICE: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(Apid::Sched as u16, 0);
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::PusHk as u32);
|
||||
pub const UDP_SERVER: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::UdpServer as u32);
|
||||
pub const TCP_SERVER: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::TcpServer as u32);
|
||||
pub const TCP_SPP_CLIENT: UniqueApidTargetId =
|
||||
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::TcpSppClient as u32);
|
||||
}
|
||||
|
||||
pub mod tasks {
|
||||
@ -124,4 +143,7 @@ pub mod tasks {
|
||||
pub const FREQ_MS_EVENT_HANDLING: u64 = 400;
|
||||
pub const FREQ_MS_AOCS: u64 = 500;
|
||||
pub const FREQ_MS_PUS_STACK: u64 = 200;
|
||||
pub const FREQ_MS_CTRL: u64 = 400;
|
||||
|
||||
pub const STOP_CHECK_FREQUENCY: u64 = 400;
|
||||
}
|
||||
|
128
src/controller.rs
Normal file
128
src/controller.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use num_enum::TryFromPrimitive;
|
||||
use satrs::{
|
||||
action::ActionRequest,
|
||||
pus::action::{ActionReplyPus, ActionReplyVariant},
|
||||
request::{GenericMessage, MessageMetadata},
|
||||
};
|
||||
use std::{
|
||||
env::temp_dir,
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic::AtomicBool, mpsc, Arc},
|
||||
};
|
||||
|
||||
use ops_sat_rs::config::{action_err::INVALID_ACTION_ID, HOME_PATH, STOP_FILE_NAME};
|
||||
|
||||
use crate::requests::CompositeRequest;
|
||||
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
#[repr(u32)]
|
||||
pub enum ActionId {
|
||||
StopExperiment = 1,
|
||||
}
|
||||
|
||||
pub struct ExperimentController {
|
||||
pub composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||
pub action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
||||
pub stop_signal: Arc<AtomicBool>,
|
||||
home_path_stop_file: PathBuf,
|
||||
tmp_path_stop_file: PathBuf,
|
||||
}
|
||||
|
||||
impl ExperimentController {
|
||||
pub fn new(
|
||||
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
let mut home_path_stop_file = PathBuf::new();
|
||||
home_path_stop_file.push(HOME_PATH.as_path());
|
||||
home_path_stop_file.push(STOP_FILE_NAME);
|
||||
let mut tmp_path_stop_file = temp_dir();
|
||||
tmp_path_stop_file.push(STOP_FILE_NAME);
|
||||
Self {
|
||||
composite_request_rx,
|
||||
action_reply_tx,
|
||||
stop_signal,
|
||||
home_path_stop_file,
|
||||
tmp_path_stop_file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExperimentController {
|
||||
pub fn perform_operation(&mut self) {
|
||||
match self.composite_request_rx.try_recv() {
|
||||
Ok(msg) => match msg.message {
|
||||
CompositeRequest::Hk(_) => {
|
||||
log::warn!("hk request handling unimplemented")
|
||||
}
|
||||
CompositeRequest::Action(action_req) => {
|
||||
self.handle_action_request(msg.requestor_info, action_req);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
if e != mpsc::TryRecvError::Empty {
|
||||
log::error!("composite request rx error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.check_stop_file();
|
||||
}
|
||||
|
||||
pub fn handle_action_request(&mut self, requestor: MessageMetadata, action_req: ActionRequest) {
|
||||
let action_id = ActionId::try_from(action_req.action_id);
|
||||
if action_id.is_err() {
|
||||
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
||||
requestor,
|
||||
action_req.action_id,
|
||||
ActionReplyVariant::CompletionFailed {
|
||||
error_code: INVALID_ACTION_ID,
|
||||
params: None,
|
||||
},
|
||||
));
|
||||
if result.is_err() {
|
||||
log::error!("sending action reply failed");
|
||||
}
|
||||
return;
|
||||
}
|
||||
let action_id = action_id.unwrap();
|
||||
match action_id {
|
||||
ActionId::StopExperiment => {
|
||||
self.stop_signal
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
||||
requestor,
|
||||
action_req.action_id,
|
||||
ActionReplyVariant::Completed,
|
||||
));
|
||||
if result.is_err() {
|
||||
log::error!("sending action reply failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_stop_file(&self) {
|
||||
let check_at_path = |path: &Path| {
|
||||
if path.exists() {
|
||||
log::warn!(
|
||||
"Detected stop file name at {:?}. Initiating experiment shutdown",
|
||||
path
|
||||
);
|
||||
// By default, clear the stop file.
|
||||
let result = std::fs::remove_file(path);
|
||||
if result.is_err() {
|
||||
log::error!(
|
||||
"failed to remove stop file at {:?}: {}",
|
||||
path,
|
||||
result.unwrap_err()
|
||||
);
|
||||
}
|
||||
self.stop_signal
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
};
|
||||
check_at_path(self.tmp_path_stop_file.as_path());
|
||||
check_at_path(self.home_path_stop_file.as_path());
|
||||
}
|
||||
}
|
1
src/interface/can.rs
Normal file
1
src/interface/can.rs
Normal file
@ -0,0 +1 @@
|
||||
//! This is a preliminary implementation of the necessary infrastructure to enable communication over OPS-SAT's internal CAN Bus.
|
@ -1,2 +1,36 @@
|
||||
pub mod tcp;
|
||||
pub mod udp;
|
||||
use derive_new::new;
|
||||
use ops_sat_rs::config::SPP_CLIENT_WIRETAPPING_RX;
|
||||
use satrs::{
|
||||
encoding::ccsds::{SpValidity, SpacePacketValidator},
|
||||
spacepackets::PacketId,
|
||||
};
|
||||
|
||||
pub mod can;
|
||||
pub mod tcp_server;
|
||||
pub mod tcp_spp_client;
|
||||
pub mod udp_server;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TcpComponent {
|
||||
Server,
|
||||
Client,
|
||||
}
|
||||
|
||||
#[derive(new, Clone)]
|
||||
pub struct SimpleSpValidator {
|
||||
component: TcpComponent,
|
||||
valid_ids: Vec<PacketId>,
|
||||
}
|
||||
|
||||
impl SpacePacketValidator for SimpleSpValidator {
|
||||
fn validate(&self, sp_header: &satrs::spacepackets::SpHeader, raw_buf: &[u8]) -> SpValidity {
|
||||
if SPP_CLIENT_WIRETAPPING_RX && self.component == TcpComponent::Client {
|
||||
log::debug!("sp header: {:?}", sp_header);
|
||||
log::debug!("raw data: {:x?}", raw_buf);
|
||||
}
|
||||
if self.valid_ids.contains(&sp_header.packet_id) {
|
||||
return SpValidity::Valid;
|
||||
}
|
||||
SpValidity::Skip
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
sync::{Arc, Mutex},
|
||||
collections::VecDeque,
|
||||
sync::{atomic::AtomicBool, mpsc, Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use log::{info, warn};
|
||||
use ops_sat_rs::config::tasks::STOP_CHECK_FREQUENCY;
|
||||
use satrs::{
|
||||
hal::std::tcp_server::{ServerConfig, TcpSpacepacketsServer},
|
||||
pus::ReceivesEcssPusTc,
|
||||
hal::std::tcp_server::{HandledConnectionHandler, ServerConfig, TcpSpacepacketsServer},
|
||||
queue::GenericSendError,
|
||||
spacepackets::PacketId,
|
||||
tmtc::{CcsdsDistributor, CcsdsError, ReceivesCcsdsTc, TmPacketSourceCore},
|
||||
tmtc::{PacketAsVec, PacketSource},
|
||||
};
|
||||
|
||||
use crate::ccsds::CcsdsReceiver;
|
||||
use super::{SimpleSpValidator, TcpComponent};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct SyncTcpTmSource {
|
||||
@ -41,7 +43,7 @@ impl SyncTcpTmSource {
|
||||
}
|
||||
}
|
||||
|
||||
impl TmPacketSourceCore for SyncTcpTmSource {
|
||||
impl PacketSource for SyncTcpTmSource {
|
||||
type Error = ();
|
||||
|
||||
fn retrieve_packet(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
@ -69,58 +71,55 @@ impl TmPacketSourceCore for SyncTcpTmSource {
|
||||
}
|
||||
}
|
||||
|
||||
pub type TcpServerType<TcSource, MpscErrorType> = TcpSpacepacketsServer<
|
||||
(),
|
||||
CcsdsError<MpscErrorType>,
|
||||
SyncTcpTmSource,
|
||||
CcsdsDistributor<CcsdsReceiver<TcSource, MpscErrorType>, MpscErrorType>,
|
||||
HashSet<PacketId>,
|
||||
>;
|
||||
#[derive(Default)]
|
||||
pub struct ConnectionFinishedHandler {}
|
||||
|
||||
pub struct TcpTask<
|
||||
TcSource: ReceivesCcsdsTc<Error = MpscErrorType>
|
||||
+ ReceivesEcssPusTc<Error = MpscErrorType>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
MpscErrorType: 'static,
|
||||
> {
|
||||
server: TcpServerType<TcSource, MpscErrorType>,
|
||||
impl HandledConnectionHandler for ConnectionFinishedHandler {
|
||||
fn handled_connection(&mut self, info: satrs::hal::std::tcp_server::HandledConnectionInfo) {
|
||||
info!(
|
||||
"Served {} TMs and {} TCs for client {:?}",
|
||||
info.num_sent_tms, info.num_received_tcs, info.addr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
TcSource: ReceivesCcsdsTc<Error = MpscErrorType>
|
||||
+ ReceivesEcssPusTc<Error = MpscErrorType>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
MpscErrorType: 'static + core::fmt::Debug,
|
||||
> TcpTask<TcSource, MpscErrorType>
|
||||
{
|
||||
pub type TcpServer = TcpSpacepacketsServer<
|
||||
SyncTcpTmSource,
|
||||
mpsc::Sender<PacketAsVec>,
|
||||
SimpleSpValidator,
|
||||
ConnectionFinishedHandler,
|
||||
(),
|
||||
GenericSendError,
|
||||
>;
|
||||
|
||||
pub struct TcpTask(pub TcpServer);
|
||||
|
||||
impl TcpTask {
|
||||
pub fn new(
|
||||
cfg: ServerConfig,
|
||||
tm_source: SyncTcpTmSource,
|
||||
tc_receiver: CcsdsDistributor<CcsdsReceiver<TcSource, MpscErrorType>, MpscErrorType>,
|
||||
packet_id_lookup: HashSet<PacketId>,
|
||||
tc_sender: mpsc::Sender<PacketAsVec>,
|
||||
valid_ids: Vec<PacketId>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
) -> Result<Self, std::io::Error> {
|
||||
Ok(Self {
|
||||
server: TcpSpacepacketsServer::new(cfg, tm_source, tc_receiver, packet_id_lookup)?,
|
||||
})
|
||||
Ok(Self(TcpSpacepacketsServer::new(
|
||||
cfg,
|
||||
tm_source,
|
||||
tc_sender,
|
||||
SimpleSpValidator::new(TcpComponent::Server, valid_ids),
|
||||
ConnectionFinishedHandler::default(),
|
||||
Some(stop_signal),
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn periodic_operation(&mut self) {
|
||||
loop {
|
||||
let result = self.server.handle_next_connection();
|
||||
match result {
|
||||
Ok(conn_result) => {
|
||||
info!(
|
||||
"Served {} TMs and {} TCs for client {:?}",
|
||||
conn_result.num_sent_tms, conn_result.num_received_tcs, conn_result.addr
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("TCP server error: {e:?}");
|
||||
}
|
||||
let result = self
|
||||
.0
|
||||
.handle_all_connections(Some(Duration::from_millis(STOP_CHECK_FREQUENCY)));
|
||||
match result {
|
||||
Ok(_conn_result) => (),
|
||||
Err(e) => {
|
||||
warn!("TCP server error: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
191
src/interface/tcp_spp_client.rs
Normal file
191
src/interface/tcp_spp_client.rs
Normal file
@ -0,0 +1,191 @@
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use mio::net::TcpStream;
|
||||
use mio::{Events, Interest, Poll, Token};
|
||||
use ops_sat_rs::config::tasks::STOP_CHECK_FREQUENCY;
|
||||
use ops_sat_rs::config::{SPP_CLIENT_WIRETAPPING_RX, TCP_SPP_SERVER_PORT};
|
||||
use satrs::encoding::ccsds::parse_buffer_for_ccsds_space_packets;
|
||||
use satrs::queue::GenericSendError;
|
||||
use satrs::spacepackets::PacketId;
|
||||
use satrs::tmtc::PacketAsVec;
|
||||
use satrs::ComponentId;
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{SimpleSpValidator, TcpComponent};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PacketForwardingError {
|
||||
#[error("send error: {0}")]
|
||||
Send(#[from] GenericSendError),
|
||||
#[error("io error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
pub struct TcpSppClient {
|
||||
id: ComponentId,
|
||||
poll: Poll,
|
||||
events: Events,
|
||||
// Optional to allow periodic reconnection attempts on the TCP server.
|
||||
client: Option<TcpStream>,
|
||||
read_buf: [u8; 4096],
|
||||
tm_tcp_client_rx: mpsc::Receiver<PacketAsVec>,
|
||||
server_addr: SocketAddr,
|
||||
tc_source_tx: mpsc::Sender<PacketAsVec>,
|
||||
validator: SimpleSpValidator,
|
||||
}
|
||||
|
||||
impl TcpSppClient {
|
||||
pub fn new(
|
||||
id: ComponentId,
|
||||
tc_source_tx: mpsc::Sender<PacketAsVec>,
|
||||
tm_tcp_client_rx: mpsc::Receiver<PacketAsVec>,
|
||||
valid_ids: &'static [PacketId],
|
||||
) -> io::Result<Self> {
|
||||
let mut poll = Poll::new()?;
|
||||
let events = Events::with_capacity(128);
|
||||
let server_addr =
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 1, 1)), TCP_SPP_SERVER_PORT);
|
||||
let client = Self::attempt_connection(&mut poll, &server_addr);
|
||||
if client.is_err() {
|
||||
log::warn!(
|
||||
"connection to TCP server {} failed: {}",
|
||||
server_addr,
|
||||
client.unwrap_err()
|
||||
);
|
||||
return Ok(Self {
|
||||
id,
|
||||
poll,
|
||||
events,
|
||||
client: None,
|
||||
read_buf: [0; 4096],
|
||||
server_addr,
|
||||
tm_tcp_client_rx,
|
||||
tc_source_tx,
|
||||
validator: SimpleSpValidator::new(TcpComponent::Client, valid_ids.to_vec()),
|
||||
});
|
||||
}
|
||||
Ok(Self {
|
||||
id,
|
||||
poll,
|
||||
events,
|
||||
client: Some(client.unwrap()),
|
||||
read_buf: [0; 4096],
|
||||
server_addr,
|
||||
tm_tcp_client_rx,
|
||||
tc_source_tx,
|
||||
validator: SimpleSpValidator::new(TcpComponent::Client, valid_ids.to_vec()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn attempt_connection(poll: &mut Poll, server_addr: &SocketAddr) -> io::Result<TcpStream> {
|
||||
let mut client = TcpStream::connect(*server_addr)?;
|
||||
poll.registry().register(
|
||||
&mut client,
|
||||
Token(0),
|
||||
Interest::READABLE | Interest::WRITABLE,
|
||||
)?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn periodic_operation(&mut self) -> Result<(), PacketForwardingError> {
|
||||
if self.client.is_some() {
|
||||
return self.perform_regular_operation();
|
||||
} else {
|
||||
let client_result = Self::attempt_connection(&mut self.poll, &self.server_addr);
|
||||
match client_result {
|
||||
Ok(client) => {
|
||||
self.client = Some(client);
|
||||
self.perform_regular_operation()?;
|
||||
}
|
||||
Err(ref e) => {
|
||||
log::warn!(
|
||||
"connection to TCP server {} failed: {}",
|
||||
self.server_addr,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn perform_regular_operation(&mut self) -> Result<(), PacketForwardingError> {
|
||||
self.poll.poll(
|
||||
&mut self.events,
|
||||
Some(Duration::from_millis(STOP_CHECK_FREQUENCY)),
|
||||
)?;
|
||||
let events: Vec<mio::event::Event> = self.events.iter().cloned().collect();
|
||||
for event in events {
|
||||
if event.token() == Token(0) {
|
||||
if event.is_readable() {
|
||||
self.read_from_server()?;
|
||||
}
|
||||
if event.is_writable() {
|
||||
self.write_to_server()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_from_server(&mut self) -> Result<(), PacketForwardingError> {
|
||||
let client = self
|
||||
.client
|
||||
.as_mut()
|
||||
.expect("TCP stream invalid when it should not be");
|
||||
match client.read(&mut self.read_buf) {
|
||||
Ok(0) => return Err(io::Error::from(io::ErrorKind::BrokenPipe).into()),
|
||||
Ok(read_bytes) => self.handle_read_bytstream(read_bytes)?,
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_to_server(&mut self) -> io::Result<()> {
|
||||
let client = self
|
||||
.client
|
||||
.as_mut()
|
||||
.expect("TCP stream invalid when it should not be");
|
||||
loop {
|
||||
match self.tm_tcp_client_rx.try_recv() {
|
||||
Ok(tm) => {
|
||||
client.write_all(&tm.packet)?;
|
||||
}
|
||||
Err(e) => match e {
|
||||
mpsc::TryRecvError::Empty => break,
|
||||
mpsc::TryRecvError::Disconnected => {
|
||||
log::error!("TM sender to TCP client has disconnected");
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_read_bytstream(
|
||||
&mut self,
|
||||
read_bytes: usize,
|
||||
) -> Result<(), PacketForwardingError> {
|
||||
let mut dummy = 0;
|
||||
if SPP_CLIENT_WIRETAPPING_RX {
|
||||
log::debug!(
|
||||
"received {} bytes on TCP client: {:x?}",
|
||||
read_bytes,
|
||||
&self.read_buf[..read_bytes]
|
||||
);
|
||||
}
|
||||
// This parser is able to deal with broken tail packets, but we ignore those for now..
|
||||
parse_buffer_for_ccsds_space_packets(
|
||||
&mut self.read_buf[..read_bytes],
|
||||
&self.validator,
|
||||
self.id,
|
||||
&self.tc_source_tx,
|
||||
&mut dummy,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -2,18 +2,18 @@ use std::net::{SocketAddr, UdpSocket};
|
||||
use std::sync::mpsc;
|
||||
|
||||
use log::{info, warn};
|
||||
use satrs::pus::PusTmAsVec;
|
||||
use satrs::{
|
||||
hal::std::udp_server::{ReceiveResult, UdpTcServer},
|
||||
tmtc::CcsdsError,
|
||||
};
|
||||
use satrs::hal::std::udp_server::{ReceiveResult, UdpTcServer};
|
||||
use satrs::queue::GenericSendError;
|
||||
use satrs::tmtc::PacketAsVec;
|
||||
|
||||
use crate::pus::HandlingStatus;
|
||||
|
||||
pub trait UdpTmHandler {
|
||||
fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr);
|
||||
}
|
||||
|
||||
pub struct DynamicUdpTmHandler {
|
||||
pub tm_rx: mpsc::Receiver<PusTmAsVec>,
|
||||
pub tm_rx: mpsc::Receiver<PacketAsVec>,
|
||||
}
|
||||
|
||||
impl UdpTmHandler for DynamicUdpTmHandler {
|
||||
@ -34,42 +34,39 @@ impl UdpTmHandler for DynamicUdpTmHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UdpTmtcServer<TmHandler: UdpTmHandler, SendError> {
|
||||
pub udp_tc_server: UdpTcServer<CcsdsError<SendError>>,
|
||||
pub struct UdpTmtcServer<TmHandler: UdpTmHandler> {
|
||||
pub udp_tc_server: UdpTcServer<mpsc::Sender<PacketAsVec>, GenericSendError>,
|
||||
pub tm_handler: TmHandler,
|
||||
}
|
||||
|
||||
impl<TmHandler: UdpTmHandler, SendError: core::fmt::Debug + 'static>
|
||||
UdpTmtcServer<TmHandler, SendError>
|
||||
{
|
||||
impl<TmHandler: UdpTmHandler> UdpTmtcServer<TmHandler> {
|
||||
pub fn periodic_operation(&mut self) {
|
||||
while self.poll_tc_server() {}
|
||||
loop {
|
||||
if self.poll_tc_server() == HandlingStatus::Empty {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(recv_addr) = self.udp_tc_server.last_sender() {
|
||||
self.tm_handler
|
||||
.send_tm_to_udp_client(&self.udp_tc_server.socket, &recv_addr);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_tc_server(&mut self) -> bool {
|
||||
fn poll_tc_server(&mut self) -> HandlingStatus {
|
||||
match self.udp_tc_server.try_recv_tc() {
|
||||
Ok(_) => true,
|
||||
Err(e) => match e {
|
||||
ReceiveResult::ReceiverError(e) => match e {
|
||||
CcsdsError::ByteConversionError(e) => {
|
||||
warn!("packet error: {e:?}");
|
||||
true
|
||||
Ok(_) => HandlingStatus::HandledOne,
|
||||
Err(e) => {
|
||||
match e {
|
||||
ReceiveResult::NothingReceived => (),
|
||||
ReceiveResult::Io(io_error) => {
|
||||
warn!("Error receiving TC from UDP server: {io_error}");
|
||||
}
|
||||
CcsdsError::CustomError(e) => {
|
||||
warn!("mpsc custom error {e:?}");
|
||||
true
|
||||
ReceiveResult::Send(send_error) => {
|
||||
warn!("error sending TM to UDP client: {send_error}");
|
||||
}
|
||||
},
|
||||
ReceiveResult::IoError(e) => {
|
||||
warn!("IO error {e}");
|
||||
false
|
||||
}
|
||||
ReceiveResult::NothingReceived => false,
|
||||
},
|
||||
HandlingStatus::Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,29 +76,35 @@ mod tests {
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
net::IpAddr,
|
||||
sync::{Arc, Mutex},
|
||||
sync::{mpsc::TryRecvError, Arc, Mutex},
|
||||
};
|
||||
|
||||
use ops_sat_rs::config::{EXPERIMENT_APID, OBSW_SERVER_ADDR};
|
||||
use satrs::{
|
||||
spacepackets::{
|
||||
ecss::{tc::PusTcCreator, WritablePusPacket},
|
||||
SpHeader,
|
||||
},
|
||||
tmtc::ReceivesTcCore,
|
||||
tmtc::PacketSenderRaw,
|
||||
ComponentId,
|
||||
};
|
||||
use ops_sat_rs::config::{components, OBSW_SERVER_ADDR};
|
||||
|
||||
use super::*;
|
||||
|
||||
const UDP_SERVER_ID: ComponentId = 0x05;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct TestReceiver {
|
||||
tc_vec: Arc<Mutex<VecDeque<Vec<u8>>>>,
|
||||
tc_vec: Arc<Mutex<VecDeque<PacketAsVec>>>,
|
||||
}
|
||||
|
||||
impl ReceivesTcCore for TestReceiver {
|
||||
type Error = CcsdsError<()>;
|
||||
fn pass_tc(&mut self, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
self.tc_vec.lock().unwrap().push_back(tc_raw.to_vec());
|
||||
impl PacketSenderRaw for TestReceiver {
|
||||
type Error = ();
|
||||
fn send_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> {
|
||||
self.tc_vec
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_back(PacketAsVec::new(sender_id, packet.to_vec()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -120,9 +123,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
||||
let test_receiver = TestReceiver::default();
|
||||
let tc_queue = test_receiver.tc_vec.clone();
|
||||
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(test_receiver)).unwrap();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, tx).unwrap();
|
||||
let tm_handler = TestTmHandler::default();
|
||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||
let mut udp_dyn_server = UdpTmtcServer {
|
||||
@ -130,16 +132,14 @@ mod tests {
|
||||
tm_handler,
|
||||
};
|
||||
udp_dyn_server.periodic_operation();
|
||||
assert!(tc_queue.lock().unwrap().is_empty());
|
||||
assert!(tm_handler_calls.lock().unwrap().is_empty());
|
||||
matches!(rx.try_recv(), Err(TryRecvError::Empty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transactions() {
|
||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0);
|
||||
let test_receiver = TestReceiver::default();
|
||||
let tc_queue = test_receiver.tc_vec.clone();
|
||||
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(test_receiver)).unwrap();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, tx).unwrap();
|
||||
let server_addr = udp_tc_server.socket.local_addr().unwrap();
|
||||
let tm_handler = TestTmHandler::default();
|
||||
let tm_handler_calls = tm_handler.addrs_to_send_to.clone();
|
||||
@ -147,7 +147,7 @@ mod tests {
|
||||
udp_tc_server,
|
||||
tm_handler,
|
||||
};
|
||||
let sph = SpHeader::new_for_unseg_tc(components::Apid::GenericPus as u16, 0, 0);
|
||||
let sph = SpHeader::new_for_unseg_tc(EXPERIMENT_APID, 0, 0);
|
||||
let ping_tc = PusTcCreator::new_simple(sph, 17, 1, &[], true)
|
||||
.to_vec()
|
||||
.unwrap();
|
||||
@ -157,10 +157,9 @@ mod tests {
|
||||
client.send(&ping_tc).unwrap();
|
||||
udp_dyn_server.periodic_operation();
|
||||
{
|
||||
let mut tc_queue = tc_queue.lock().unwrap();
|
||||
assert!(!tc_queue.is_empty());
|
||||
let received_tc = tc_queue.pop_front().unwrap();
|
||||
assert_eq!(received_tc, ping_tc);
|
||||
let packet_with_sender = rx.try_recv().unwrap();
|
||||
assert_eq!(packet_with_sender.packet, ping_tc);
|
||||
matches!(rx.try_recv(), Err(TryRecvError::Empty));
|
||||
}
|
||||
|
||||
{
|
||||
@ -171,7 +170,7 @@ mod tests {
|
||||
assert_eq!(received_addr, client_addr);
|
||||
}
|
||||
udp_dyn_server.periodic_operation();
|
||||
assert!(tc_queue.lock().unwrap().is_empty());
|
||||
matches!(rx.try_recv(), Err(TryRecvError::Empty));
|
||||
// Still tries to send to the same client.
|
||||
{
|
||||
let mut tm_handler_calls = tm_handler_calls.lock().unwrap();
|
190
src/main.rs
190
src/main.rs
@ -1,73 +1,83 @@
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::mpsc,
|
||||
sync::{atomic::AtomicBool, mpsc, Arc},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use log::info;
|
||||
use ops_sat_rs::config::tasks::FREQ_MS_PUS_STACK;
|
||||
use ops_sat_rs::config::{
|
||||
tasks::FREQ_MS_UDP_TMTC, OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT,
|
||||
};
|
||||
use satrs::{
|
||||
hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer},
|
||||
tmtc::CcsdsDistributor,
|
||||
components::{CONTROLLER_ID, TCP_SERVER, TCP_SPP_CLIENT, UDP_SERVER},
|
||||
tasks::{FREQ_MS_CTRL, FREQ_MS_PUS_STACK},
|
||||
VALID_PACKET_ID_LIST,
|
||||
};
|
||||
use ops_sat_rs::config::{tasks::FREQ_MS_UDP_TMTC, OBSW_SERVER_ADDR, SERVER_PORT};
|
||||
use satrs::hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer};
|
||||
|
||||
use crate::pus::stack::PusStack;
|
||||
use crate::pus::test::create_test_service_dynamic;
|
||||
use crate::pus::{PusReceiver, PusTcMpscRouter};
|
||||
use crate::tm_funnel::TmFunnelDynamic;
|
||||
use crate::tmtc::TcSourceTaskDynamic;
|
||||
use crate::tmtc::tc_source::TcSourceTaskDynamic;
|
||||
use crate::tmtc::tm_sink::TmFunnelDynamic;
|
||||
use crate::{controller::ExperimentController, pus::test::create_test_service};
|
||||
use crate::{
|
||||
ccsds::CcsdsReceiver,
|
||||
interface::tcp::{SyncTcpTmSource, TcpTask},
|
||||
interface::udp::{DynamicUdpTmHandler, UdpTmtcServer},
|
||||
interface::tcp_server::{SyncTcpTmSource, TcpTask},
|
||||
interface::udp_server::{DynamicUdpTmHandler, UdpTmtcServer},
|
||||
logger::setup_logger,
|
||||
tmtc::PusTcSourceProviderDynamic,
|
||||
};
|
||||
use crate::{
|
||||
interface::tcp_spp_client::TcpSppClient,
|
||||
pus::{PusTcDistributor, PusTcMpscRouter},
|
||||
};
|
||||
use crate::{
|
||||
pus::{action::create_action_service, stack::PusStack},
|
||||
requests::GenericRequestRouter,
|
||||
};
|
||||
|
||||
mod ccsds;
|
||||
mod controller;
|
||||
mod interface;
|
||||
mod logger;
|
||||
mod pus;
|
||||
mod requests;
|
||||
mod tm_funnel;
|
||||
mod tmtc;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn main() {
|
||||
setup_logger().expect("setting up logging with fern failed");
|
||||
println!("OPS-SAT Rust experiment OBSW");
|
||||
println!("OPS-SAT Rust Experiment OBSW");
|
||||
|
||||
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let (tc_source_tx, tc_source_rx) = mpsc::channel();
|
||||
let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
|
||||
let (tm_server_tx, tm_server_rx) = mpsc::channel();
|
||||
|
||||
let tc_source = PusTcSourceProviderDynamic(tc_source_tx);
|
||||
let (tm_tcp_server_tx, tm_tcp_server_rx) = mpsc::channel();
|
||||
let (tm_tcp_client_tx, tm_tcp_client_rx) = mpsc::channel();
|
||||
|
||||
let (pus_test_tx, pus_test_rx) = mpsc::channel();
|
||||
// let (pus_event_tx, pus_event_rx) = mpsc::channel();
|
||||
// let (pus_sched_tx, pus_sched_rx) = mpsc::channel();
|
||||
// let (pus_hk_tx, pus_hk_rx) = mpsc::channel();
|
||||
// let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||
let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||
// let (pus_mode_tx, pus_mode_rx) = mpsc::channel();
|
||||
|
||||
// let (_pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
|
||||
let (pus_action_reply_tx, pus_action_reply_rx) = mpsc::channel();
|
||||
// let (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::channel();
|
||||
// let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
|
||||
let (controller_composite_tx, controller_composite_rx) = mpsc::channel();
|
||||
// let (controller_action_reply_tx, controller_action_reply_rx) = mpsc::channel();
|
||||
|
||||
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
|
||||
let mut request_map = GenericRequestRouter::default();
|
||||
request_map
|
||||
.composite_router_map
|
||||
.insert(CONTROLLER_ID.id(), controller_composite_tx);
|
||||
|
||||
let pus_router = PusTcMpscRouter {
|
||||
test_tc_sender: pus_test_tx,
|
||||
// event_tc_sender: pus_event_tx,
|
||||
// sched_tc_sender: pus_sched_tx,
|
||||
// hk_tc_sender: pus_hk_tx,
|
||||
// action_tc_sender: pus_action_tx,
|
||||
action_tc_sender: pus_action_tx,
|
||||
// mode_tc_sender: pus_mode_tx,
|
||||
};
|
||||
|
||||
let pus_test_service = create_test_service_dynamic(
|
||||
let pus_test_service = create_test_service(
|
||||
tm_funnel_tx.clone(),
|
||||
// event_handler.clone_event_sender(),
|
||||
pus_test_rx,
|
||||
@ -81,12 +91,12 @@ fn main() {
|
||||
//
|
||||
// let pus_event_service =
|
||||
// create_event_service_dynamic(tm_funnel_tx.clone(), pus_event_rx, event_request_tx);
|
||||
// let pus_action_service = create_action_service_dynamic(
|
||||
// tm_funnel_tx.clone(),
|
||||
// pus_action_rx,
|
||||
// request_map.clone(),
|
||||
// pus_action_reply_rx,
|
||||
// );
|
||||
let pus_action_service = create_action_service(
|
||||
tm_funnel_tx.clone(),
|
||||
pus_action_rx,
|
||||
request_map.clone(),
|
||||
pus_action_reply_rx,
|
||||
);
|
||||
// let pus_hk_service = create_hk_service_dynamic(
|
||||
// tm_funnel_tx.clone(),
|
||||
// pus_hk_rx,
|
||||
@ -103,89 +113,165 @@ fn main() {
|
||||
pus_test_service,
|
||||
// pus_hk_service,
|
||||
// pus_event_service,
|
||||
// pus_action_service,
|
||||
pus_action_service,
|
||||
// pus_scheduler_service,
|
||||
// pus_mode_service,
|
||||
);
|
||||
|
||||
let ccsds_receiver = CcsdsReceiver { tc_source };
|
||||
|
||||
let mut tmtc_task = TcSourceTaskDynamic::new(
|
||||
tc_source_rx,
|
||||
PusReceiver::new(tm_funnel_tx.clone(), pus_router),
|
||||
PusTcDistributor::new(tm_funnel_tx.clone(), pus_router),
|
||||
);
|
||||
|
||||
let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT);
|
||||
let udp_ccsds_distributor = CcsdsDistributor::new(ccsds_receiver.clone());
|
||||
let udp_tc_server = UdpTcServer::new(sock_addr, 2048, Box::new(udp_ccsds_distributor))
|
||||
let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source_tx.clone())
|
||||
.expect("creating UDP TMTC server failed");
|
||||
let mut udp_tmtc_server = UdpTmtcServer {
|
||||
udp_tc_server,
|
||||
tm_handler: DynamicUdpTmHandler {
|
||||
tm_rx: tm_server_rx,
|
||||
tm_rx: tm_tcp_server_rx,
|
||||
},
|
||||
};
|
||||
|
||||
let tcp_ccsds_distributor = CcsdsDistributor::new(ccsds_receiver);
|
||||
let tcp_server_cfg = ServerConfig::new(sock_addr, Duration::from_millis(400), 4096, 8192);
|
||||
let tcp_server_cfg = ServerConfig::new(
|
||||
TCP_SERVER.id(),
|
||||
sock_addr,
|
||||
Duration::from_millis(400),
|
||||
4096,
|
||||
8192,
|
||||
);
|
||||
let sync_tm_tcp_source = SyncTcpTmSource::new(200);
|
||||
let mut tcp_server = TcpTask::new(
|
||||
tcp_server_cfg,
|
||||
sync_tm_tcp_source.clone(),
|
||||
tcp_ccsds_distributor,
|
||||
PACKET_ID_VALIDATOR.clone(),
|
||||
tc_source_tx.clone(),
|
||||
VALID_PACKET_ID_LIST.to_vec(),
|
||||
stop_signal.clone(),
|
||||
)
|
||||
.expect("tcp server creation failed");
|
||||
|
||||
let mut tm_funnel = TmFunnelDynamic::new(sync_tm_tcp_source, tm_funnel_rx, tm_server_tx);
|
||||
let mut tm_funnel = TmFunnelDynamic::new(
|
||||
sync_tm_tcp_source,
|
||||
tm_funnel_rx,
|
||||
tm_tcp_server_tx,
|
||||
tm_tcp_client_tx,
|
||||
stop_signal.clone(),
|
||||
);
|
||||
|
||||
let mut controller = ExperimentController::new(
|
||||
controller_composite_rx,
|
||||
pus_action_reply_tx,
|
||||
stop_signal.clone(),
|
||||
);
|
||||
|
||||
let mut tcp_spp_client = TcpSppClient::new(
|
||||
TCP_SPP_CLIENT.id(),
|
||||
tc_source_tx,
|
||||
tm_tcp_client_rx,
|
||||
VALID_PACKET_ID_LIST,
|
||||
)
|
||||
.expect("creating TCP SPP client failed");
|
||||
|
||||
info!("Starting CTRL task");
|
||||
let ctrl_stop_signal = stop_signal.clone();
|
||||
let jh_ctrl_thread = thread::Builder::new()
|
||||
.name("ops-sat ctrl".to_string())
|
||||
.spawn(move || loop {
|
||||
controller.perform_operation();
|
||||
if ctrl_stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(FREQ_MS_CTRL));
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
info!("Starting TMTC and UDP task");
|
||||
let tmtc_stop_signal = stop_signal.clone();
|
||||
let jh_udp_tmtc = thread::Builder::new()
|
||||
.name("TMTC and UDP".to_string())
|
||||
.name("ops-sat tmtc-udp".to_string())
|
||||
.spawn(move || {
|
||||
info!("Running UDP server on port {SERVER_PORT}");
|
||||
loop {
|
||||
udp_tmtc_server.periodic_operation();
|
||||
tmtc_task.periodic_operation();
|
||||
if tmtc_stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC));
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
info!("Starting TCP task");
|
||||
let jh_tcp = thread::Builder::new()
|
||||
.name("TCP".to_string())
|
||||
let tcp_server_stop_signal = stop_signal.clone();
|
||||
info!("Starting TCP server task");
|
||||
let jh_tcp_server = thread::Builder::new()
|
||||
.name("ops-sat tcp-server".to_string())
|
||||
.spawn(move || {
|
||||
info!("Running TCP server on port {SERVER_PORT}");
|
||||
loop {
|
||||
tcp_server.periodic_operation();
|
||||
if tcp_server_stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// We could also move this to the existing TCP server thread, but we would have to adapt
|
||||
// the server code for this so we do not block anymore and we pause manually if both the client
|
||||
// and server are IDLE and have nothing to do..
|
||||
let tcp_client_stop_signal = stop_signal.clone();
|
||||
info!("Starting TCP SPP client task");
|
||||
let jh_tcp_client = thread::Builder::new()
|
||||
.name("ops-sat tcp-client".to_string())
|
||||
.spawn(move || {
|
||||
info!("Running TCP SPP client");
|
||||
loop {
|
||||
let _result = tcp_spp_client.periodic_operation();
|
||||
if tcp_client_stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
info!("Starting TM funnel task");
|
||||
let funnel_stop_signal = stop_signal.clone();
|
||||
let jh_tm_funnel = thread::Builder::new()
|
||||
.name("TM Funnel".to_string())
|
||||
.name("ops-sat tm-funnel".to_string())
|
||||
.spawn(move || loop {
|
||||
tm_funnel.operation();
|
||||
if funnel_stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
info!("Starting PUS handler thread");
|
||||
let pus_stop_signal = stop_signal.clone();
|
||||
let jh_pus_handler = thread::Builder::new()
|
||||
.name("PUS".to_string())
|
||||
.name("ops-sat pus".to_string())
|
||||
.spawn(move || loop {
|
||||
pus_stack.periodic_operation();
|
||||
if pus_stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
jh_ctrl_thread
|
||||
.join()
|
||||
.expect("Joining Controller thread failed");
|
||||
jh_udp_tmtc
|
||||
.join()
|
||||
.expect("Joining UDP TMTC server thread failed");
|
||||
jh_tcp
|
||||
jh_tcp_server
|
||||
.join()
|
||||
.expect("Joining TCP TMTC server thread failed");
|
||||
jh_tcp_client
|
||||
.join()
|
||||
.expect("Joining TCP TMTC client thread failed");
|
||||
jh_tm_funnel
|
||||
.join()
|
||||
.expect("Joining TM Funnel thread failed");
|
||||
|
716
src/pus/action.rs
Normal file
716
src/pus/action.rs
Normal file
@ -0,0 +1,716 @@
|
||||
use log::{error, warn};
|
||||
use ops_sat_rs::config::components::PUS_ACTION_SERVICE;
|
||||
use ops_sat_rs::config::tmtc_err;
|
||||
use satrs::action::{ActionRequest, ActionRequestVariant};
|
||||
use satrs::params::WritableToBeBytes;
|
||||
use satrs::pus::action::{
|
||||
ActionReplyPus, ActionReplyVariant, ActivePusActionRequestStd, DefaultActiveActionRequestMap,
|
||||
};
|
||||
use satrs::pus::verification::{
|
||||
FailParams, FailParamsWithStep, TcStateAccepted, TcStateStarted, VerificationReporter,
|
||||
VerificationReportingProvider, VerificationToken,
|
||||
};
|
||||
use satrs::pus::{
|
||||
ActiveRequestProvider, EcssTcAndToken, EcssTcInVecConverter, EcssTmSender, EcssTmtcError,
|
||||
GenericConversionError, PusPacketHandlerResult, PusReplyHandler, PusServiceHelper,
|
||||
PusTcToRequestConverter,
|
||||
};
|
||||
use satrs::request::{GenericMessage, UniqueApidTargetId};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket};
|
||||
use satrs::tmtc::PacketAsVec;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::requests::GenericRequestRouter;
|
||||
|
||||
use super::{
|
||||
create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus,
|
||||
PusTargetedRequestService, TargetedPusService,
|
||||
};
|
||||
|
||||
pub struct ActionReplyHandler {
|
||||
fail_data_buf: [u8; 128],
|
||||
}
|
||||
|
||||
impl Default for ActionReplyHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fail_data_buf: [0; 128],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PusReplyHandler<ActivePusActionRequestStd, ActionReplyPus> for ActionReplyHandler {
|
||||
type Error = EcssTmtcError;
|
||||
|
||||
fn handle_unrequested_reply(
|
||||
&mut self,
|
||||
reply: &GenericMessage<ActionReplyPus>,
|
||||
_tm_sender: &impl EcssTmSender,
|
||||
) -> Result<(), Self::Error> {
|
||||
warn!("received unexpected reply for service 8: {reply:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_reply(
|
||||
&mut self,
|
||||
reply: &GenericMessage<ActionReplyPus>,
|
||||
active_request: &ActivePusActionRequestStd,
|
||||
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||
verification_handler: &impl VerificationReportingProvider,
|
||||
time_stamp: &[u8],
|
||||
) -> Result<bool, Self::Error> {
|
||||
let verif_token: VerificationToken<TcStateStarted> = active_request
|
||||
.token()
|
||||
.try_into()
|
||||
.expect("invalid token state");
|
||||
let remove_entry = match &reply.message.variant {
|
||||
ActionReplyVariant::CompletionFailed { error_code, params } => {
|
||||
let mut fail_data_len = 0;
|
||||
if let Some(params) = params {
|
||||
fail_data_len = params.write_to_be_bytes(&mut self.fail_data_buf)?;
|
||||
}
|
||||
verification_handler.completion_failure(
|
||||
tm_sender,
|
||||
verif_token,
|
||||
FailParams::new(time_stamp, error_code, &self.fail_data_buf[..fail_data_len]),
|
||||
)?;
|
||||
true
|
||||
}
|
||||
ActionReplyVariant::StepFailed {
|
||||
error_code,
|
||||
step,
|
||||
params,
|
||||
} => {
|
||||
let mut fail_data_len = 0;
|
||||
if let Some(params) = params {
|
||||
fail_data_len = params.write_to_be_bytes(&mut self.fail_data_buf)?;
|
||||
}
|
||||
verification_handler.step_failure(
|
||||
tm_sender,
|
||||
verif_token,
|
||||
FailParamsWithStep::new(
|
||||
time_stamp,
|
||||
&EcssEnumU16::new(*step),
|
||||
error_code,
|
||||
&self.fail_data_buf[..fail_data_len],
|
||||
),
|
||||
)?;
|
||||
true
|
||||
}
|
||||
ActionReplyVariant::Completed => {
|
||||
verification_handler.completion_success(tm_sender, verif_token, time_stamp)?;
|
||||
true
|
||||
}
|
||||
ActionReplyVariant::StepSuccess { step } => {
|
||||
verification_handler.step_success(
|
||||
tm_sender,
|
||||
&verif_token,
|
||||
time_stamp,
|
||||
EcssEnumU16::new(*step),
|
||||
)?;
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
Ok(remove_entry)
|
||||
}
|
||||
|
||||
fn handle_request_timeout(
|
||||
&mut self,
|
||||
active_request: &ActivePusActionRequestStd,
|
||||
tm_sender: &impl EcssTmSender,
|
||||
verification_handler: &impl VerificationReportingProvider,
|
||||
time_stamp: &[u8],
|
||||
) -> Result<(), Self::Error> {
|
||||
generic_pus_request_timeout_handler(
|
||||
tm_sender,
|
||||
active_request,
|
||||
verification_handler,
|
||||
time_stamp,
|
||||
"action",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ActionRequestConverter {}
|
||||
|
||||
impl PusTcToRequestConverter<ActivePusActionRequestStd, ActionRequest> for ActionRequestConverter {
|
||||
type Error = GenericConversionError;
|
||||
|
||||
fn convert(
|
||||
&mut self,
|
||||
token: VerificationToken<TcStateAccepted>,
|
||||
tc: &PusTcReader,
|
||||
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||
verif_reporter: &impl VerificationReportingProvider,
|
||||
time_stamp: &[u8],
|
||||
) -> Result<(ActivePusActionRequestStd, ActionRequest), Self::Error> {
|
||||
let subservice = tc.subservice();
|
||||
let user_data = tc.user_data();
|
||||
if user_data.len() < 8 {
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
tm_sender,
|
||||
token,
|
||||
FailParams::new_no_fail_data(time_stamp, &tmtc_err::NOT_ENOUGH_APP_DATA),
|
||||
)
|
||||
.expect("Sending start failure failed");
|
||||
return Err(GenericConversionError::NotEnoughAppData {
|
||||
expected: 8,
|
||||
found: user_data.len(),
|
||||
});
|
||||
}
|
||||
let target_id_and_apid = UniqueApidTargetId::from_pus_tc(tc).unwrap();
|
||||
let action_id = u32::from_be_bytes(user_data[4..8].try_into().unwrap());
|
||||
if subservice == 128 {
|
||||
let req_variant = if user_data.len() == 8 {
|
||||
ActionRequestVariant::NoData
|
||||
} else {
|
||||
ActionRequestVariant::VecData(user_data[8..].to_vec())
|
||||
};
|
||||
Ok((
|
||||
ActivePusActionRequestStd::new(
|
||||
action_id,
|
||||
target_id_and_apid.into(),
|
||||
token.into(),
|
||||
Duration::from_secs(30),
|
||||
),
|
||||
ActionRequest::new(action_id, req_variant),
|
||||
))
|
||||
} else {
|
||||
verif_reporter
|
||||
.start_failure(
|
||||
tm_sender,
|
||||
token,
|
||||
FailParams::new_no_fail_data(time_stamp, &tmtc_err::INVALID_PUS_SUBSERVICE),
|
||||
)
|
||||
.expect("Sending start failure failed");
|
||||
Err(GenericConversionError::InvalidSubservice(subservice))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_action_service(
|
||||
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||
pus_action_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
action_router: GenericRequestRouter,
|
||||
reply_receiver: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
||||
) -> ActionServiceWrapper {
|
||||
let action_request_handler = PusTargetedRequestService::new(
|
||||
PusServiceHelper::new(
|
||||
PUS_ACTION_SERVICE.id(),
|
||||
pus_action_rx,
|
||||
tm_funnel_tx,
|
||||
create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid),
|
||||
EcssTcInVecConverter::default(),
|
||||
),
|
||||
ActionRequestConverter::default(),
|
||||
DefaultActiveActionRequestMap::default(),
|
||||
ActionReplyHandler::default(),
|
||||
action_router,
|
||||
reply_receiver,
|
||||
);
|
||||
ActionServiceWrapper {
|
||||
service: action_request_handler,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ActionServiceWrapper {
|
||||
pub(crate) service: PusTargetedRequestService<
|
||||
VerificationReporter,
|
||||
ActionRequestConverter,
|
||||
ActionReplyHandler,
|
||||
DefaultActiveActionRequestMap,
|
||||
ActivePusActionRequestStd,
|
||||
ActionRequest,
|
||||
ActionReplyPus,
|
||||
>,
|
||||
}
|
||||
|
||||
impl TargetedPusService for ActionServiceWrapper {
|
||||
/// Returns [true] if the packet handling is finished.
|
||||
fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||
match self.service.poll_and_handle_next_tc(time_stamp) {
|
||||
Ok(result) => match result {
|
||||
PusPacketHandlerResult::RequestHandled => {}
|
||||
PusPacketHandlerResult::RequestHandledPartialSuccess(e) => {
|
||||
warn!("PUS 8 partial packet handling success: {e:?}")
|
||||
}
|
||||
PusPacketHandlerResult::CustomSubservice(invalid, _) => {
|
||||
warn!("PUS 8 invalid subservice {invalid}");
|
||||
}
|
||||
PusPacketHandlerResult::SubserviceNotImplemented(subservice, _) => {
|
||||
warn!("PUS 8 subservice {subservice} not implemented");
|
||||
}
|
||||
PusPacketHandlerResult::Empty => return HandlingStatus::Empty,
|
||||
},
|
||||
Err(error) => {
|
||||
error!("PUS packet handling error: {error:?}");
|
||||
return HandlingStatus::Empty;
|
||||
}
|
||||
}
|
||||
HandlingStatus::HandledOne
|
||||
}
|
||||
|
||||
fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||
// This only fails if all senders disconnected. Treat it like an empty queue.
|
||||
self.service
|
||||
.poll_and_check_next_reply(time_stamp)
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("PUS 8: Handling reply failed with error {e:?}");
|
||||
HandlingStatus::Empty
|
||||
})
|
||||
}
|
||||
|
||||
fn check_for_request_timeouts(&mut self) {
|
||||
self.service.check_for_request_timeouts();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use satrs::pus::test_util::{
|
||||
TEST_APID, TEST_COMPONENT_ID_0, TEST_COMPONENT_ID_1, TEST_UNIQUE_ID_0, TEST_UNIQUE_ID_1,
|
||||
};
|
||||
use satrs::pus::verification;
|
||||
use satrs::pus::verification::test_util::TestVerificationReporter;
|
||||
use satrs::request::MessageMetadata;
|
||||
use satrs::ComponentId;
|
||||
use satrs::{
|
||||
res_code::ResultU16,
|
||||
spacepackets::{
|
||||
ecss::{
|
||||
tc::{PusTcCreator, PusTcSecondaryHeader},
|
||||
tm::PusTmReader,
|
||||
WritablePusPacket,
|
||||
},
|
||||
SpHeader,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
pus::tests::{PusConverterTestbench, ReplyHandlerTestbench, TargetedPusRequestTestbench},
|
||||
requests::CompositeRequest,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl
|
||||
TargetedPusRequestTestbench<
|
||||
ActionRequestConverter,
|
||||
ActionReplyHandler,
|
||||
DefaultActiveActionRequestMap,
|
||||
ActivePusActionRequestStd,
|
||||
ActionRequest,
|
||||
ActionReplyPus,
|
||||
>
|
||||
{
|
||||
pub fn new_for_action(owner_id: ComponentId, target_id: ComponentId) -> Self {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
|
||||
let (pus_action_tx, pus_action_rx) = mpsc::channel();
|
||||
let (action_reply_tx, action_reply_rx) = mpsc::channel();
|
||||
let (action_req_tx, action_req_rx) = mpsc::channel();
|
||||
let verif_reporter = TestVerificationReporter::new(owner_id);
|
||||
let mut generic_req_router = GenericRequestRouter::default();
|
||||
generic_req_router
|
||||
.composite_router_map
|
||||
.insert(target_id, action_req_tx);
|
||||
Self {
|
||||
service: PusTargetedRequestService::new(
|
||||
PusServiceHelper::new(
|
||||
owner_id,
|
||||
pus_action_rx,
|
||||
tm_funnel_tx.clone(),
|
||||
verif_reporter,
|
||||
EcssTcInVecConverter::default(),
|
||||
),
|
||||
ActionRequestConverter::default(),
|
||||
DefaultActiveActionRequestMap::default(),
|
||||
ActionReplyHandler::default(),
|
||||
generic_req_router,
|
||||
action_reply_rx,
|
||||
),
|
||||
request_id: None,
|
||||
pus_packet_tx: pus_action_tx,
|
||||
tm_funnel_rx,
|
||||
reply_tx: action_reply_tx,
|
||||
request_rx: action_req_rx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_packet_started(&self) {
|
||||
self.service
|
||||
.service_helper
|
||||
.common
|
||||
.verif_reporter
|
||||
.check_next_is_started_success(
|
||||
self.service.service_helper.id(),
|
||||
self.request_id.expect("request ID not set").into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn verify_packet_completed(&self) {
|
||||
self.service
|
||||
.service_helper
|
||||
.common
|
||||
.verif_reporter
|
||||
.check_next_is_completion_success(
|
||||
self.service.service_helper.id(),
|
||||
self.request_id.expect("request ID not set").into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn verify_tm_empty(&self) {
|
||||
let packet = self.tm_funnel_rx.try_recv();
|
||||
if let Err(mpsc::TryRecvError::Empty) = packet {
|
||||
} else {
|
||||
let tm = packet.unwrap();
|
||||
let unexpected_tm = PusTmReader::new(&tm.packet, 7).unwrap().0;
|
||||
panic!("unexpected TM packet {unexpected_tm:?}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_next_tc_is_handled_properly(&mut self, time_stamp: &[u8]) {
|
||||
let result = self.service.poll_and_handle_next_tc(time_stamp);
|
||||
if let Err(e) = result {
|
||||
panic!("unexpected error {:?}", e);
|
||||
}
|
||||
let result = result.unwrap();
|
||||
match result {
|
||||
PusPacketHandlerResult::RequestHandled => (),
|
||||
_ => panic!("unexpected result {result:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_all_tcs_handled(&mut self, time_stamp: &[u8]) {
|
||||
let result = self.service.poll_and_handle_next_tc(time_stamp);
|
||||
if let Err(e) = result {
|
||||
panic!("unexpected error {:?}", e);
|
||||
}
|
||||
let result = result.unwrap();
|
||||
match result {
|
||||
PusPacketHandlerResult::Empty => (),
|
||||
_ => panic!("unexpected result {result:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_next_reply_is_handled_properly(&mut self, time_stamp: &[u8]) {
|
||||
let result = self.service.poll_and_check_next_reply(time_stamp);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), HandlingStatus::HandledOne);
|
||||
}
|
||||
|
||||
pub fn verify_all_replies_handled(&mut self, time_stamp: &[u8]) {
|
||||
let result = self.service.poll_and_check_next_reply(time_stamp);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), HandlingStatus::Empty);
|
||||
}
|
||||
|
||||
pub fn add_tc(&mut self, tc: &PusTcCreator) {
|
||||
self.request_id = Some(verification::RequestId::new(tc).into());
|
||||
let token = self.service.service_helper.verif_reporter_mut().add_tc(tc);
|
||||
let accepted_token = self
|
||||
.service
|
||||
.service_helper
|
||||
.verif_reporter()
|
||||
.acceptance_success(self.service.service_helper.tm_sender(), token, &[0; 7])
|
||||
.expect("TC acceptance failed");
|
||||
self.service
|
||||
.service_helper
|
||||
.verif_reporter()
|
||||
.check_next_was_added(accepted_token.request_id());
|
||||
let id = self.service.service_helper.id();
|
||||
self.service
|
||||
.service_helper
|
||||
.verif_reporter()
|
||||
.check_next_is_acceptance_success(id, accepted_token.request_id());
|
||||
self.pus_packet_tx
|
||||
.send(EcssTcAndToken::new(tc.to_vec().unwrap(), accepted_token))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_request() {
|
||||
let mut testbench = TargetedPusRequestTestbench::new_for_action(
|
||||
TEST_COMPONENT_ID_0.id(),
|
||||
TEST_COMPONENT_ID_1.id(),
|
||||
);
|
||||
// Create a basic action request and verify forwarding.
|
||||
let sp_header = SpHeader::new_from_apid(TEST_APID);
|
||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
||||
let action_id = 5_u32;
|
||||
let mut app_data: [u8; 8] = [0; 8];
|
||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_1.to_be_bytes());
|
||||
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||
let pus8_packet = PusTcCreator::new(sp_header, sec_header, &app_data, true);
|
||||
testbench.add_tc(&pus8_packet);
|
||||
let time_stamp: [u8; 7] = [0; 7];
|
||||
testbench.verify_next_tc_is_handled_properly(&time_stamp);
|
||||
testbench.verify_all_tcs_handled(&time_stamp);
|
||||
|
||||
testbench.verify_packet_started();
|
||||
|
||||
let possible_req = testbench.request_rx.try_recv();
|
||||
assert!(possible_req.is_ok());
|
||||
let req = possible_req.unwrap();
|
||||
if let CompositeRequest::Action(action_req) = req.message {
|
||||
assert_eq!(action_req.action_id, action_id);
|
||||
assert_eq!(action_req.variant, ActionRequestVariant::NoData);
|
||||
let action_reply = ActionReplyPus::new(action_id, ActionReplyVariant::Completed);
|
||||
testbench
|
||||
.reply_tx
|
||||
.send(GenericMessage::new(req.requestor_info, action_reply))
|
||||
.unwrap();
|
||||
} else {
|
||||
panic!("unexpected request type");
|
||||
}
|
||||
testbench.verify_next_reply_is_handled_properly(&time_stamp);
|
||||
testbench.verify_all_replies_handled(&time_stamp);
|
||||
|
||||
testbench.verify_packet_completed();
|
||||
testbench.verify_tm_empty();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_request_routing_error() {
|
||||
let mut testbench = TargetedPusRequestTestbench::new_for_action(
|
||||
TEST_COMPONENT_ID_0.id(),
|
||||
TEST_COMPONENT_ID_1.id(),
|
||||
);
|
||||
// Create a basic action request and verify forwarding.
|
||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
||||
let action_id = 5_u32;
|
||||
let mut app_data: [u8; 8] = [0; 8];
|
||||
// Invalid ID, routing should fail.
|
||||
app_data[0..4].copy_from_slice(&0_u32.to_be_bytes());
|
||||
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||
let pus8_packet = PusTcCreator::new(
|
||||
SpHeader::new_from_apid(TEST_APID),
|
||||
sec_header,
|
||||
&app_data,
|
||||
true,
|
||||
);
|
||||
testbench.add_tc(&pus8_packet);
|
||||
let time_stamp: [u8; 7] = [0; 7];
|
||||
|
||||
let result = testbench.service.poll_and_handle_next_tc(&time_stamp);
|
||||
assert!(result.is_err());
|
||||
// Verify the correct result and completion failure.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converter_action_req_no_data() {
|
||||
let mut testbench = PusConverterTestbench::new(
|
||||
TEST_COMPONENT_ID_0.raw(),
|
||||
ActionRequestConverter::default(),
|
||||
);
|
||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
||||
let action_id = 5_u32;
|
||||
let mut app_data: [u8; 8] = [0; 8];
|
||||
// Invalid ID, routing should fail.
|
||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
||||
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||
let pus8_packet = PusTcCreator::new(
|
||||
SpHeader::new_from_apid(TEST_APID),
|
||||
sec_header,
|
||||
&app_data,
|
||||
true,
|
||||
);
|
||||
let token = testbench.add_tc(&pus8_packet);
|
||||
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
|
||||
assert!(result.is_ok());
|
||||
let (active_req, request) = result.unwrap();
|
||||
if let ActionRequestVariant::NoData = request.variant {
|
||||
assert_eq!(request.action_id, action_id);
|
||||
assert_eq!(active_req.action_id, action_id);
|
||||
assert_eq!(
|
||||
active_req.target_id(),
|
||||
UniqueApidTargetId::new(TEST_APID, TEST_UNIQUE_ID_0).raw()
|
||||
);
|
||||
assert_eq!(
|
||||
active_req.token().request_id(),
|
||||
testbench.request_id().unwrap()
|
||||
);
|
||||
} else {
|
||||
panic!("unexpected action request variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converter_action_req_with_data() {
|
||||
let mut testbench =
|
||||
PusConverterTestbench::new(TEST_COMPONENT_ID_0.id(), ActionRequestConverter::default());
|
||||
let sec_header = PusTcSecondaryHeader::new_simple(8, 128);
|
||||
let action_id = 5_u32;
|
||||
let mut app_data: [u8; 16] = [0; 16];
|
||||
// Invalid ID, routing should fail.
|
||||
app_data[0..4].copy_from_slice(&TEST_UNIQUE_ID_0.to_be_bytes());
|
||||
app_data[4..8].copy_from_slice(&action_id.to_be_bytes());
|
||||
for i in 0..8 {
|
||||
app_data[i + 8] = i as u8;
|
||||
}
|
||||
let pus8_packet = PusTcCreator::new(
|
||||
SpHeader::new_from_apid(TEST_APID),
|
||||
sec_header,
|
||||
&app_data,
|
||||
true,
|
||||
);
|
||||
let token = testbench.add_tc(&pus8_packet);
|
||||
let result = testbench.convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0);
|
||||
assert!(result.is_ok());
|
||||
let (active_req, request) = result.unwrap();
|
||||
if let ActionRequestVariant::VecData(vec) = request.variant {
|
||||
assert_eq!(request.action_id, action_id);
|
||||
assert_eq!(active_req.action_id, action_id);
|
||||
assert_eq!(vec, app_data[8..].to_vec());
|
||||
} else {
|
||||
panic!("unexpected action request variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reply_handling_completion_success() {
|
||||
let mut testbench =
|
||||
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||
let action_id = 5_u32;
|
||||
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||
let active_action_req =
|
||||
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
|
||||
let reply = ActionReplyPus::new(action_id, ActionReplyVariant::Completed);
|
||||
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
|
||||
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
testbench.verif_reporter.assert_full_completion_success(
|
||||
TEST_COMPONENT_ID_0.id(),
|
||||
req_id,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reply_handling_completion_failure() {
|
||||
let mut testbench =
|
||||
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||
let action_id = 5_u32;
|
||||
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||
let active_action_req =
|
||||
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
|
||||
let error_code = ResultU16::new(2, 3);
|
||||
let reply = ActionReplyPus::new(
|
||||
action_id,
|
||||
ActionReplyVariant::CompletionFailed {
|
||||
error_code,
|
||||
params: None,
|
||||
},
|
||||
);
|
||||
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
|
||||
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
testbench.verif_reporter.assert_completion_failure(
|
||||
TEST_COMPONENT_ID_0.into(),
|
||||
req_id,
|
||||
None,
|
||||
error_code.raw() as u64,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reply_handling_step_success() {
|
||||
let mut testbench =
|
||||
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||
let action_id = 5_u32;
|
||||
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||
let active_action_req =
|
||||
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
|
||||
let reply = ActionReplyPus::new(action_id, ActionReplyVariant::StepSuccess { step: 1 });
|
||||
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
|
||||
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
|
||||
assert!(result.is_ok());
|
||||
// Entry should not be removed, completion not done yet.
|
||||
assert!(!result.unwrap());
|
||||
testbench.verif_reporter.check_next_was_added(req_id);
|
||||
testbench
|
||||
.verif_reporter
|
||||
.check_next_is_acceptance_success(TEST_COMPONENT_ID_0.raw(), req_id);
|
||||
testbench
|
||||
.verif_reporter
|
||||
.check_next_is_started_success(TEST_COMPONENT_ID_0.raw(), req_id);
|
||||
testbench
|
||||
.verif_reporter
|
||||
.check_next_is_step_success(TEST_COMPONENT_ID_0.raw(), req_id, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reply_handling_step_failure() {
|
||||
let mut testbench =
|
||||
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||
let action_id = 5_u32;
|
||||
let (req_id, active_req) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||
let active_action_req =
|
||||
ActivePusActionRequestStd::new_from_common_req(action_id, active_req);
|
||||
let error_code = ResultU16::new(2, 3);
|
||||
let reply = ActionReplyPus::new(
|
||||
action_id,
|
||||
ActionReplyVariant::StepFailed {
|
||||
error_code,
|
||||
step: 1,
|
||||
params: None,
|
||||
},
|
||||
);
|
||||
let generic_reply = GenericMessage::new(MessageMetadata::new(req_id.into(), 0), reply);
|
||||
let result = testbench.handle_reply(&generic_reply, &active_action_req, &[]);
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
testbench.verif_reporter.check_next_was_added(req_id);
|
||||
testbench
|
||||
.verif_reporter
|
||||
.check_next_is_acceptance_success(TEST_COMPONENT_ID_0.id(), req_id);
|
||||
testbench
|
||||
.verif_reporter
|
||||
.check_next_is_started_success(TEST_COMPONENT_ID_0.id(), req_id);
|
||||
testbench.verif_reporter.check_next_is_step_failure(
|
||||
TEST_COMPONENT_ID_0.id(),
|
||||
req_id,
|
||||
error_code.raw().into(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reply_handling_unrequested_reply() {
|
||||
let mut testbench =
|
||||
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||
let action_reply = ActionReplyPus::new(5_u32, ActionReplyVariant::Completed);
|
||||
let unrequested_reply =
|
||||
GenericMessage::new(MessageMetadata::new(10_u32, 15_u64), action_reply);
|
||||
// Right now this function does not do a lot. We simply check that it does not panic or do
|
||||
// weird stuff.
|
||||
let result = testbench.handle_unrequested_reply(&unrequested_reply);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reply_handling_reply_timeout() {
|
||||
let mut testbench =
|
||||
ReplyHandlerTestbench::new(TEST_COMPONENT_ID_0.id(), ActionReplyHandler::default());
|
||||
let action_id = 5_u32;
|
||||
let (req_id, active_request) = testbench.add_tc(TEST_APID, TEST_UNIQUE_ID_0, &[]);
|
||||
let result = testbench.handle_request_timeout(
|
||||
&ActivePusActionRequestStd::new_from_common_req(action_id, active_request),
|
||||
&[],
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
testbench.verif_reporter.assert_completion_failure(
|
||||
TEST_COMPONENT_ID_0.raw(),
|
||||
req_id,
|
||||
None,
|
||||
tmtc_err::REQUEST_TIMEOUT.raw() as u64,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
pub mod action;
|
||||
pub mod stack;
|
||||
pub mod test;
|
||||
|
||||
use crate::requests::GenericRequestRouter;
|
||||
use crate::tmtc::MpscStoreAndSendError;
|
||||
use log::warn;
|
||||
use ops_sat_rs::config::components::PUS_ROUTING_SERVICE;
|
||||
use ops_sat_rs::config::{tmtc_err, CustomPusServiceId};
|
||||
@ -13,14 +13,15 @@ use satrs::pus::verification::{
|
||||
};
|
||||
use satrs::pus::{
|
||||
ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter,
|
||||
EcssTcReceiverCore, EcssTmSenderCore, EcssTmtcError, GenericConversionError,
|
||||
GenericRoutingError, PusPacketHandlerResult, PusPacketHandlingError, PusReplyHandler,
|
||||
PusRequestRouter, PusServiceHelper, PusTcToRequestConverter, TcInMemory,
|
||||
EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError,
|
||||
MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlerResult, PusPacketHandlingError,
|
||||
PusReplyHandler, PusRequestRouter, PusServiceHelper, PusTcToRequestConverter, TcInMemory,
|
||||
};
|
||||
use satrs::queue::GenericReceiveError;
|
||||
use satrs::queue::{GenericReceiveError, GenericSendError};
|
||||
use satrs::request::{Apid, GenericMessage, MessageMetadata};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::PusServiceId;
|
||||
use satrs::spacepackets::ecss::{PusPacket, PusServiceId};
|
||||
use satrs::tmtc::PacketAsVec;
|
||||
use satrs::ComponentId;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::mpsc::{self, Sender};
|
||||
@ -53,11 +54,11 @@ pub struct PusTcMpscRouter {
|
||||
// pub event_tc_sender: Sender<EcssTcAndToken>,
|
||||
// pub sched_tc_sender: Sender<EcssTcAndToken>,
|
||||
// pub hk_tc_sender: Sender<EcssTcAndToken>,
|
||||
// pub action_tc_sender: Sender<EcssTcAndToken>,
|
||||
pub action_tc_sender: Sender<EcssTcAndToken>,
|
||||
// pub mode_tc_sender: Sender<EcssTcAndToken>,
|
||||
}
|
||||
|
||||
pub struct PusReceiver<TmSender: EcssTmSenderCore> {
|
||||
pub struct PusTcDistributor<TmSender: EcssTmSender> {
|
||||
pub id: ComponentId,
|
||||
pub tm_sender: TmSender,
|
||||
pub verif_reporter: VerificationReporter,
|
||||
@ -65,7 +66,7 @@ pub struct PusReceiver<TmSender: EcssTmSenderCore> {
|
||||
stamp_helper: TimeStampHelper,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSenderCore> PusReceiver<TmSender> {
|
||||
impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
|
||||
pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self {
|
||||
Self {
|
||||
id: PUS_ROUTING_SERVICE.raw(),
|
||||
@ -81,29 +82,38 @@ impl<TmSender: EcssTmSenderCore> PusReceiver<TmSender> {
|
||||
|
||||
pub fn handle_tc_packet(
|
||||
&mut self,
|
||||
tc_in_memory: TcInMemory,
|
||||
service: u8,
|
||||
pus_tc: &PusTcReader,
|
||||
) -> Result<PusPacketHandlerResult, MpscStoreAndSendError> {
|
||||
let init_token = self.verif_reporter.add_tc(pus_tc);
|
||||
sender_id: ComponentId,
|
||||
tc: Vec<u8>,
|
||||
) -> Result<PusPacketHandlerResult, GenericSendError> {
|
||||
let pus_tc_result = PusTcReader::new(&tc);
|
||||
if pus_tc_result.is_err() {
|
||||
log::warn!(
|
||||
"error creating PUS TC received from {}: {}",
|
||||
sender_id,
|
||||
pus_tc_result.unwrap_err()
|
||||
);
|
||||
log::warn!("raw data: {:x?}", tc);
|
||||
return Ok(PusPacketHandlerResult::RequestHandled);
|
||||
}
|
||||
let pus_tc = pus_tc_result.unwrap().0;
|
||||
let init_token = self.verif_reporter.add_tc(&pus_tc);
|
||||
self.stamp_helper.update_from_now();
|
||||
let accepted_token = self
|
||||
.verif_reporter
|
||||
.acceptance_success(&self.tm_sender, init_token, self.stamp_helper.stamp())
|
||||
.expect("Acceptance success failure");
|
||||
let service = PusServiceId::try_from(service);
|
||||
let service = PusServiceId::try_from(pus_tc.service());
|
||||
let tc_in_memory = TcInMemory::Vec(PacketAsVec::new(sender_id, tc));
|
||||
match service {
|
||||
Ok(standard_service) => match standard_service {
|
||||
PusServiceId::Test => self.pus_router.test_tc_sender.send(EcssTcAndToken {
|
||||
tc_in_memory,
|
||||
token: Some(accepted_token.into()),
|
||||
})?,
|
||||
// PusServiceId::Housekeeping => {
|
||||
// self.pus_router.hk_tc_sender.send(EcssTcAndToken {
|
||||
// tc_in_memory,
|
||||
// token: Some(accepted_token.into()),
|
||||
// })?
|
||||
// }
|
||||
PusServiceId::Action => self.pus_router.action_tc_sender.send(EcssTcAndToken {
|
||||
tc_in_memory,
|
||||
token: Some(accepted_token.into()),
|
||||
})?,
|
||||
// PusServiceId::Event => self.pus_router.event_tc_sender.send(EcssTcAndToken {
|
||||
// tc_in_memory,
|
||||
// token: Some(accepted_token.into()),
|
||||
@ -161,7 +171,7 @@ impl<TmSender: EcssTmSenderCore> PusReceiver<TmSender> {
|
||||
|
||||
pub trait TargetedPusService {
|
||||
/// Returns [true] interface the packet handling is finished.
|
||||
fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> bool;
|
||||
fn poll_and_handle_next_tc(&mut self, time_stamp: &[u8]) -> HandlingStatus;
|
||||
fn poll_and_handle_next_reply(&mut self, time_stamp: &[u8]) -> HandlingStatus;
|
||||
fn check_for_request_timeouts(&mut self);
|
||||
}
|
||||
@ -188,9 +198,6 @@ pub trait TargetedPusService {
|
||||
/// 3. [Self::check_for_request_timeouts] which checks for request timeouts, covering step 7.
|
||||
#[allow(dead_code)]
|
||||
pub struct PusTargetedRequestService<
|
||||
TcReceiver: EcssTcReceiverCore,
|
||||
TmSender: EcssTmSenderCore,
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||
@ -199,8 +206,12 @@ pub struct PusTargetedRequestService<
|
||||
RequestType,
|
||||
ReplyType,
|
||||
> {
|
||||
pub service_helper:
|
||||
PusServiceHelper<TcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
||||
pub service_helper: PusServiceHelper<
|
||||
MpscTcReceiver,
|
||||
MpscTmAsVecSender,
|
||||
EcssTcInVecConverter,
|
||||
VerificationReporter,
|
||||
>,
|
||||
pub request_router: GenericRequestRouter,
|
||||
pub request_converter: RequestConverter,
|
||||
pub active_request_map: ActiveRequestMap,
|
||||
@ -209,11 +220,7 @@ pub struct PusTargetedRequestService<
|
||||
phantom: std::marker::PhantomData<(RequestType, ActiveRequestInfo, ReplyType)>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl<
|
||||
TcReceiver: EcssTcReceiverCore,
|
||||
TmSender: EcssTmSenderCore,
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
VerificationReporter: VerificationReportingProvider,
|
||||
RequestConverter: PusTcToRequestConverter<ActiveRequestInfo, RequestType, Error = GenericConversionError>,
|
||||
ReplyHandler: PusReplyHandler<ActiveRequestInfo, ReplyType, Error = EcssTmtcError>,
|
||||
@ -223,9 +230,6 @@ impl<
|
||||
ReplyType,
|
||||
>
|
||||
PusTargetedRequestService<
|
||||
TcReceiver,
|
||||
TmSender,
|
||||
TcInMemConverter,
|
||||
VerificationReporter,
|
||||
RequestConverter,
|
||||
ReplyHandler,
|
||||
@ -239,9 +243,9 @@ where
|
||||
{
|
||||
pub fn new(
|
||||
service_helper: PusServiceHelper<
|
||||
TcReceiver,
|
||||
TmSender,
|
||||
TcInMemConverter,
|
||||
MpscTcReceiver,
|
||||
MpscTmAsVecSender,
|
||||
EcssTcInVecConverter,
|
||||
VerificationReporter,
|
||||
>,
|
||||
request_converter: RequestConverter,
|
||||
@ -442,7 +446,7 @@ where
|
||||
/// and also log the error.
|
||||
#[allow(dead_code)]
|
||||
pub fn generic_pus_request_timeout_handler(
|
||||
sender: &(impl EcssTmSenderCore + ?Sized),
|
||||
sender: &(impl EcssTmSender + ?Sized),
|
||||
active_request: &(impl ActiveRequestProvider + Debug),
|
||||
verification_handler: &impl VerificationReportingProvider,
|
||||
time_stamp: &[u8],
|
||||
@ -466,12 +470,13 @@ pub(crate) mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use satrs::pus::test_util::TEST_COMPONENT_ID_0;
|
||||
use satrs::pus::{MpscTmAsVecSender, PusTmAsVec, PusTmVariant};
|
||||
use satrs::pus::{MpscTmAsVecSender, PusTmVariant};
|
||||
use satrs::request::RequestId;
|
||||
use satrs::tmtc::PacketAsVec;
|
||||
use satrs::{
|
||||
pus::{
|
||||
verification::test_util::TestVerificationReporter, ActivePusRequestStd,
|
||||
ActiveRequestMapProvider, EcssTcInVecConverter, MpscTcReceiver,
|
||||
ActiveRequestMapProvider,
|
||||
},
|
||||
request::UniqueApidTargetId,
|
||||
spacepackets::{
|
||||
@ -496,7 +501,7 @@ pub(crate) mod tests {
|
||||
pub id: ComponentId,
|
||||
pub verif_reporter: TestVerificationReporter,
|
||||
pub reply_handler: ReplyHandler,
|
||||
pub tm_receiver: mpsc::Receiver<PusTmAsVec>,
|
||||
pub tm_receiver: mpsc::Receiver<PacketAsVec>,
|
||||
pub default_timeout: Duration,
|
||||
tm_sender: MpscTmAsVecSender,
|
||||
phantom: std::marker::PhantomData<(ActiveRequestInfo, Reply)>,
|
||||
@ -596,7 +601,7 @@ pub(crate) mod tests {
|
||||
/// Dummy sender component which does nothing on the [Self::send_tm] call.
|
||||
///
|
||||
/// Useful for unit tests.
|
||||
impl EcssTmSenderCore for DummySender {
|
||||
impl EcssTmSender for DummySender {
|
||||
fn send_tm(&self, _source_id: ComponentId, _tm: PusTmVariant) -> Result<(), EcssTmtcError> {
|
||||
Ok(())
|
||||
}
|
||||
@ -691,9 +696,6 @@ pub(crate) mod tests {
|
||||
ReplyType,
|
||||
> {
|
||||
pub service: PusTargetedRequestService<
|
||||
MpscTcReceiver,
|
||||
MpscTmAsVecSender,
|
||||
EcssTcInVecConverter,
|
||||
TestVerificationReporter,
|
||||
RequestConverter,
|
||||
ReplyHandler,
|
||||
@ -703,7 +705,7 @@ pub(crate) mod tests {
|
||||
ReplyType,
|
||||
>,
|
||||
pub request_id: Option<RequestId>,
|
||||
pub tm_funnel_rx: mpsc::Receiver<PusTmAsVec>,
|
||||
pub tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
||||
pub pus_packet_tx: mpsc::Sender<EcssTcAndToken>,
|
||||
pub reply_tx: mpsc::Sender<GenericMessage<ReplyType>>,
|
||||
pub request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||
|
@ -1,11 +1,9 @@
|
||||
// use crate::pus::mode::ModeServiceWrapper;
|
||||
use crate::pus::test::TestCustomServiceWrapper;
|
||||
use crate::pus::HandlingStatus;
|
||||
use derive_new::new;
|
||||
use satrs::{
|
||||
pus::{EcssTcInMemConverter, EcssTmSenderCore},
|
||||
spacepackets::time::{cds, TimeWriter},
|
||||
};
|
||||
use satrs::spacepackets::time::{cds, TimeWriter};
|
||||
|
||||
use super::{action::ActionServiceWrapper, TargetedPusService};
|
||||
|
||||
// use super::{
|
||||
// action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper,
|
||||
@ -14,18 +12,16 @@ use satrs::{
|
||||
// };
|
||||
|
||||
#[derive(new)]
|
||||
pub struct PusStack<TmSender: EcssTmSenderCore, TcInMemConverter: EcssTcInMemConverter> {
|
||||
test_srv: TestCustomServiceWrapper<TmSender, TcInMemConverter>,
|
||||
pub struct PusStack {
|
||||
test_srv: TestCustomServiceWrapper,
|
||||
// hk_srv_wrapper: HkServiceWrapper<TmSender, TcInMemConverter>,
|
||||
// event_srv: EventServiceWrapper<TmSender, TcInMemConverter>,
|
||||
// action_srv_wrapper: ActionServiceWrapper<TmSender, TcInMemConverter>,
|
||||
action_srv_wrapper: ActionServiceWrapper,
|
||||
// schedule_srv: SchedulingServiceWrapper<TmSender, TcInMemConverter>,
|
||||
// mode_srv: ModeServiceWrapper<TmSender, TcInMemConverter>,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSenderCore, TcInMemConverter: EcssTcInMemConverter>
|
||||
PusStack<TmSender, TcInMemConverter>
|
||||
{
|
||||
impl PusStack {
|
||||
pub fn periodic_operation(&mut self) {
|
||||
// Release all telecommands which reached their release time before calling the service
|
||||
// handlers.
|
||||
@ -37,24 +33,31 @@ impl<TmSender: EcssTmSenderCore, TcInMemConverter: EcssTcInMemConverter>
|
||||
loop {
|
||||
let mut nothing_to_do = true;
|
||||
let mut is_srv_finished =
|
||||
|tc_handling_done: bool, reply_handling_done: Option<HandlingStatus>| {
|
||||
if !tc_handling_done
|
||||
|| (reply_handling_done.is_some()
|
||||
&& reply_handling_done.unwrap() == HandlingStatus::Empty)
|
||||
|_srv_id: u8,
|
||||
tc_handling_status: HandlingStatus,
|
||||
reply_handling_status: Option<HandlingStatus>| {
|
||||
if tc_handling_status == HandlingStatus::HandledOne
|
||||
|| (reply_handling_status.is_some()
|
||||
&& reply_handling_status.unwrap() == HandlingStatus::HandledOne)
|
||||
{
|
||||
nothing_to_do = false;
|
||||
}
|
||||
};
|
||||
is_srv_finished(self.test_srv.poll_and_handle_next_packet(&time_stamp), None);
|
||||
is_srv_finished(
|
||||
17,
|
||||
self.test_srv.poll_and_handle_next_packet(&time_stamp),
|
||||
None,
|
||||
);
|
||||
// is_srv_finished(self.schedule_srv.poll_and_handle_next_tc(&time_stamp), None);
|
||||
// is_srv_finished(self.event_srv.poll_and_handle_next_tc(&time_stamp), None);
|
||||
// is_srv_finished(
|
||||
// self.action_srv_wrapper.poll_and_handle_next_tc(&time_stamp),
|
||||
// Some(
|
||||
// self.action_srv_wrapper
|
||||
// .poll_and_handle_next_reply(&time_stamp),
|
||||
// ),
|
||||
// );
|
||||
is_srv_finished(
|
||||
8,
|
||||
self.action_srv_wrapper.poll_and_handle_next_tc(&time_stamp),
|
||||
Some(
|
||||
self.action_srv_wrapper
|
||||
.poll_and_handle_next_reply(&time_stamp),
|
||||
),
|
||||
);
|
||||
// is_srv_finished(
|
||||
// self.hk_srv_wrapper.poll_and_handle_next_tc(&time_stamp),
|
||||
// Some(self.hk_srv_wrapper.poll_and_handle_next_reply(&time_stamp)),
|
||||
@ -65,7 +68,7 @@ impl<TmSender: EcssTmSenderCore, TcInMemConverter: EcssTcInMemConverter>
|
||||
// );
|
||||
if nothing_to_do {
|
||||
// Timeout checking is only done once.
|
||||
// self.action_srv_wrapper.check_for_request_timeouts();
|
||||
self.action_srv_wrapper.check_for_request_timeouts();
|
||||
// self.hk_srv_wrapper.check_for_request_timeouts();
|
||||
// self.mode_srv.check_for_request_timeouts();
|
||||
break;
|
||||
|
@ -6,20 +6,22 @@ use ops_sat_rs::config::tmtc_err;
|
||||
use satrs::pus::test::PusService17TestHandler;
|
||||
use satrs::pus::verification::{FailParams, VerificationReporter, VerificationReportingProvider};
|
||||
use satrs::pus::{
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, EcssTmSenderCore, MpscTcReceiver,
|
||||
MpscTmAsVecSender, PusPacketHandlerResult, PusServiceHelper, PusTmAsVec,
|
||||
EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender,
|
||||
PusPacketHandlerResult, PusServiceHelper,
|
||||
};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
use satrs::spacepackets::ecss::PusPacket;
|
||||
use satrs::spacepackets::time::cds::CdsTime;
|
||||
use satrs::spacepackets::time::TimeWriter;
|
||||
use satrs::tmtc::PacketAsVec;
|
||||
use std::sync::mpsc;
|
||||
|
||||
pub fn create_test_service_dynamic(
|
||||
tm_funnel_tx: mpsc::Sender<PusTmAsVec>,
|
||||
// event_sender: mpsc::Sender<EventMessageU32>,
|
||||
use super::HandlingStatus;
|
||||
|
||||
pub fn create_test_service(
|
||||
tm_funnel_tx: mpsc::Sender<PacketAsVec>,
|
||||
pus_test_rx: mpsc::Receiver<EcssTcAndToken>,
|
||||
) -> TestCustomServiceWrapper<MpscTmAsVecSender, EcssTcInVecConverter> {
|
||||
) -> TestCustomServiceWrapper {
|
||||
let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new(
|
||||
PUS_TEST_SERVICE.id(),
|
||||
pus_test_rx,
|
||||
@ -33,23 +35,22 @@ pub fn create_test_service_dynamic(
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestCustomServiceWrapper<
|
||||
TmSender: EcssTmSenderCore,
|
||||
TcInMemConverter: EcssTcInMemConverter,
|
||||
> {
|
||||
pub handler:
|
||||
PusService17TestHandler<MpscTcReceiver, TmSender, TcInMemConverter, VerificationReporter>,
|
||||
pub struct TestCustomServiceWrapper {
|
||||
pub handler: PusService17TestHandler<
|
||||
MpscTcReceiver,
|
||||
MpscTmAsVecSender,
|
||||
EcssTcInVecConverter,
|
||||
VerificationReporter,
|
||||
>,
|
||||
// pub test_srv_event_sender: mpsc::Sender<EventMessageU32>,
|
||||
}
|
||||
|
||||
impl<TmSender: EcssTmSenderCore, TcInMemConverter: EcssTcInMemConverter>
|
||||
TestCustomServiceWrapper<TmSender, TcInMemConverter>
|
||||
{
|
||||
pub fn poll_and_handle_next_packet(&mut self, time_stamp: &[u8]) -> bool {
|
||||
impl TestCustomServiceWrapper {
|
||||
pub fn poll_and_handle_next_packet(&mut self, time_stamp: &[u8]) -> HandlingStatus {
|
||||
let res = self.handler.poll_and_handle_next_tc(time_stamp);
|
||||
if res.is_err() {
|
||||
warn!("PUS17 handler failed with error {:?}", res.unwrap_err());
|
||||
return true;
|
||||
return HandlingStatus::Empty;
|
||||
}
|
||||
match res.unwrap() {
|
||||
PusPacketHandlerResult::RequestHandled => {
|
||||
@ -114,10 +115,8 @@ impl<TmSender: EcssTmSenderCore, TcInMemConverter: EcssTcInMemConverter>
|
||||
.expect("Sending start failure verification failed");
|
||||
}
|
||||
}
|
||||
PusPacketHandlerResult::Empty => {
|
||||
return true;
|
||||
}
|
||||
PusPacketHandlerResult::Empty => return HandlingStatus::Empty,
|
||||
}
|
||||
false
|
||||
HandlingStatus::HandledOne
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use satrs::mode::ModeRequest;
|
||||
use satrs::pus::verification::{
|
||||
FailParams, TcStateAccepted, VerificationReportingProvider, VerificationToken,
|
||||
};
|
||||
use satrs::pus::{ActiveRequestProvider, EcssTmSenderCore, GenericRoutingError, PusRequestRouter};
|
||||
use satrs::pus::{ActiveRequestProvider, EcssTmSender, GenericRoutingError, PusRequestRouter};
|
||||
use satrs::queue::GenericSendError;
|
||||
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
||||
@ -49,7 +49,7 @@ impl GenericRequestRouter {
|
||||
active_request: &impl ActiveRequestProvider,
|
||||
tc: &PusTcReader,
|
||||
error: GenericRoutingError,
|
||||
tm_sender: &(impl EcssTmSenderCore + ?Sized),
|
||||
tm_sender: &(impl EcssTmSender + ?Sized),
|
||||
verif_reporter: &impl VerificationReportingProvider,
|
||||
time_stamp: &[u8],
|
||||
) {
|
||||
|
109
src/tm_funnel.rs
109
src/tm_funnel.rs
@ -1,109 +0,0 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::mpsc::{self},
|
||||
};
|
||||
|
||||
use log::info;
|
||||
use satrs::pus::PusTmAsVec;
|
||||
use satrs::{
|
||||
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
|
||||
spacepackets::{
|
||||
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
||||
time::cds::MIN_CDS_FIELD_LEN,
|
||||
CcsdsPacket,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::interface::tcp::SyncTcpTmSource;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CcsdsSeqCounterMap {
|
||||
apid_seq_counter_map: HashMap<u16, CcsdsSimpleSeqCountProvider>,
|
||||
}
|
||||
|
||||
impl CcsdsSeqCounterMap {
|
||||
pub fn get_and_increment(&mut self, apid: u16) -> u16 {
|
||||
self.apid_seq_counter_map
|
||||
.entry(apid)
|
||||
.or_default()
|
||||
.get_and_increment()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TmFunnelCommon {
|
||||
seq_counter_map: CcsdsSeqCounterMap,
|
||||
msg_counter_map: HashMap<u8, u16>,
|
||||
sync_tm_tcp_source: SyncTcpTmSource,
|
||||
}
|
||||
|
||||
impl TmFunnelCommon {
|
||||
pub fn new(sync_tm_tcp_source: SyncTcpTmSource) -> Self {
|
||||
Self {
|
||||
seq_counter_map: Default::default(),
|
||||
msg_counter_map: Default::default(),
|
||||
sync_tm_tcp_source,
|
||||
}
|
||||
}
|
||||
|
||||
// Applies common packet processing operations for PUS TM packets. This includes setting
|
||||
// a sequence counter
|
||||
fn apply_packet_processing(&mut self, mut zero_copy_writer: PusTmZeroCopyWriter) {
|
||||
// zero_copy_writer.set_apid(PUS_APID);
|
||||
zero_copy_writer.set_seq_count(
|
||||
self.seq_counter_map
|
||||
.get_and_increment(zero_copy_writer.apid()),
|
||||
);
|
||||
let entry = self
|
||||
.msg_counter_map
|
||||
.entry(zero_copy_writer.service())
|
||||
.or_insert(0);
|
||||
zero_copy_writer.set_msg_count(*entry);
|
||||
if *entry == u16::MAX {
|
||||
*entry = 0;
|
||||
} else {
|
||||
*entry += 1;
|
||||
}
|
||||
|
||||
Self::packet_printout(&zero_copy_writer);
|
||||
// This operation has to come last!
|
||||
zero_copy_writer.finish();
|
||||
}
|
||||
|
||||
fn packet_printout(tm: &PusTmZeroCopyWriter) {
|
||||
info!("Sending PUS TM[{},{}]", tm.service(), tm.subservice());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TmFunnelDynamic {
|
||||
common: TmFunnelCommon,
|
||||
tm_funnel_rx: mpsc::Receiver<PusTmAsVec>,
|
||||
tm_server_tx: mpsc::Sender<PusTmAsVec>,
|
||||
}
|
||||
|
||||
impl TmFunnelDynamic {
|
||||
pub fn new(
|
||||
sync_tm_tcp_source: SyncTcpTmSource,
|
||||
tm_funnel_rx: mpsc::Receiver<PusTmAsVec>,
|
||||
tm_server_tx: mpsc::Sender<PusTmAsVec>,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
||||
tm_funnel_rx,
|
||||
tm_server_tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operation(&mut self) {
|
||||
if let Ok(mut tm) = self.tm_funnel_rx.recv() {
|
||||
// Read the TM, set sequence counter and message counter, and finally update
|
||||
// the CRC.
|
||||
let zero_copy_writer = PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN)
|
||||
.expect("Creating TM zero copy writer failed");
|
||||
self.common.apply_packet_processing(zero_copy_writer);
|
||||
self.common.sync_tm_tcp_source.add_tm(&tm.packet);
|
||||
self.tm_server_tx
|
||||
.send(tm)
|
||||
.expect("Sending TM to server failed");
|
||||
}
|
||||
}
|
||||
}
|
94
src/tmtc.rs
94
src/tmtc.rs
@ -1,94 +0,0 @@
|
||||
use crate::pus::PusReceiver;
|
||||
use satrs::pool::{StoreAddr, StoreError};
|
||||
use satrs::pus::{EcssTcAndToken, MpscTmAsVecSender};
|
||||
use satrs::spacepackets::ecss::PusPacket;
|
||||
use satrs::{
|
||||
pus::ReceivesEcssPusTc,
|
||||
spacepackets::{ecss::tc::PusTcReader, SpHeader},
|
||||
tmtc::ReceivesCcsdsTc,
|
||||
};
|
||||
use std::sync::mpsc::{self, SendError, Sender, TryRecvError};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum MpscStoreAndSendError {
|
||||
#[error("Store error: {0}")]
|
||||
Store(#[from] StoreError),
|
||||
#[error("TC send error: {0}")]
|
||||
TcSend(#[from] SendError<EcssTcAndToken>),
|
||||
#[error("TMTC send error: {0}")]
|
||||
TmTcSend(#[from] SendError<StoreAddr>),
|
||||
}
|
||||
|
||||
// Newtype, can not implement necessary traits on MPSC sender directly because of orphan rules.
|
||||
#[derive(Clone)]
|
||||
pub struct PusTcSourceProviderDynamic(pub Sender<Vec<u8>>);
|
||||
|
||||
impl ReceivesEcssPusTc for PusTcSourceProviderDynamic {
|
||||
type Error = SendError<Vec<u8>>;
|
||||
|
||||
fn pass_pus_tc(&mut self, _: &SpHeader, pus_tc: &PusTcReader) -> Result<(), Self::Error> {
|
||||
self.0.send(pus_tc.raw_data().to_vec())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ReceivesCcsdsTc for PusTcSourceProviderDynamic {
|
||||
type Error = mpsc::SendError<Vec<u8>>;
|
||||
|
||||
fn pass_ccsds(&mut self, _: &SpHeader, tc_raw: &[u8]) -> Result<(), Self::Error> {
|
||||
self.0.send(tc_raw.to_vec())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// TC source components where the heap is the backing memory of the received telecommands.
|
||||
pub struct TcSourceTaskDynamic {
|
||||
pub tc_receiver: mpsc::Receiver<Vec<u8>>,
|
||||
pus_receiver: PusReceiver<MpscTmAsVecSender>,
|
||||
}
|
||||
|
||||
impl TcSourceTaskDynamic {
|
||||
pub fn new(
|
||||
tc_receiver: mpsc::Receiver<Vec<u8>>,
|
||||
pus_receiver: PusReceiver<MpscTmAsVecSender>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tc_receiver,
|
||||
pus_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn periodic_operation(&mut self) {
|
||||
self.poll_tc();
|
||||
}
|
||||
|
||||
pub fn poll_tc(&mut self) -> bool {
|
||||
match self.tc_receiver.try_recv() {
|
||||
Ok(tc) => match PusTcReader::new(&tc) {
|
||||
Ok((pus_tc, _)) => {
|
||||
self.pus_receiver
|
||||
.handle_tc_packet(
|
||||
satrs::pus::TcInMemory::Vec(tc.clone()),
|
||||
pus_tc.service(),
|
||||
&pus_tc,
|
||||
)
|
||||
.ok();
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("error creating PUS TC from raw data: {e}");
|
||||
log::warn!("raw data: {:x?}", tc);
|
||||
true
|
||||
}
|
||||
},
|
||||
Err(e) => match e {
|
||||
TryRecvError::Empty => false,
|
||||
TryRecvError::Disconnected => {
|
||||
log::warn!("tmtc thread: sender disconnected");
|
||||
false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
2
src/tmtc/mod.rs
Normal file
2
src/tmtc/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod tc_source;
|
||||
pub mod tm_sink;
|
47
src/tmtc/tc_source.rs
Normal file
47
src/tmtc/tc_source.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use std::sync::mpsc::{self, TryRecvError};
|
||||
|
||||
use satrs::{pus::MpscTmAsVecSender, tmtc::PacketAsVec};
|
||||
|
||||
use crate::pus::{HandlingStatus, PusTcDistributor};
|
||||
|
||||
// TC source components where the heap is the backing memory of the received telecommands.
|
||||
pub struct TcSourceTaskDynamic {
|
||||
pub tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||
pus_distrib: PusTcDistributor<MpscTmAsVecSender>,
|
||||
}
|
||||
|
||||
impl TcSourceTaskDynamic {
|
||||
pub fn new(
|
||||
tc_receiver: mpsc::Receiver<PacketAsVec>,
|
||||
pus_receiver: PusTcDistributor<MpscTmAsVecSender>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tc_receiver,
|
||||
pus_distrib: pus_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn periodic_operation(&mut self) {
|
||||
self.poll_tc();
|
||||
}
|
||||
|
||||
pub fn poll_tc(&mut self) -> HandlingStatus {
|
||||
// Right now, we only expect PUS packets. If any other protocols like CFDP are added at
|
||||
// a later stage, we probably need to check for the APID before routing the packet.
|
||||
match self.tc_receiver.try_recv() {
|
||||
Ok(packet_with_sender) => {
|
||||
self.pus_distrib
|
||||
.handle_tc_packet(packet_with_sender.sender_id, packet_with_sender.packet)
|
||||
.ok();
|
||||
HandlingStatus::HandledOne
|
||||
}
|
||||
Err(e) => match e {
|
||||
TryRecvError::Empty => HandlingStatus::Empty,
|
||||
TryRecvError::Disconnected => {
|
||||
log::warn!("tmtc thread: sender disconnected");
|
||||
HandlingStatus::Empty
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
142
src/tmtc/tm_sink.rs
Normal file
142
src/tmtc/tm_sink.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, sync::mpsc, time::Duration};
|
||||
|
||||
use log::info;
|
||||
use ops_sat_rs::config::tasks::STOP_CHECK_FREQUENCY;
|
||||
use satrs::tmtc::PacketAsVec;
|
||||
use satrs::{
|
||||
seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore},
|
||||
spacepackets::{
|
||||
ecss::{tm::PusTmZeroCopyWriter, PusPacket},
|
||||
time::cds::MIN_CDS_FIELD_LEN,
|
||||
CcsdsPacket,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::interface::tcp_server::SyncTcpTmSource;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CcsdsSeqCounterMap {
|
||||
apid_seq_counter_map: HashMap<u16, CcsdsSimpleSeqCountProvider>,
|
||||
}
|
||||
|
||||
impl CcsdsSeqCounterMap {
|
||||
pub fn get_and_increment(&mut self, apid: u16) -> u16 {
|
||||
self.apid_seq_counter_map
|
||||
.entry(apid)
|
||||
.or_default()
|
||||
.get_and_increment()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TmFunnelCommon {
|
||||
seq_counter_map: CcsdsSeqCounterMap,
|
||||
msg_counter_map: HashMap<u8, u16>,
|
||||
sync_tm_tcp_source: SyncTcpTmSource,
|
||||
}
|
||||
|
||||
impl TmFunnelCommon {
|
||||
pub fn new(sync_tm_tcp_source: SyncTcpTmSource) -> Self {
|
||||
Self {
|
||||
seq_counter_map: Default::default(),
|
||||
msg_counter_map: Default::default(),
|
||||
sync_tm_tcp_source,
|
||||
}
|
||||
}
|
||||
|
||||
// Applies common packet processing operations for PUS TM packets. This includes setting
|
||||
// a sequence counter
|
||||
fn apply_packet_processing(&mut self, mut zero_copy_writer: PusTmZeroCopyWriter) {
|
||||
// zero_copy_writer.set_apid(PUS_APID);
|
||||
zero_copy_writer.set_seq_count(
|
||||
self.seq_counter_map
|
||||
.get_and_increment(zero_copy_writer.apid()),
|
||||
);
|
||||
let entry = self
|
||||
.msg_counter_map
|
||||
.entry(zero_copy_writer.service())
|
||||
.or_insert(0);
|
||||
zero_copy_writer.set_msg_count(*entry);
|
||||
if *entry == u16::MAX {
|
||||
*entry = 0;
|
||||
} else {
|
||||
*entry += 1;
|
||||
}
|
||||
|
||||
Self::packet_printout(&zero_copy_writer);
|
||||
// This operation has to come last!
|
||||
zero_copy_writer.finish();
|
||||
}
|
||||
|
||||
fn packet_printout(tm: &PusTmZeroCopyWriter) {
|
||||
info!("Sending PUS TM[{},{}]", tm.service(), tm.subservice());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TmFunnelDynamic {
|
||||
common: TmFunnelCommon,
|
||||
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
||||
tm_udp_server_tx: mpsc::Sender<PacketAsVec>,
|
||||
tm_tcp_client_tx: mpsc::Sender<PacketAsVec>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl TmFunnelDynamic {
|
||||
pub fn new(
|
||||
sync_tm_tcp_source: SyncTcpTmSource,
|
||||
tm_funnel_rx: mpsc::Receiver<PacketAsVec>,
|
||||
tm_udp_server_tx: mpsc::Sender<PacketAsVec>,
|
||||
tm_tcp_client_tx: mpsc::Sender<PacketAsVec>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: TmFunnelCommon::new(sync_tm_tcp_source),
|
||||
tm_funnel_rx,
|
||||
tm_udp_server_tx,
|
||||
tm_tcp_client_tx,
|
||||
stop_signal,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operation(&mut self) {
|
||||
loop {
|
||||
match self
|
||||
.tm_funnel_rx
|
||||
.recv_timeout(Duration::from_millis(STOP_CHECK_FREQUENCY))
|
||||
{
|
||||
Ok(mut tm) => {
|
||||
// Read the TM, set sequence counter and message counter, and finally update
|
||||
// the CRC.
|
||||
let zero_copy_writer =
|
||||
PusTmZeroCopyWriter::new(&mut tm.packet, MIN_CDS_FIELD_LEN)
|
||||
.expect("Creating TM zero copy writer failed");
|
||||
self.common.apply_packet_processing(zero_copy_writer);
|
||||
self.common.sync_tm_tcp_source.add_tm(&tm.packet);
|
||||
let result = self.tm_udp_server_tx.send(tm.clone());
|
||||
if result.is_err() {
|
||||
log::error!("TM UDP server has disconnected");
|
||||
}
|
||||
let result = self.tm_tcp_client_tx.send(tm);
|
||||
if result.is_err() {
|
||||
log::error!("TM TCP client has disconnected");
|
||||
}
|
||||
if self.stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
mpsc::RecvTimeoutError::Timeout => {
|
||||
if self.stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mpsc::RecvTimeoutError::Disconnected => {
|
||||
log::warn!("All TM funnel senders have disconnected");
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user