From 29c045551be31ea2edc144f260ff3301860d4861 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 21 Nov 2024 18:35:16 +0100 Subject: [PATCH] Mode Tree Feature Update --- .github/workflows/ci.yml | 6 +- docs.sh | 3 + satrs-example/Cargo.toml | 5 +- satrs-example/README.md | 2 +- satrs-example/src/acs/mgm.rs | 202 +-- satrs-example/src/config.rs | 6 + satrs-example/src/eps/pcdu.rs | 128 +- satrs-example/src/interface/tcp.rs | 37 +- satrs-example/src/interface/udp.rs | 69 +- satrs-example/src/main.rs | 668 ++++------ satrs-example/src/pus/action.rs | 58 +- satrs-example/src/pus/event.rs | 52 +- satrs-example/src/pus/hk.rs | 51 +- satrs-example/src/pus/mod.rs | 29 +- satrs-example/src/pus/mode.rs | 88 +- satrs-example/src/pus/scheduler.rs | 85 +- satrs-example/src/pus/stack.rs | 25 +- satrs-example/src/pus/test.rs | 52 +- satrs-example/src/spi.rs | 6 + satrs-example/src/tmtc/mod.rs | 1 + satrs-example/src/tmtc/sender.rs | 75 ++ satrs-example/src/tmtc/tc_source.rs | 32 +- satrs-example/src/tmtc/tm_sink.rs | 21 +- satrs/CHANGELOG.md | 11 + satrs/Cargo.toml | 2 +- satrs/src/dev_mgmt.rs | 448 +++++++ satrs/src/health.rs | 39 + satrs/src/lib.rs | 14 +- satrs/src/mode.rs | 429 +++++-- satrs/src/mode_tree.rs | 597 ++++++++- satrs/src/pool.rs | 3 +- satrs/src/pus/action.rs | 66 +- satrs/src/pus/event_srv.rs | 10 +- satrs/src/pus/mod.rs | 71 +- satrs/src/pus/mode.rs | 5 +- satrs/src/pus/scheduler_srv.rs | 14 +- satrs/src/pus/test.rs | 15 +- satrs/src/queue.rs | 85 +- satrs/src/request.rs | 277 +++-- satrs/src/scheduling.rs | 2 +- satrs/src/subsystem.rs | 1610 ++++++++++++++++++++++++ satrs/src/tmtc/mod.rs | 11 +- satrs/tests/mode_tree.rs | 1780 +++++++++++++++++++++++---- satrs/tests/pus_events.rs | 8 +- 44 files changed, 5636 insertions(+), 1562 deletions(-) create mode 100755 docs.sh create mode 100644 satrs-example/src/spi.rs create mode 100644 satrs-example/src/tmtc/sender.rs create mode 100644 satrs/src/dev_mgmt.rs create mode 100644 satrs/src/health.rs create mode 100644 satrs/src/subsystem.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f80eed..3671c69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,9 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo check --release + - run: cargo check + # Check example with static pool configuration + - run: cargo check -p satrs-example --no-default-features test: name: Run Tests @@ -37,7 +39,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: "armv7-unknown-linux-gnueabihf, thumbv7em-none-eabihf" - - run: cargo check -p satrs --release --target=${{matrix.target}} --no-default-features + - run: cargo check -p satrs --target=${{matrix.target}} --no-default-features fmt: name: Check formatting diff --git a/docs.sh b/docs.sh new file mode 100755 index 0000000..37563d2 --- /dev/null +++ b/docs.sh @@ -0,0 +1,3 @@ +#!/bin/sh +export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" +cargo +nightly doc --all-features --open diff --git a/satrs-example/Cargo.toml b/satrs-example/Cargo.toml index aeb0379..fa1dc88 100644 --- a/satrs-example/Cargo.toml +++ b/satrs-example/Cargo.toml @@ -20,6 +20,7 @@ thiserror = "2" lazy_static = "1" strum = { version = "0.26", features = ["derive"] } derive-new = "0.7" +cfg-if = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -35,8 +36,8 @@ version = "0.1.1" path = "../satrs-mib" [features] -dyn_tmtc = [] -default = ["dyn_tmtc"] +heap_tmtc = [] +default = ["heap_tmtc"] [dev-dependencies] env_logger = "0.11" diff --git a/satrs-example/README.md b/satrs-example/README.md index b661423..87a8e13 100644 --- a/satrs-example/README.md +++ b/satrs-example/README.md @@ -14,7 +14,7 @@ You can run the application using `cargo run`. # Features -The example has the `dyn_tmtc` feature which is enabled by default. With this feature enabled, +The example has the `heap_tmtc` feature which is enabled by default. With this feature enabled, TMTC packets are exchanged using the heap as the backing memory instead of pre-allocated static stores. diff --git a/satrs-example/src/acs/mgm.rs b/satrs-example/src/acs/mgm.rs index e3ab71f..8b5c711 100644 --- a/satrs-example/src/acs/mgm.rs +++ b/satrs-example/src/acs/mgm.rs @@ -1,7 +1,7 @@ use derive_new::new; use satrs::hk::{HkRequest, HkRequestVariant}; +use satrs::mode_tree::{ModeChild, ModeNode}; use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender}; -use satrs::queue::{GenericSendError, GenericTargetedMessagingError}; use satrs_example::{DeviceMode, TimestampHelper}; use satrs_minisim::acs::lis3mdl::{ MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR, @@ -15,7 +15,8 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; use satrs::mode::{ - ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequest, ModeRequestHandler, + ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler, + ModeRequestHandlerMpscBounded, }; use satrs::pus::{EcssTmSender, PusTmVariant}; use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId}; @@ -24,6 +25,8 @@ use satrs_example::config::components::{NO_SENDER, PUS_MODE_SERVICE}; use crate::hk::PusHkHelper; use crate::pus::hk::{HkReply, HkReplyVariant}; use crate::requests::CompositeRequest; +use crate::spi::SpiInterface; +use crate::tmtc::sender::TmTcSender; use serde::{Deserialize, Serialize}; @@ -48,11 +51,6 @@ pub enum TransitionState { Done, } -pub trait SpiInterface { - type Error: Debug; - fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>; -} - #[derive(Default)] pub struct SpiDummyInterface { pub dummy_values: MgmLis3RawValues, @@ -129,13 +127,6 @@ pub struct MgmData { pub z: f32, } -pub struct MpscModeLeafInterface { - pub request_rx: mpsc::Receiver>, - pub reply_to_pus_tx: mpsc::Sender>, - #[allow(dead_code)] - pub reply_to_parent_tx: mpsc::SyncSender>, -} - #[derive(Default)] pub struct BufWrapper { tx_buf: [u8; 32], @@ -166,16 +157,15 @@ impl Default for ModeHelpers { #[allow(clippy::too_many_arguments)] pub struct MgmHandlerLis3Mdl< ComInterface: SpiInterface, - TmSender: EcssTmSender, SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, > { id: UniqueApidTargetId, dev_str: &'static str, - mode_interface: MpscModeLeafInterface, + mode_node: ModeRequestHandlerMpscBounded, composite_request_rx: mpsc::Receiver>, - hk_reply_tx: mpsc::Sender>, + hk_reply_tx: mpsc::SyncSender>, switch_helper: SwitchHelper, - tm_sender: TmSender, + tm_sender: TmTcSender, pub com_interface: ComInterface, shared_mgm_set: Arc>, #[new(value = "PusHkHelper::new(id)")] @@ -190,9 +180,8 @@ pub struct MgmHandlerLis3Mdl< impl< ComInterface: SpiInterface, - TmSender: EcssTmSender, SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, - > MgmHandlerLis3Mdl + > MgmHandlerLis3Mdl { pub fn periodic_operation(&mut self) { self.stamp_helper.update_from_now(); @@ -275,25 +264,28 @@ impl< pub fn handle_mode_requests(&mut self) { loop { // TODO: Only allow one set mode request per cycle? - match self.mode_interface.request_rx.try_recv() { - Ok(msg) => { - let result = self.handle_mode_request(msg); - // TODO: Trigger event? - if result.is_err() { - log::warn!( - "{}: mode request failed with error {:?}", - self.dev_str, - result.err().unwrap() - ); - } - } - Err(e) => { - if e != mpsc::TryRecvError::Empty { - log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e); + match self.mode_node.try_recv_mode_request() { + Ok(opt_msg) => { + if let Some(msg) = opt_msg { + let result = self.handle_mode_request(msg); + // TODO: Trigger event? + if result.is_err() { + log::warn!( + "{}: mode request failed with error {:?}", + self.dev_str, + result.err().unwrap() + ); + } } else { break; } } + Err(e) => match e { + satrs::queue::GenericReceiveError::Empty => break, + satrs::queue::GenericReceiveError::TxDisconnected(e) => { + log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e); + } + }, } } } @@ -365,9 +357,8 @@ impl< impl< ComInterface: SpiInterface, - TmSender: EcssTmSender, SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, - > ModeProvider for MgmHandlerLis3Mdl + > ModeProvider for MgmHandlerLis3Mdl { fn mode_and_submode(&self) -> ModeAndSubmode { self.mode_helpers.current @@ -376,9 +367,8 @@ impl< impl< ComInterface: SpiInterface, - TmSender: EcssTmSender, SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, - > ModeRequestHandler for MgmHandlerLis3Mdl + > ModeRequestHandler for MgmHandlerLis3Mdl { type Error = ModeError; @@ -386,6 +376,7 @@ impl< &mut self, requestor: MessageMetadata, mode_and_submode: ModeAndSubmode, + _forced: bool, ) -> Result<(), satrs::mode::ModeError> { log::info!( "{}: transitioning to mode {:?}", @@ -448,10 +439,9 @@ impl< requestor.sender_id() ); } - self.mode_interface - .reply_to_pus_tx - .send(GenericMessage::new(requestor, reply)) - .map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?; + self.mode_node + .send_mode_reply(requestor, reply) + .map_err(ModeError::Send)?; Ok(()) } @@ -464,17 +454,44 @@ impl< } } +impl< + ComInterface: SpiInterface, + SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, + > ModeNode for MgmHandlerLis3Mdl +{ + fn id(&self) -> satrs::ComponentId { + self.id.into() + } +} + +impl< + ComInterface: SpiInterface, + SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, + > ModeChild for MgmHandlerLis3Mdl +{ + type Sender = mpsc::SyncSender>; + + fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) { + self.mode_node.add_message_target(id, reply_sender); + } +} + #[cfg(test)] mod tests { - use std::sync::{mpsc, Arc}; + use std::{ + collections::HashMap, + sync::{mpsc, Arc}, + }; use satrs::{ mode::{ModeReply, ModeRequest}, + mode_tree::ModeParent, power::SwitchStateBinary, request::{GenericMessage, UniqueApidTargetId}, tmtc::PacketAsVec, + ComponentId, }; - use satrs_example::config::components::Apid; + use satrs_example::config::components::{Apid, MGM_ASSEMBLY}; use satrs_minisim::acs::lis3mdl::MgmLis3RawValues; use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest}; @@ -502,49 +519,88 @@ mod tests { } } + #[allow(dead_code)] pub struct MgmTestbench { - pub mode_request_tx: mpsc::Sender>, + pub mode_request_tx: mpsc::SyncSender>, pub mode_reply_rx_to_pus: mpsc::Receiver>, pub mode_reply_rx_to_parent: mpsc::Receiver>, pub composite_request_tx: mpsc::Sender>, pub hk_reply_rx: mpsc::Receiver>, pub tm_rx: mpsc::Receiver, - pub handler: - MgmHandlerLis3Mdl, TestSwitchHelper>, + pub handler: MgmHandlerLis3Mdl, + } + + #[derive(Default)] + pub struct MgmAssemblyMock( + pub HashMap>>, + ); + + impl ModeNode for MgmAssemblyMock { + fn id(&self) -> satrs::ComponentId { + PUS_MODE_SERVICE.into() + } + } + + impl ModeParent for MgmAssemblyMock { + type Sender = mpsc::SyncSender>; + + fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) { + self.0.insert(id, request_sender); + } + } + + #[derive(Default)] + pub struct PusMock { + pub request_sender_map: HashMap>>, + } + + impl ModeNode for PusMock { + fn id(&self) -> satrs::ComponentId { + PUS_MODE_SERVICE.into() + } + } + + impl ModeParent for PusMock { + type Sender = mpsc::SyncSender>; + + fn add_mode_child(&mut self, id: satrs::ComponentId, request_sender: Self::Sender) { + self.request_sender_map.insert(id, request_sender); + } } impl MgmTestbench { pub fn new() -> Self { - let (request_tx, request_rx) = mpsc::channel(); - let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::channel(); + let (request_tx, request_rx) = mpsc::sync_channel(5); + let (reply_tx_to_pus, reply_rx_to_pus) = mpsc::sync_channel(5); let (reply_tx_to_parent, reply_rx_to_parent) = mpsc::sync_channel(5); - let mode_interface = MpscModeLeafInterface { - request_rx, - reply_to_pus_tx: reply_tx_to_pus, - reply_to_parent_tx: reply_tx_to_parent, - }; + let id = UniqueApidTargetId::new(Apid::Acs as u16, 1); + let mode_node = ModeRequestHandlerMpscBounded::new(id.into(), request_rx); let (composite_request_tx, composite_request_rx) = mpsc::channel(); - let (hk_reply_tx, hk_reply_rx) = mpsc::channel(); - let (tm_tx, tm_rx) = mpsc::channel::(); + let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10); + let (tm_tx, tm_rx) = mpsc::sync_channel(10); + let tm_sender = TmTcSender::Heap(tm_tx); let shared_mgm_set = Arc::default(); + let mut handler = MgmHandlerLis3Mdl::new( + id, + "TEST_MGM", + mode_node, + composite_request_rx, + hk_reply_tx, + TestSwitchHelper::default(), + tm_sender, + TestSpiInterface::default(), + shared_mgm_set, + ); + handler.add_mode_parent(PUS_MODE_SERVICE.into(), reply_tx_to_pus); + handler.add_mode_parent(MGM_ASSEMBLY.into(), reply_tx_to_parent); Self { mode_request_tx: request_tx, mode_reply_rx_to_pus: reply_rx_to_pus, mode_reply_rx_to_parent: reply_rx_to_parent, composite_request_tx, + handler, tm_rx, hk_reply_rx, - handler: MgmHandlerLis3Mdl::new( - UniqueApidTargetId::new(Apid::Acs as u16, 1), - "TEST_MGM", - mode_interface, - composite_request_rx, - hk_reply_tx, - TestSwitchHelper::default(), - tm_tx, - TestSpiInterface::default(), - shared_mgm_set, - ), } } } @@ -575,7 +631,10 @@ mod tests { .mode_request_tx .send(GenericMessage::new( MessageMetadata::new(0, PUS_MODE_SERVICE.id()), - ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)), + ModeRequest::SetMode { + mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), + forced: false, + }, )) .expect("failed to send mode request"); testbench.handler.periodic_operation(); @@ -633,7 +692,10 @@ mod tests { .mode_request_tx .send(GenericMessage::new( MessageMetadata::new(0, PUS_MODE_SERVICE.id()), - ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)), + ModeRequest::SetMode { + mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), + forced: false, + }, )) .expect("failed to send mode request"); testbench.handler.periodic_operation(); diff --git a/satrs-example/src/config.rs b/satrs-example/src/config.rs index 5608bf3..5b4a8dd 100644 --- a/satrs-example/src/config.rs +++ b/satrs-example/src/config.rs @@ -149,11 +149,13 @@ pub mod components { #[derive(Copy, Clone, PartialEq, Eq)] pub enum AcsId { Mgm0 = 0, + Assembly = 1, } #[derive(Copy, Clone, PartialEq, Eq)] pub enum EpsId { Pcdu = 0, + Subsystem = 1, } #[derive(Copy, Clone, PartialEq, Eq)] @@ -176,8 +178,12 @@ pub mod components { UniqueApidTargetId::new(Apid::GenericPus as u16, PusId::PusHk as u32); pub const PUS_SCHED_SERVICE: UniqueApidTargetId = UniqueApidTargetId::new(Apid::Sched as u16, 0); + pub const MGM_ASSEMBLY: UniqueApidTargetId = + UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Assembly as u32); pub const MGM_HANDLER_0: UniqueApidTargetId = UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Mgm0 as u32); + pub const EPS_SUBSYSTEM: UniqueApidTargetId = + UniqueApidTargetId::new(Apid::Eps as u16, EpsId::Subsystem as u32); pub const PCDU_HANDLER: UniqueApidTargetId = UniqueApidTargetId::new(Apid::Eps as u16, EpsId::Pcdu as u32); pub const UDP_SERVER: UniqueApidTargetId = diff --git a/satrs-example/src/eps/pcdu.rs b/satrs-example/src/eps/pcdu.rs index 908bfb2..e975c1d 100644 --- a/satrs-example/src/eps/pcdu.rs +++ b/satrs-example/src/eps/pcdu.rs @@ -8,15 +8,19 @@ use derive_new::new; use num_enum::{IntoPrimitive, TryFromPrimitive}; use satrs::{ hk::{HkRequest, HkRequestVariant}, - mode::{ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler}, + mode::{ + ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler, + ModeRequestHandlerMpscBounded, + }, + mode_tree::{ModeChild, ModeNode}, power::SwitchRequest, pus::{EcssTmSender, PusTmVariant}, - queue::{GenericSendError, GenericTargetedMessagingError}, + queue::GenericSendError, request::{GenericMessage, MessageMetadata, UniqueApidTargetId}, spacepackets::ByteConversionError, }; use satrs_example::{ - config::components::{NO_SENDER, PUS_MODE_SERVICE}, + config::components::{NO_SENDER, PCDU_HANDLER, PUS_MODE_SERVICE}, DeviceMode, TimestampHelper, }; use satrs_minisim::{ @@ -28,7 +32,6 @@ use satrs_minisim::{ use serde::{Deserialize, Serialize}; use crate::{ - acs::mgm::MpscModeLeafInterface, hk::PusHkHelper, pus::hk::{HkReply, HkReplyVariant}, requests::CompositeRequest, @@ -203,9 +206,9 @@ pub type SharedSwitchSet = Arc>; pub struct PcduHandler { id: UniqueApidTargetId, dev_str: &'static str, - mode_interface: MpscModeLeafInterface, + mode_node: ModeRequestHandlerMpscBounded, composite_request_rx: mpsc::Receiver>, - hk_reply_tx: mpsc::Sender>, + hk_reply_tx: mpsc::SyncSender>, switch_request_rx: mpsc::Receiver>, tm_sender: TmSender, pub com_interface: ComInterface, @@ -324,25 +327,30 @@ impl PcduHandler { - let result = self.handle_mode_request(msg); - // TODO: Trigger event? - if result.is_err() { - log::warn!( - "{}: mode request failed with error {:?}", - self.dev_str, - result.err().unwrap() - ); - } - } - Err(e) => { - if e != mpsc::TryRecvError::Empty { - log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e); + match self.mode_node.try_recv_mode_request() { + Ok(opt_msg) => { + if let Some(msg) = opt_msg { + let result = self.handle_mode_request(msg); + // TODO: Trigger event? + if result.is_err() { + log::warn!( + "{}: mode request failed with error {:?}", + self.dev_str, + result.err().unwrap() + ); + } } else { break; } } + Err(e) => match e { + satrs::queue::GenericReceiveError::Empty => { + break; + } + satrs::queue::GenericReceiveError::TxDisconnected(_) => { + log::warn!("{}: failed to receive mode request: {:?}", self.dev_str, e); + } + }, } } } @@ -412,6 +420,7 @@ impl ModeRequestHandler &mut self, requestor: MessageMetadata, mode_and_submode: ModeAndSubmode, + _forced: bool, ) -> Result<(), satrs::mode::ModeError> { log::info!( "{}: transitioning to mode {:?}", @@ -466,10 +475,9 @@ impl ModeRequestHandler requestor.sender_id() ); } - self.mode_interface - .reply_to_pus_tx - .send(GenericMessage::new(requestor, reply)) - .map_err(|_| GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected))?; + self.mode_node + .send_mode_reply(requestor, reply) + .map_err(|_| GenericSendError::RxDisconnected)?; Ok(()) } @@ -482,6 +490,24 @@ impl ModeRequestHandler } } +impl ModeNode + for PcduHandler +{ + fn id(&self) -> satrs::ComponentId { + PCDU_HANDLER.into() + } +} + +impl ModeChild + for PcduHandler +{ + type Sender = mpsc::SyncSender>; + + fn add_mode_parent(&mut self, id: satrs::ComponentId, reply_sender: Self::Sender) { + self.mode_node.add_message_target(id, reply_sender); + } +} + #[cfg(test)] mod tests { use std::sync::mpsc; @@ -489,7 +515,7 @@ mod tests { use satrs::{ mode::ModeRequest, power::SwitchStateBinary, request::GenericMessage, tmtc::PacketAsVec, }; - use satrs_example::config::components::{Apid, MGM_HANDLER_0}; + use satrs_example::config::components::{Apid, EPS_SUBSYSTEM, MGM_HANDLER_0, PCDU_HANDLER}; use satrs_minisim::eps::SwitchMapBinary; use super::*; @@ -530,7 +556,7 @@ mod tests { } pub struct PcduTestbench { - pub mode_request_tx: mpsc::Sender>, + pub mode_request_tx: mpsc::SyncSender>, pub mode_reply_rx_to_pus: mpsc::Receiver>, pub mode_reply_rx_to_parent: mpsc::Receiver>, pub composite_request_tx: mpsc::Sender>, @@ -542,19 +568,29 @@ mod tests { impl PcduTestbench { pub fn new() -> Self { - let (mode_request_tx, mode_request_rx) = mpsc::channel(); - let (mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::channel(); + let (mode_request_tx, mode_request_rx) = mpsc::sync_channel(5); + let (mode_reply_tx_to_pus, mode_reply_rx_to_pus) = mpsc::sync_channel(5); let (mode_reply_tx_to_parent, mode_reply_rx_to_parent) = mpsc::sync_channel(5); - let mode_interface = MpscModeLeafInterface { - request_rx: mode_request_rx, - reply_to_pus_tx: mode_reply_tx_to_pus, - reply_to_parent_tx: mode_reply_tx_to_parent, - }; + let mode_node = + ModeRequestHandlerMpscBounded::new(PCDU_HANDLER.into(), mode_request_rx); let (composite_request_tx, composite_request_rx) = mpsc::channel(); - let (hk_reply_tx, hk_reply_rx) = mpsc::channel(); + let (hk_reply_tx, hk_reply_rx) = mpsc::sync_channel(10); let (tm_tx, tm_rx) = mpsc::channel::(); let (switch_request_tx, switch_reqest_rx) = mpsc::channel(); let shared_switch_map = Arc::new(Mutex::new(SwitchSet::default())); + let mut handler = PcduHandler::new( + UniqueApidTargetId::new(Apid::Eps as u16, 0), + "TEST_PCDU", + mode_node, + composite_request_rx, + hk_reply_tx, + switch_reqest_rx, + tm_tx, + SerialInterfaceTest::default(), + shared_switch_map, + ); + handler.add_mode_parent(EPS_SUBSYSTEM.into(), mode_reply_tx_to_parent); + handler.add_mode_parent(PUS_MODE_SERVICE.into(), mode_reply_tx_to_pus); Self { mode_request_tx, mode_reply_rx_to_pus, @@ -563,17 +599,7 @@ mod tests { hk_reply_rx, tm_rx, switch_request_tx, - handler: PcduHandler::new( - UniqueApidTargetId::new(Apid::Eps as u16, 0), - "TEST_PCDU", - mode_interface, - composite_request_rx, - hk_reply_tx, - switch_reqest_rx, - tm_tx, - SerialInterfaceTest::default(), - shared_switch_map, - ), + handler, } } @@ -660,7 +686,10 @@ mod tests { .mode_request_tx .send(GenericMessage::new( MessageMetadata::new(0, PUS_MODE_SERVICE.id()), - ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)), + ModeRequest::SetMode { + mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), + forced: false, + }, )) .expect("failed to send mode request"); let switch_map_shared = testbench.handler.shared_switch_map.lock().unwrap(); @@ -692,7 +721,10 @@ mod tests { .mode_request_tx .send(GenericMessage::new( MessageMetadata::new(0, PUS_MODE_SERVICE.id()), - ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as u32, 0)), + ModeRequest::SetMode { + mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as u32, 0), + forced: false, + }, )) .expect("failed to send mode request"); testbench diff --git a/satrs-example/src/interface/tcp.rs b/satrs-example/src/interface/tcp.rs index 021ad31..720f705 100644 --- a/satrs-example/src/interface/tcp.rs +++ b/satrs-example/src/interface/tcp.rs @@ -1,19 +1,20 @@ use std::time::Duration; use std::{ collections::{HashSet, VecDeque}, - fmt::Debug, - marker::PhantomData, sync::{Arc, Mutex}, }; use log::{info, warn}; +use satrs::tmtc::StoreAndSendError; use satrs::{ encoding::ccsds::{SpValidity, SpacePacketValidator}, hal::std::tcp_server::{HandledConnectionHandler, ServerConfig, TcpSpacepacketsServer}, spacepackets::{CcsdsPacket, PacketId}, - tmtc::{PacketSenderRaw, PacketSource}, + tmtc::PacketSource, }; +use crate::tmtc::sender::TmTcSender; + #[derive(Default)] pub struct ConnectionFinishedHandler {} @@ -111,31 +112,23 @@ pub type TcpServer = TcpSpacepacketsServer< SendError, >; -pub struct TcpTask, SendError: Debug + 'static>( - pub TcpServer, - PhantomData, -); +pub struct TcpTask(pub TcpServer); -impl, SendError: Debug + 'static> - TcpTask -{ +impl TcpTask { pub fn new( cfg: ServerConfig, tm_source: SyncTcpTmSource, - tc_sender: TcSender, + tc_sender: TmTcSender, valid_ids: HashSet, ) -> Result { - Ok(Self( - TcpSpacepacketsServer::new( - cfg, - tm_source, - tc_sender, - SimplePacketValidator { valid_ids }, - ConnectionFinishedHandler::default(), - None, - )?, - PhantomData, - )) + Ok(Self(TcpSpacepacketsServer::new( + cfg, + tm_source, + tc_sender, + SimplePacketValidator { valid_ids }, + ConnectionFinishedHandler::default(), + None, + )?)) } pub fn periodic_operation(&mut self) { diff --git a/satrs-example/src/interface/udp.rs b/satrs-example/src/interface/udp.rs index e7720bb..bf0a71c 100644 --- a/satrs-example/src/interface/udp.rs +++ b/satrs-example/src/interface/udp.rs @@ -1,15 +1,16 @@ -use core::fmt::Debug; use std::net::{SocketAddr, UdpSocket}; use std::sync::mpsc; use log::{info, warn}; use satrs::pus::HandlingStatus; -use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderRaw}; +use satrs::tmtc::{PacketAsVec, PacketInPool, StoreAndSendError}; use satrs::{ hal::std::udp_server::{ReceiveResult, UdpTcServer}, pool::{PoolProviderWithGuards, SharedStaticMemoryPool}, }; +use crate::tmtc::sender::TmTcSender; + pub trait UdpTmHandler { fn send_tm_to_udp_client(&mut self, socket: &UdpSocket, recv_addr: &SocketAddr); } @@ -65,21 +66,12 @@ impl UdpTmHandler for DynamicUdpTmHandler { } } -pub struct UdpTmtcServer< - TcSender: PacketSenderRaw, - TmHandler: UdpTmHandler, - SendError, -> { - pub udp_tc_server: UdpTcServer, +pub struct UdpTmtcServer { + pub udp_tc_server: UdpTcServer, pub tm_handler: TmHandler, } -impl< - TcSender: PacketSenderRaw, - TmHandler: UdpTmHandler, - SendError: Debug + 'static, - > UdpTmtcServer -{ +impl UdpTmtcServer { pub fn periodic_operation(&mut self) { loop { if self.poll_tc_server() == HandlingStatus::Empty { @@ -115,7 +107,6 @@ impl< mod tests { use std::net::Ipv4Addr; use std::{ - cell::RefCell, collections::VecDeque, net::IpAddr, sync::{Arc, Mutex}, @@ -126,30 +117,16 @@ mod tests { ecss::{tc::PusTcCreator, WritablePusPacket}, SpHeader, }, - tmtc::PacketSenderRaw, ComponentId, }; use satrs_example::config::{components, OBSW_SERVER_ADDR}; + use crate::tmtc::sender::{MockSender, TmTcSender}; + use super::*; const UDP_SERVER_ID: ComponentId = 0x05; - #[derive(Default, Debug)] - pub struct TestSender { - tc_vec: RefCell>, - } - - impl PacketSenderRaw for TestSender { - type Error = (); - - fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> { - let mut mut_queue = self.tc_vec.borrow_mut(); - mut_queue.push_back(PacketAsVec::new(sender_id, tc_raw.to_vec())); - Ok(()) - } - } - #[derive(Default, Debug, Clone)] pub struct TestTmHandler { addrs_to_send_to: Arc>>, @@ -164,8 +141,7 @@ mod tests { #[test] fn test_basic() { let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), 0); - let test_receiver = TestSender::default(); - // let tc_queue = test_receiver.tc_vec.clone(); + let test_receiver = TmTcSender::Mock(MockSender::default()); let udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap(); let tm_handler = TestTmHandler::default(); @@ -175,7 +151,13 @@ mod tests { tm_handler, }; udp_dyn_server.periodic_operation(); - let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow(); + let queue = udp_dyn_server + .udp_tc_server + .tc_sender + .get_mock_sender() + .unwrap() + .0 + .borrow(); assert!(queue.is_empty()); assert!(tm_handler_calls.lock().unwrap().is_empty()); } @@ -183,8 +165,7 @@ mod tests { #[test] fn test_transactions() { let sock_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0); - let test_receiver = TestSender::default(); - // let tc_queue = test_receiver.tc_vec.clone(); + let test_receiver = TmTcSender::Mock(MockSender::default()); let udp_tc_server = UdpTcServer::new(UDP_SERVER_ID, sock_addr, 2048, test_receiver).unwrap(); let server_addr = udp_tc_server.socket.local_addr().unwrap(); @@ -204,7 +185,13 @@ mod tests { client.send_to(&ping_tc, server_addr).unwrap(); udp_dyn_server.periodic_operation(); { - let mut queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow_mut(); + let mut queue = udp_dyn_server + .udp_tc_server + .tc_sender + .get_mock_sender() + .unwrap() + .0 + .borrow_mut(); assert!(!queue.is_empty()); let packet_with_sender = queue.pop_front().unwrap(); assert_eq!(packet_with_sender.packet, ping_tc); @@ -219,7 +206,13 @@ mod tests { assert_eq!(received_addr, client_addr); } udp_dyn_server.periodic_operation(); - let queue = udp_dyn_server.udp_tc_server.tc_sender.tc_vec.borrow(); + let queue = udp_dyn_server + .udp_tc_server + .tc_sender + .get_mock_sender() + .unwrap() + .0 + .borrow(); assert!(queue.is_empty()); drop(queue); // Still tries to send to the same client. diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index 317e3f0..7be5717 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -1,3 +1,72 @@ +use std::{ + net::{IpAddr, SocketAddr}, + sync::{mpsc, Arc, Mutex}, + thread, + time::Duration, +}; + +use acs::mgm::{MgmHandlerLis3Mdl, SpiDummyInterface, SpiSimInterface, SpiSimInterfaceWrapper}; +use eps::{ + pcdu::{PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper}, + PowerSwitchHelper, +}; +use events::EventHandler; +use interface::{ + sim_client_udp::create_sim_client, + tcp::{SyncTcpTmSource, TcpTask}, + udp::UdpTmtcServer, +}; +use log::info; +use logger::setup_logger; +use pus::{ + action::create_action_service, + event::create_event_service, + hk::create_hk_service, + mode::create_mode_service, + scheduler::{create_scheduler_service, TcReleaser}, + stack::PusStack, + test::create_test_service, + PusTcDistributor, PusTcMpscRouter, +}; +use requests::GenericRequestRouter; +use satrs::{ + hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer}, + mode::{Mode, ModeAndSubmode, ModeRequest, ModeRequestHandlerMpscBounded}, + mode_tree::connect_mode_nodes, + pus::{event_man::EventRequestWithToken, EcssTcInMemConverter, HandlingStatus}, + request::{GenericMessage, MessageMetadata}, + spacepackets::time::{cds::CdsTime, TimeWriter}, +}; +use satrs_example::{ + config::{ + components::{MGM_HANDLER_0, NO_SENDER, PCDU_HANDLER, TCP_SERVER, UDP_SERVER}, + pool::create_sched_tc_pool, + tasks::{FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS}, + OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT, + }, + DeviceMode, +}; +use tmtc::sender::TmTcSender; +use tmtc::{tc_source::TcSourceTask, tm_sink::TmSink}; + +cfg_if::cfg_if! { + if #[cfg(feature = "heap_tmtc")] { + use interface::udp::DynamicUdpTmHandler; + use satrs::pus::EcssTcInVecConverter; + use tmtc::{tc_source::TcSourceTaskDynamic, tm_sink::TmSinkDynamic}; + } else { + use std::sync::RwLock; + use interface::udp::StaticUdpTmHandler; + use satrs::pus::EcssTcInSharedPoolConverter; + use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool}; + use satrs_example::config::pool::create_static_pools; + use tmtc::{ + tc_source::TcSourceTaskStatic, + tm_sink::TmSinkStatic, + }; + } +} + mod acs; mod eps; mod events; @@ -6,106 +75,68 @@ mod interface; mod logger; mod pus; mod requests; +mod spi; mod tmtc; -use crate::eps::pcdu::{ - PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper, -}; -use crate::eps::PowerSwitchHelper; -use crate::events::EventHandler; -use crate::interface::udp::DynamicUdpTmHandler; -use crate::pus::stack::PusStack; -use crate::tmtc::tc_source::{TcSourceTaskDynamic, TcSourceTaskStatic}; -use crate::tmtc::tm_sink::{TmSinkDynamic, TmSinkStatic}; -use log::info; -use pus::test::create_test_service_dynamic; -use satrs::hal::std::tcp_server::ServerConfig; -use satrs::hal::std::udp_server::UdpTcServer; -use satrs::pus::HandlingStatus; -use satrs::request::{GenericMessage, MessageMetadata}; -use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool}; -use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools}; -use satrs_example::config::tasks::{ - FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC, SIM_CLIENT_IDLE_DELAY_MS, -}; -use satrs_example::config::{OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT}; -use satrs_example::DeviceMode; +fn main() { + setup_logger().expect("setting up logging with fern failed"); + println!("Running OBSW example"); -use crate::acs::mgm::{ - MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface, SpiSimInterface, - SpiSimInterfaceWrapper, -}; -use crate::interface::sim_client_udp::create_sim_client; -use crate::interface::tcp::{SyncTcpTmSource, TcpTask}; -use crate::interface::udp::{StaticUdpTmHandler, UdpTmtcServer}; -use crate::logger::setup_logger; -use crate::pus::action::{create_action_service_dynamic, create_action_service_static}; -use crate::pus::event::{create_event_service_dynamic, create_event_service_static}; -use crate::pus::hk::{create_hk_service_dynamic, create_hk_service_static}; -use crate::pus::mode::{create_mode_service_dynamic, create_mode_service_static}; -use crate::pus::scheduler::{create_scheduler_service_dynamic, create_scheduler_service_static}; -use crate::pus::test::create_test_service_static; -use crate::pus::{PusTcDistributor, PusTcMpscRouter}; -use crate::requests::{CompositeRequest, GenericRequestRouter}; -use satrs::mode::{Mode, ModeAndSubmode, ModeRequest}; -use satrs::pus::event_man::EventRequestWithToken; -use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter}; -use satrs_example::config::components::{ - MGM_HANDLER_0, NO_SENDER, PCDU_HANDLER, TCP_SERVER, UDP_SERVER, -}; -use std::net::{IpAddr, SocketAddr}; -use std::sync::{mpsc, Mutex}; -use std::sync::{Arc, RwLock}; -use std::thread; -use std::time::Duration; + cfg_if::cfg_if! { + if #[cfg(not(feature = "heap_tmtc"))] { + let (tm_pool, tc_pool) = create_static_pools(); + let shared_tm_pool = Arc::new(RwLock::new(tm_pool)); + let shared_tc_pool = Arc::new(RwLock::new(tc_pool)); + let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool); + let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool); + } + } -#[allow(dead_code)] -fn static_tmtc_pool_main() { - let (tm_pool, tc_pool) = create_static_pools(); - let shared_tm_pool = Arc::new(RwLock::new(tm_pool)); - let shared_tc_pool = Arc::new(RwLock::new(tc_pool)); - let shared_tm_pool_wrapper = SharedPacketPool::new(&shared_tm_pool); - let shared_tc_pool_wrapper = SharedPacketPool::new(&shared_tc_pool); let (tc_source_tx, tc_source_rx) = mpsc::sync_channel(50); let (tm_sink_tx, tm_sink_rx) = mpsc::sync_channel(50); let (tm_server_tx, tm_server_rx) = mpsc::sync_channel(50); - let tm_sink_tx_sender = - PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone()); + cfg_if::cfg_if! { + if #[cfg(not(feature = "heap_tmtc"))] { + let tm_sender = TmTcSender::Static( + PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone()) + ); + } else if #[cfg(feature = "heap_tmtc")] { + let tm_sender = TmTcSender::Heap(tm_sink_tx.clone()); + } + } let (sim_request_tx, sim_request_rx) = mpsc::channel(); let (mgm_sim_reply_tx, mgm_sim_reply_rx) = mpsc::channel(); let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel(); let mut opt_sim_client = create_sim_client(sim_request_rx); - let (mgm_handler_composite_tx, mgm_handler_composite_rx) = - mpsc::sync_channel::>(10); - let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) = - mpsc::sync_channel::>(30); - - let (mgm_handler_mode_tx, mgm_handler_mode_rx) = - mpsc::sync_channel::>(5); - let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) = - mpsc::sync_channel::>(5); + let (mgm_handler_composite_tx, mgm_handler_composite_rx) = mpsc::sync_channel(10); + let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) = mpsc::sync_channel(30); + let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::sync_channel(5); + let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) = mpsc::sync_channel(5); // 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(MGM_HANDLER_0.id(), mgm_handler_composite_tx); - request_map - .mode_router_map - .insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx); request_map .composite_router_map .insert(PCDU_HANDLER.id(), pcdu_handler_composite_tx); - request_map - .mode_router_map - .insert(PCDU_HANDLER.id(), pcdu_handler_mode_tx.clone()); // This helper structure is used by all telecommand providers which need to send telecommands // to the TC source. - let tc_source = PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone()); + cfg_if::cfg_if! { + if #[cfg(not(feature = "heap_tmtc"))] { + let tc_sender_with_shared_pool = + PacketSenderWithSharedPool::new(tc_source_tx, shared_tc_pool_wrapper.clone()); + let tc_in_mem_converter = + EcssTcInMemConverter::Static(EcssTcInSharedPoolConverter::new(shared_tc_pool, 4096)); + } else if #[cfg(feature = "heap_tmtc")] { + let tc_in_mem_converter = EcssTcInMemConverter::Heap(EcssTcInVecConverter::default()); + } + } // Create event handling components // These sender handles are used to send event requests, for example to enable or disable @@ -117,17 +148,24 @@ fn static_tmtc_pool_main() { // in the sat-rs documentation. let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx); - 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_mode_tx, pus_mode_rx) = mpsc::channel(); + let (pus_test_tx, pus_test_rx) = mpsc::sync_channel(20); + let (pus_event_tx, pus_event_rx) = mpsc::sync_channel(10); + let (pus_sched_tx, pus_sched_rx) = mpsc::sync_channel(50); + let (pus_hk_tx, pus_hk_rx) = mpsc::sync_channel(50); + let (pus_action_tx, pus_action_rx) = mpsc::sync_channel(50); + let (pus_mode_tx, pus_mode_rx) = mpsc::sync_channel(50); 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 (pus_hk_reply_tx, pus_hk_reply_rx) = mpsc::sync_channel(50); + let (pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::sync_channel(30); + cfg_if::cfg_if! { + if #[cfg(not(feature = "heap_tmtc"))] { + let tc_releaser = TcReleaser::Static(tc_sender_with_shared_pool.clone()); + } else if #[cfg(feature = "heap_tmtc")] { + let tc_releaser = TcReleaser::Heap(tc_source_tx.clone()); + } + } let pus_router = PusTcMpscRouter { test_tc_sender: pus_test_tx, event_tc_sender: pus_event_tx, @@ -136,41 +174,42 @@ fn static_tmtc_pool_main() { action_tc_sender: pus_action_tx, mode_tc_sender: pus_mode_tx, }; - let pus_test_service = create_test_service_static( - tm_sink_tx_sender.clone(), - shared_tc_pool.clone(), + let pus_test_service = create_test_service( + tm_sender.clone(), + tc_in_mem_converter.clone(), event_tx.clone(), pus_test_rx, ); - let pus_scheduler_service = create_scheduler_service_static( - tm_sink_tx_sender.clone(), - tc_source.clone(), + let pus_scheduler_service = create_scheduler_service( + tm_sender.clone(), + tc_in_mem_converter.clone(), + tc_releaser, pus_sched_rx, create_sched_tc_pool(), ); - let pus_event_service = create_event_service_static( - tm_sink_tx_sender.clone(), - shared_tc_pool.clone(), + let pus_event_service = create_event_service( + tm_sender.clone(), + tc_in_mem_converter.clone(), pus_event_rx, event_request_tx, ); - let pus_action_service = create_action_service_static( - tm_sink_tx_sender.clone(), - shared_tc_pool.clone(), + let pus_action_service = create_action_service( + tm_sender.clone(), + tc_in_mem_converter.clone(), pus_action_rx, request_map.clone(), pus_action_reply_rx, ); - let pus_hk_service = create_hk_service_static( - tm_sink_tx_sender.clone(), - shared_tc_pool.clone(), + let pus_hk_service = create_hk_service( + tm_sender.clone(), + tc_in_mem_converter.clone(), pus_hk_rx, request_map.clone(), pus_hk_reply_rx, ); - let pus_mode_service = create_mode_service_static( - tm_sink_tx_sender.clone(), - shared_tc_pool.clone(), + let pus_mode_service = create_mode_service( + tm_sender.clone(), + tc_in_mem_converter.clone(), pus_mode_rx, request_map, pus_mode_reply_rx, @@ -184,21 +223,36 @@ fn static_tmtc_pool_main() { pus_mode_service, ); - let mut tmtc_task = TcSourceTaskStatic::new( - shared_tc_pool_wrapper.clone(), - tc_source_rx, - PusTcDistributor::new(tm_sink_tx_sender, pus_router), - ); + cfg_if::cfg_if! { + if #[cfg(not(feature = "heap_tmtc"))] { + let mut tmtc_task = TcSourceTask::Static(TcSourceTaskStatic::new( + shared_tc_pool_wrapper.clone(), + tc_source_rx, + PusTcDistributor::new(tm_sender.clone(), pus_router), + )); + let tc_sender = TmTcSender::Static(tc_sender_with_shared_pool); + let udp_tm_handler = StaticUdpTmHandler { + tm_rx: tm_server_rx, + tm_store: shared_tm_pool.clone(), + }; + } else if #[cfg(feature = "heap_tmtc")] { + let mut tmtc_task = TcSourceTask::Heap(TcSourceTaskDynamic::new( + tc_source_rx, + PusTcDistributor::new(tm_sender.clone(), pus_router), + )); + let tc_sender = TmTcSender::Heap(tc_source_tx.clone()); + let udp_tm_handler = DynamicUdpTmHandler { + tm_rx: tm_server_rx, + }; + } + } let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT); - let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_source.clone()) + let udp_tc_server = UdpTcServer::new(UDP_SERVER.id(), sock_addr, 2048, tc_sender.clone()) .expect("creating UDP TMTC server failed"); let mut udp_tmtc_server = UdpTmtcServer { udp_tc_server, - tm_handler: StaticUdpTmHandler { - tm_rx: tm_server_rx, - tm_store: shared_tm_pool.clone(), - }, + tm_handler: udp_tm_handler, }; let tcp_server_cfg = ServerConfig::new( @@ -212,32 +266,35 @@ fn static_tmtc_pool_main() { let mut tcp_server = TcpTask::new( tcp_server_cfg, sync_tm_tcp_source.clone(), - tc_source.clone(), + tc_sender, PACKET_ID_VALIDATOR.clone(), ) .expect("tcp server creation failed"); - let mut tm_sink = TmSinkStatic::new( - shared_tm_pool_wrapper, - sync_tm_tcp_source, - tm_sink_rx, - tm_server_tx, - ); - - let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) = - mpsc::sync_channel(5); + cfg_if::cfg_if! { + if #[cfg(not(feature = "heap_tmtc"))] { + let mut tm_sink = TmSink::Static(TmSinkStatic::new( + shared_tm_pool_wrapper, + sync_tm_tcp_source, + tm_sink_rx, + tm_server_tx, + )); + } else if #[cfg(feature = "heap_tmtc")] { + let mut tm_sink = TmSink::Heap(TmSinkDynamic::new( + sync_tm_tcp_source, + tm_sink_rx, + tm_server_tx, + )); + } + } let shared_switch_set = Arc::new(Mutex::default()); let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20); let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone()); let shared_mgm_set = Arc::default(); - let mgm_mode_leaf_interface = MpscModeLeafInterface { - request_rx: mgm_handler_mode_rx, - reply_to_pus_tx: pus_mode_reply_tx.clone(), - reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx, - }; - + let mgm_mode_node = + ModeRequestHandlerMpscBounded::new(MGM_HANDLER_0.into(), mgm_handler_mode_rx); let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() { sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx); SpiSimInterfaceWrapper::Sim(SpiSimInterface { @@ -250,22 +307,22 @@ fn static_tmtc_pool_main() { let mut mgm_handler = MgmHandlerLis3Mdl::new( MGM_HANDLER_0, "MGM_0", - mgm_mode_leaf_interface, + mgm_mode_node, mgm_handler_composite_rx, pus_hk_reply_tx.clone(), switch_helper.clone(), - tm_sink_tx.clone(), + tm_sender, mgm_spi_interface, shared_mgm_set, ); + // Connect PUS service to device handler. + connect_mode_nodes( + &mut pus_stack.mode_srv, + mgm_handler_mode_tx, + &mut mgm_handler, + pus_mode_reply_tx.clone(), + ); - let (pcdu_handler_mode_reply_to_parent_tx, _pcdu_handler_mode_reply_to_parent_rx) = - mpsc::sync_channel(10); - let pcdu_mode_leaf_interface = MpscModeLeafInterface { - request_rx: pcdu_handler_mode_rx, - reply_to_pus_tx: pus_mode_reply_tx, - reply_to_parent_tx: pcdu_handler_mode_reply_to_parent_tx, - }; let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() { sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx); SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new( @@ -275,11 +332,12 @@ fn static_tmtc_pool_main() { } else { SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default()) }; - + let pcdu_mode_node = + ModeRequestHandlerMpscBounded::new(PCDU_HANDLER.into(), pcdu_handler_mode_rx); let mut pcdu_handler = PcduHandler::new( PCDU_HANDLER, "PCDU", - pcdu_mode_leaf_interface, + pcdu_mode_node, pcdu_handler_composite_rx, pus_hk_reply_tx, switch_request_rx, @@ -287,11 +345,21 @@ fn static_tmtc_pool_main() { pcdu_serial_interface, shared_switch_set, ); + connect_mode_nodes( + &mut pus_stack.mode_srv, + pcdu_handler_mode_tx.clone(), + &mut pcdu_handler, + pus_mode_reply_tx, + ); + // The PCDU is a critical component which should be in normal mode immediately. pcdu_handler_mode_tx .send(GenericMessage::new( MessageMetadata::new(0, NO_SENDER), - ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as Mode, 0)), + ModeRequest::SetMode { + mode_and_submode: ModeAndSubmode::new(DeviceMode::Normal as Mode, 0), + forced: false, + }, )) .expect("sending initial mode request failed"); @@ -357,11 +425,13 @@ fn static_tmtc_pool_main() { .spawn(move || loop { // TODO: We should introduce something like a fixed timeslot helper to allow a more // declarative API. It would also be very useful for the AOCS task. - pcdu_handler.periodic_operation(eps::pcdu::OpCode::RegularOp); + // + // TODO: The fixed timeslot handler exists.. use it. + pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::RegularOp); thread::sleep(Duration::from_millis(50)); - pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies); + pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies); thread::sleep(Duration::from_millis(50)); - pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies); + pcdu_handler.periodic_operation(crate::eps::pcdu::OpCode::PollAndRecvReplies); thread::sleep(Duration::from_millis(300)); }) .unwrap(); @@ -397,322 +467,6 @@ fn static_tmtc_pool_main() { .expect("Joining PUS handler thread failed"); } -#[allow(dead_code)] -fn dyn_tmtc_pool_main() { - let (tc_source_tx, tc_source_rx) = mpsc::channel(); - let (tm_sink_tx, tm_sink_rx) = mpsc::channel(); - let (tm_server_tx, tm_server_rx) = mpsc::channel(); - - let (sim_request_tx, sim_request_rx) = mpsc::channel(); - let (mgm_sim_reply_tx, mgm_sim_reply_rx) = mpsc::channel(); - let (pcdu_sim_reply_tx, pcdu_sim_reply_rx) = mpsc::channel(); - let mut opt_sim_client = create_sim_client(sim_request_rx); - - // Some request are targetable. This map is used to retrieve sender handles based on a target ID. - let (mgm_handler_composite_tx, mgm_handler_composite_rx) = - mpsc::sync_channel::>(5); - let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) = - mpsc::sync_channel::>(10); - let (mgm_handler_mode_tx, mgm_handler_mode_rx) = - mpsc::sync_channel::>(5); - let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) = - mpsc::sync_channel::>(10); - - // 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(MGM_HANDLER_0.id(), mgm_handler_composite_tx); - request_map - .mode_router_map - .insert(MGM_HANDLER_0.id(), mgm_handler_mode_tx); - request_map - .composite_router_map - .insert(PCDU_HANDLER.id(), pcdu_handler_composite_tx); - request_map - .mode_router_map - .insert(PCDU_HANDLER.id(), pcdu_handler_mode_tx.clone()); - - // Create event handling components - // These sender handles are used to send event requests, for example to enable or disable - // certain events. - let (event_tx, event_rx) = mpsc::sync_channel(100); - let (event_request_tx, event_request_rx) = mpsc::channel::(); - // The event task is the core handler to perform the event routing and TM handling as specified - // in the sat-rs documentation. - let mut event_handler = EventHandler::new(tm_sink_tx.clone(), event_rx, event_request_rx); - - 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_mode_tx, pus_mode_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 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, - mode_tc_sender: pus_mode_tx, - }; - - let pus_test_service = - create_test_service_dynamic(tm_sink_tx.clone(), event_tx.clone(), pus_test_rx); - let pus_scheduler_service = create_scheduler_service_dynamic( - tm_sink_tx.clone(), - tc_source_tx.clone(), - pus_sched_rx, - create_sched_tc_pool(), - ); - - let pus_event_service = - create_event_service_dynamic(tm_sink_tx.clone(), pus_event_rx, event_request_tx); - let pus_action_service = create_action_service_dynamic( - tm_sink_tx.clone(), - pus_action_rx, - request_map.clone(), - pus_action_reply_rx, - ); - let pus_hk_service = create_hk_service_dynamic( - tm_sink_tx.clone(), - pus_hk_rx, - request_map.clone(), - pus_hk_reply_rx, - ); - let pus_mode_service = create_mode_service_dynamic( - tm_sink_tx.clone(), - pus_mode_rx, - request_map, - pus_mode_reply_rx, - ); - let mut pus_stack = PusStack::new( - pus_test_service, - pus_hk_service, - pus_event_service, - pus_action_service, - pus_scheduler_service, - pus_mode_service, - ); - - let mut tmtc_task = TcSourceTaskDynamic::new( - tc_source_rx, - PusTcDistributor::new(tm_sink_tx.clone(), pus_router), - ); - - let sock_addr = SocketAddr::new(IpAddr::V4(OBSW_SERVER_ADDR), SERVER_PORT); - 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, - }, - }; - - 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(), - tc_source_tx.clone(), - PACKET_ID_VALIDATOR.clone(), - ) - .expect("tcp server creation failed"); - - let mut tm_funnel = TmSinkDynamic::new(sync_tm_tcp_source, tm_sink_rx, tm_server_tx); - - let shared_switch_set = Arc::new(Mutex::default()); - let (switch_request_tx, switch_request_rx) = mpsc::sync_channel(20); - let switch_helper = PowerSwitchHelper::new(switch_request_tx, shared_switch_set.clone()); - - let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) = - mpsc::sync_channel(5); - let shared_mgm_set = Arc::default(); - let mode_leaf_interface = MpscModeLeafInterface { - request_rx: mgm_handler_mode_rx, - reply_to_pus_tx: pus_mode_reply_tx.clone(), - reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx, - }; - - let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() { - sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx); - SpiSimInterfaceWrapper::Sim(SpiSimInterface { - sim_request_tx: sim_request_tx.clone(), - sim_reply_rx: mgm_sim_reply_rx, - }) - } else { - SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default()) - }; - let mut mgm_handler = MgmHandlerLis3Mdl::new( - MGM_HANDLER_0, - "MGM_0", - mode_leaf_interface, - mgm_handler_composite_rx, - pus_hk_reply_tx.clone(), - switch_helper.clone(), - tm_sink_tx.clone(), - mgm_spi_interface, - shared_mgm_set, - ); - - let (pcdu_handler_mode_reply_to_parent_tx, _pcdu_handler_mode_reply_to_parent_rx) = - mpsc::sync_channel(10); - let pcdu_mode_leaf_interface = MpscModeLeafInterface { - request_rx: pcdu_handler_mode_rx, - reply_to_pus_tx: pus_mode_reply_tx, - reply_to_parent_tx: pcdu_handler_mode_reply_to_parent_tx, - }; - let pcdu_serial_interface = if let Some(sim_client) = opt_sim_client.as_mut() { - sim_client.add_reply_recipient(satrs_minisim::SimComponent::Pcdu, pcdu_sim_reply_tx); - SerialSimInterfaceWrapper::Sim(SerialInterfaceToSim::new( - sim_request_tx.clone(), - pcdu_sim_reply_rx, - )) - } else { - SerialSimInterfaceWrapper::Dummy(SerialInterfaceDummy::default()) - }; - let mut pcdu_handler = PcduHandler::new( - PCDU_HANDLER, - "PCDU", - pcdu_mode_leaf_interface, - pcdu_handler_composite_rx, - pus_hk_reply_tx, - switch_request_rx, - tm_sink_tx, - pcdu_serial_interface, - shared_switch_set, - ); - // The PCDU is a critical component which should be in normal mode immediately. - pcdu_handler_mode_tx - .send(GenericMessage::new( - MessageMetadata::new(0, NO_SENDER), - ModeRequest::SetMode(ModeAndSubmode::new(DeviceMode::Normal as Mode, 0)), - )) - .expect("sending initial mode request failed"); - - info!("Starting TMTC and UDP task"); - let jh_udp_tmtc = thread::Builder::new() - .name("sat-rs tmtc-udp".to_string()) - .spawn(move || { - info!("Running UDP server on port {SERVER_PORT}"); - loop { - udp_tmtc_server.periodic_operation(); - tmtc_task.periodic_operation(); - thread::sleep(Duration::from_millis(FREQ_MS_UDP_TMTC)); - } - }) - .unwrap(); - - info!("Starting TCP task"); - let jh_tcp = thread::Builder::new() - .name("sat-rs tcp".to_string()) - .spawn(move || { - info!("Running TCP server on port {SERVER_PORT}"); - loop { - tcp_server.periodic_operation(); - } - }) - .unwrap(); - - info!("Starting TM funnel task"); - let jh_tm_funnel = thread::Builder::new() - .name("sat-rs tm-sink".to_string()) - .spawn(move || loop { - tm_funnel.operation(); - }) - .unwrap(); - - let mut opt_jh_sim_client = None; - if let Some(mut sim_client) = opt_sim_client { - info!("Starting UDP sim client task"); - opt_jh_sim_client = Some( - thread::Builder::new() - .name("sat-rs sim adapter".to_string()) - .spawn(move || loop { - if sim_client.operation() == HandlingStatus::Empty { - std::thread::sleep(Duration::from_millis(SIM_CLIENT_IDLE_DELAY_MS)); - } - }) - .unwrap(), - ); - } - - info!("Starting AOCS thread"); - let jh_aocs = thread::Builder::new() - .name("sat-rs aocs".to_string()) - .spawn(move || loop { - mgm_handler.periodic_operation(); - thread::sleep(Duration::from_millis(FREQ_MS_AOCS)); - }) - .unwrap(); - - info!("Starting EPS thread"); - let jh_eps = thread::Builder::new() - .name("sat-rs eps".to_string()) - .spawn(move || loop { - // TODO: We should introduce something like a fixed timeslot helper to allow a more - // declarative API. It would also be very useful for the AOCS task. - pcdu_handler.periodic_operation(eps::pcdu::OpCode::RegularOp); - thread::sleep(Duration::from_millis(50)); - pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies); - thread::sleep(Duration::from_millis(50)); - pcdu_handler.periodic_operation(eps::pcdu::OpCode::PollAndRecvReplies); - thread::sleep(Duration::from_millis(300)); - }) - .unwrap(); - - info!("Starting PUS handler thread"); - let jh_pus_handler = thread::Builder::new() - .name("sat-rs pus".to_string()) - .spawn(move || loop { - pus_stack.periodic_operation(); - event_handler.periodic_operation(); - thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK)); - }) - .unwrap(); - - jh_udp_tmtc - .join() - .expect("Joining UDP TMTC server thread failed"); - jh_tcp - .join() - .expect("Joining TCP TMTC server thread failed"); - jh_tm_funnel - .join() - .expect("Joining TM Funnel thread failed"); - if let Some(jh_sim_client) = opt_jh_sim_client { - jh_sim_client - .join() - .expect("Joining SIM client thread failed"); - } - jh_aocs.join().expect("Joining AOCS thread failed"); - jh_eps.join().expect("Joining EPS thread failed"); - jh_pus_handler - .join() - .expect("Joining PUS handler thread failed"); -} - -fn main() { - setup_logger().expect("setting up logging with fern failed"); - println!("Running OBSW example"); - #[cfg(not(feature = "dyn_tmtc"))] - static_tmtc_pool_main(); - #[cfg(feature = "dyn_tmtc")] - dyn_tmtc_pool_main(); -} - pub fn update_time(time_provider: &mut CdsTime, timestamp: &mut [u8]) { time_provider .update_from_now() diff --git a/satrs-example/src/pus/action.rs b/satrs-example/src/pus/action.rs index 238f1c5..3d68ac3 100644 --- a/satrs-example/src/pus/action.rs +++ b/satrs-example/src/pus/action.rs @@ -1,6 +1,5 @@ use log::warn; use satrs::action::{ActionRequest, ActionRequestVariant}; -use satrs::pool::SharedStaticMemoryPool; use satrs::pus::action::{ ActionReplyPus, ActionReplyVariant, ActivePusActionRequestStd, DefaultActiveActionRequestMap, }; @@ -10,21 +9,20 @@ use satrs::pus::verification::{ VerificationReportingProvider, VerificationToken, }; use satrs::pus::{ - ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, - EcssTcInVecConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver, - MpscTmAsVecSender, PusPacketHandlingError, PusReplyHandler, PusServiceHelper, - PusTcToRequestConverter, + ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, EcssTmSender, EcssTmtcError, + GenericConversionError, MpscTcReceiver, PusPacketHandlingError, PusReplyHandler, + PusServiceHelper, PusTcToRequestConverter, }; use satrs::request::{GenericMessage, UniqueApidTargetId}; use satrs::spacepackets::ecss::tc::PusTcReader; use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket, PusServiceId}; -use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use satrs_example::config::components::PUS_ACTION_SERVICE; use satrs_example::config::tmtc_err; use std::sync::mpsc; use std::time::Duration; use crate::requests::GenericRequestRouter; +use crate::tmtc::sender::TmTcSender; use super::{ create_verification_reporter, generic_pus_request_timeout_handler, HandlingStatus, @@ -207,20 +205,20 @@ impl PusTcToRequestConverter for Actio } } -pub fn create_action_service_static( - tm_sender: PacketSenderWithSharedPool, - tc_pool: SharedStaticMemoryPool, +pub fn create_action_service( + tm_sender: TmTcSender, + tc_in_mem_converter: EcssTcInMemConverter, pus_action_rx: mpsc::Receiver, action_router: GenericRequestRouter, reply_receiver: mpsc::Receiver>, -) -> ActionServiceWrapper { +) -> ActionServiceWrapper { let action_request_handler = PusTargetedRequestService::new( PusServiceHelper::new( PUS_ACTION_SERVICE.id(), pus_action_rx, tm_sender, create_verification_reporter(PUS_ACTION_SERVICE.id(), PUS_ACTION_SERVICE.apid), - EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048), + tc_in_mem_converter, ), ActionRequestConverter::default(), // TODO: Implementation which does not use run-time allocation? Maybe something like @@ -235,36 +233,11 @@ pub fn create_action_service_static( } } -pub fn create_action_service_dynamic( - tm_funnel_tx: mpsc::Sender, - pus_action_rx: mpsc::Receiver, - action_router: GenericRequestRouter, - reply_receiver: mpsc::Receiver>, -) -> 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 struct ActionServiceWrapper { pub(crate) service: PusTargetedRequestService< MpscTcReceiver, - TmSender, - TcInMemConverter, + TmTcSender, + EcssTcInMemConverter, VerificationReporter, ActionRequestConverter, ActionReplyHandler, @@ -275,9 +248,7 @@ pub struct ActionServiceWrapper, } -impl TargetedPusService - for ActionServiceWrapper -{ +impl TargetedPusService for ActionServiceWrapper { const SERVICE_ID: u8 = PusServiceId::Action as u8; const SERVICE_STR: &'static str = "action"; @@ -303,9 +274,10 @@ 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::pus::{verification, EcssTcInVecConverter}; use satrs::request::MessageMetadata; + use satrs::tmtc::PacketAsVec; use satrs::ComponentId; use satrs::{ res_code::ResultU16, diff --git a/satrs-example/src/pus/event.rs b/satrs-example/src/pus/event.rs index caecdd9..e21c81e 100644 --- a/satrs-example/src/pus/event.rs +++ b/satrs-example/src/pus/event.rs @@ -1,34 +1,32 @@ use std::sync::mpsc; use crate::pus::create_verification_reporter; -use satrs::pool::SharedStaticMemoryPool; +use crate::tmtc::sender::TmTcSender; use satrs::pus::event_man::EventRequestWithToken; use satrs::pus::event_srv::PusEventServiceHandler; use satrs::pus::verification::VerificationReporter; use satrs::pus::{ - DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, - EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver, - MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper, + DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, MpscTcReceiver, + PartialPusHandlingError, PusServiceHelper, }; use satrs::spacepackets::ecss::PusServiceId; -use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use satrs_example::config::components::PUS_EVENT_MANAGEMENT; use super::{DirectPusService, HandlingStatus}; -pub fn create_event_service_static( - tm_sender: PacketSenderWithSharedPool, - tc_pool: SharedStaticMemoryPool, +pub fn create_event_service( + tm_sender: TmTcSender, + tm_in_pool_converter: EcssTcInMemConverter, pus_event_rx: mpsc::Receiver, event_request_tx: mpsc::Sender, -) -> EventServiceWrapper { +) -> EventServiceWrapper { let pus_5_handler = PusEventServiceHandler::new( PusServiceHelper::new( PUS_EVENT_MANAGEMENT.id(), pus_event_rx, tm_sender, create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid), - EcssTcInSharedStoreConverter::new(tc_pool.clone(), 2048), + tm_in_pool_converter, ), event_request_tx, ); @@ -37,34 +35,16 @@ pub fn create_event_service_static( } } -pub fn create_event_service_dynamic( - tm_funnel_tx: mpsc::Sender, - pus_event_rx: mpsc::Receiver, - event_request_tx: mpsc::Sender, -) -> EventServiceWrapper { - let pus_5_handler = PusEventServiceHandler::new( - PusServiceHelper::new( - PUS_EVENT_MANAGEMENT.id(), - pus_event_rx, - tm_funnel_tx, - create_verification_reporter(PUS_EVENT_MANAGEMENT.id(), PUS_EVENT_MANAGEMENT.apid), - EcssTcInVecConverter::default(), - ), - event_request_tx, - ); - EventServiceWrapper { - handler: pus_5_handler, - } +pub struct EventServiceWrapper { + pub handler: PusEventServiceHandler< + MpscTcReceiver, + TmTcSender, + EcssTcInMemConverter, + VerificationReporter, + >, } -pub struct EventServiceWrapper { - pub handler: - PusEventServiceHandler, -} - -impl DirectPusService - for EventServiceWrapper -{ +impl DirectPusService for EventServiceWrapper { const SERVICE_ID: u8 = PusServiceId::Event as u8; const SERVICE_STR: &'static str = "events"; diff --git a/satrs-example/src/pus/hk.rs b/satrs-example/src/pus/hk.rs index c0d21bf..83a5f26 100644 --- a/satrs-example/src/pus/hk.rs +++ b/satrs-example/src/pus/hk.rs @@ -1,21 +1,18 @@ use derive_new::new; use satrs::hk::{CollectionIntervalFactor, HkRequest, HkRequestVariant, UniqueId}; -use satrs::pool::SharedStaticMemoryPool; use satrs::pus::verification::{ FailParams, TcStateAccepted, TcStateStarted, VerificationReporter, VerificationReportingProvider, VerificationToken, }; use satrs::pus::{ ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap, EcssTcAndToken, - EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, - EcssTmtcError, GenericConversionError, MpscTcReceiver, MpscTmAsVecSender, + EcssTcInMemConverter, EcssTmSender, EcssTmtcError, GenericConversionError, MpscTcReceiver, PusPacketHandlingError, PusReplyHandler, PusServiceHelper, PusTcToRequestConverter, }; use satrs::request::{GenericMessage, UniqueApidTargetId}; use satrs::res_code::ResultU16; use satrs::spacepackets::ecss::tc::PusTcReader; use satrs::spacepackets::ecss::{hk, PusPacket, PusServiceId}; -use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use satrs_example::config::components::PUS_HK_SERVICE; use satrs_example::config::{hk_err, tmtc_err}; use std::sync::mpsc; @@ -23,6 +20,7 @@ use std::time::Duration; use crate::pus::{create_verification_reporter, generic_pus_request_timeout_handler}; use crate::requests::GenericRequestRouter; +use crate::tmtc::sender::TmTcSender; use super::{HandlingStatus, PusTargetedRequestService, TargetedPusService}; @@ -242,20 +240,20 @@ impl PusTcToRequestConverter for HkRequestConver } } -pub fn create_hk_service_static( - tm_sender: PacketSenderWithSharedPool, - tc_pool: SharedStaticMemoryPool, +pub fn create_hk_service( + tm_sender: TmTcSender, + tc_in_mem_converter: EcssTcInMemConverter, pus_hk_rx: mpsc::Receiver, request_router: GenericRequestRouter, reply_receiver: mpsc::Receiver>, -) -> HkServiceWrapper { +) -> HkServiceWrapper { let pus_3_handler = PusTargetedRequestService::new( PusServiceHelper::new( PUS_HK_SERVICE.id(), pus_hk_rx, tm_sender, create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid), - EcssTcInSharedStoreConverter::new(tc_pool, 2048), + tc_in_mem_converter, ), HkRequestConverter::default(), DefaultActiveRequestMap::default(), @@ -268,36 +266,11 @@ pub fn create_hk_service_static( } } -pub fn create_hk_service_dynamic( - tm_funnel_tx: mpsc::Sender, - pus_hk_rx: mpsc::Receiver, - request_router: GenericRequestRouter, - reply_receiver: mpsc::Receiver>, -) -> HkServiceWrapper { - let pus_3_handler = PusTargetedRequestService::new( - PusServiceHelper::new( - PUS_HK_SERVICE.id(), - pus_hk_rx, - tm_funnel_tx, - create_verification_reporter(PUS_HK_SERVICE.id(), PUS_HK_SERVICE.apid), - EcssTcInVecConverter::default(), - ), - HkRequestConverter::default(), - DefaultActiveRequestMap::default(), - HkReplyHandler::default(), - request_router, - reply_receiver, - ); - HkServiceWrapper { - service: pus_3_handler, - } -} - -pub struct HkServiceWrapper { +pub struct HkServiceWrapper { pub(crate) service: PusTargetedRequestService< MpscTcReceiver, - TmSender, - TcInMemConverter, + TmTcSender, + EcssTcInMemConverter, VerificationReporter, HkRequestConverter, HkReplyHandler, @@ -308,9 +281,7 @@ pub struct HkServiceWrapper, } -impl TargetedPusService - for HkServiceWrapper -{ +impl TargetedPusService for HkServiceWrapper { const SERVICE_ID: u8 = PusServiceId::Housekeeping as u8; const SERVICE_STR: &'static str = "housekeeping"; diff --git a/satrs-example/src/pus/mod.rs b/satrs-example/src/pus/mod.rs index 3ce5533..7324da5 100644 --- a/satrs-example/src/pus/mod.rs +++ b/satrs-example/src/pus/mod.rs @@ -1,4 +1,5 @@ use crate::requests::GenericRequestRouter; +use crate::tmtc::sender::TmTcSender; use log::warn; use satrs::pool::PoolAddr; use satrs::pus::verification::{ @@ -6,7 +7,7 @@ use satrs::pus::verification::{ VerificationReporterCfg, VerificationReportingProvider, VerificationToken, }; use satrs::pus::{ - ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConverter, + ActiveRequestMapProvider, ActiveRequestProvider, EcssTcAndToken, EcssTcInMemConversionProvider, EcssTcReceiver, EcssTmSender, EcssTmtcError, GenericConversionError, GenericRoutingError, HandlingStatus, PusPacketHandlingError, PusReplyHandler, PusRequestRouter, PusServiceHelper, PusTcToRequestConverter, TcInMemory, @@ -21,7 +22,7 @@ use satrs_example::config::components::PUS_ROUTING_SERVICE; use satrs_example::config::{tmtc_err, CustomPusServiceId}; use satrs_example::TimestampHelper; use std::fmt::Debug; -use std::sync::mpsc::{self, Sender}; +use std::sync::mpsc; pub mod action; pub mod event; @@ -40,26 +41,26 @@ pub fn create_verification_reporter(owner_id: ComponentId, apid: Apid) -> Verifi /// Simple router structure which forwards PUS telecommands to dedicated handlers. pub struct PusTcMpscRouter { - pub test_tc_sender: Sender, - pub event_tc_sender: Sender, - pub sched_tc_sender: Sender, - pub hk_tc_sender: Sender, + pub test_tc_sender: mpsc::SyncSender, + pub event_tc_sender: mpsc::SyncSender, + pub sched_tc_sender: mpsc::SyncSender, + pub hk_tc_sender: mpsc::SyncSender, #[allow(dead_code)] - pub action_tc_sender: Sender, - pub mode_tc_sender: Sender, + pub action_tc_sender: mpsc::SyncSender, + pub mode_tc_sender: mpsc::SyncSender, } -pub struct PusTcDistributor { +pub struct PusTcDistributor { #[allow(dead_code)] pub id: ComponentId, - pub tm_sender: TmSender, + pub tm_sender: TmTcSender, pub verif_reporter: VerificationReporter, pub pus_router: PusTcMpscRouter, stamp_helper: TimestampHelper, } -impl PusTcDistributor { - pub fn new(tm_sender: TmSender, pus_router: PusTcMpscRouter) -> Self { +impl PusTcDistributor { + pub fn new(tm_sender: TmTcSender, pus_router: PusTcMpscRouter) -> Self { Self { id: PUS_ROUTING_SERVICE.raw(), tm_sender, @@ -269,7 +270,7 @@ pub trait DirectPusService { pub struct PusTargetedRequestService< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, RequestConverter: PusTcToRequestConverter, ReplyHandler: PusReplyHandler, @@ -291,7 +292,7 @@ pub struct PusTargetedRequestService< impl< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, RequestConverter: PusTcToRequestConverter, ReplyHandler: PusReplyHandler, diff --git a/satrs-example/src/pus/mode.rs b/satrs-example/src/pus/mode.rs index 56ae9d4..1980855 100644 --- a/satrs-example/src/pus/mode.rs +++ b/satrs-example/src/pus/mode.rs @@ -1,15 +1,14 @@ use derive_new::new; -use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; +use satrs::mode_tree::{ModeNode, ModeParent}; use std::sync::mpsc; use std::time::Duration; use crate::requests::GenericRequestRouter; -use satrs::pool::SharedStaticMemoryPool; +use crate::tmtc::sender::TmTcSender; use satrs::pus::verification::VerificationReporter; use satrs::pus::{ - DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, EcssTcInSharedStoreConverter, - EcssTcInVecConverter, MpscTcReceiver, MpscTmAsVecSender, PusPacketHandlingError, - PusServiceHelper, + DefaultActiveRequestMap, EcssTcAndToken, EcssTcInMemConverter, MpscTcReceiver, + PusPacketHandlingError, PusServiceHelper, }; use satrs::request::GenericMessage; use satrs::{ @@ -110,6 +109,7 @@ impl PusReplyHandler for ModeReplyHandler { ), )?; } + ModeReply::ModeInfo(_mode_and_submode) => (), }; Ok(true) } @@ -190,7 +190,13 @@ impl PusTcToRequestConverter for ModeRequestCo } let mode_and_submode = ModeAndSubmode::from_be_bytes(&tc.user_data()[4..]) .expect("mode and submode extraction failed"); - Ok((active_request, ModeRequest::SetMode(mode_and_submode))) + Ok(( + active_request, + ModeRequest::SetMode { + mode_and_submode, + forced: false, + }, + )) } Subservice::TcReadMode => Ok((active_request, ModeRequest::ReadMode)), Subservice::TcAnnounceMode => Ok((active_request, ModeRequest::AnnounceMode)), @@ -202,20 +208,20 @@ impl PusTcToRequestConverter for ModeRequestCo } } -pub fn create_mode_service_static( - tm_sender: PacketSenderWithSharedPool, - tc_pool: SharedStaticMemoryPool, +pub fn create_mode_service( + tm_sender: TmTcSender, + tc_in_mem_converter: EcssTcInMemConverter, pus_action_rx: mpsc::Receiver, mode_router: GenericRequestRouter, reply_receiver: mpsc::Receiver>, -) -> ModeServiceWrapper { +) -> ModeServiceWrapper { let mode_request_handler = PusTargetedRequestService::new( PusServiceHelper::new( PUS_MODE_SERVICE.id(), pus_action_rx, tm_sender, create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid), - EcssTcInSharedStoreConverter::new(tc_pool, 2048), + tc_in_mem_converter, ), ModeRequestConverter::default(), DefaultActiveRequestMap::default(), @@ -228,36 +234,11 @@ pub fn create_mode_service_static( } } -pub fn create_mode_service_dynamic( - tm_funnel_tx: mpsc::Sender, - pus_action_rx: mpsc::Receiver, - mode_router: GenericRequestRouter, - reply_receiver: mpsc::Receiver>, -) -> ModeServiceWrapper { - let mode_request_handler = PusTargetedRequestService::new( - PusServiceHelper::new( - PUS_MODE_SERVICE.id(), - pus_action_rx, - tm_funnel_tx, - create_verification_reporter(PUS_MODE_SERVICE.id(), PUS_MODE_SERVICE.apid), - EcssTcInVecConverter::default(), - ), - ModeRequestConverter::default(), - DefaultActiveRequestMap::default(), - ModeReplyHandler::new(PUS_MODE_SERVICE.id()), - mode_router, - reply_receiver, - ); - ModeServiceWrapper { - service: mode_request_handler, - } -} - -pub struct ModeServiceWrapper { +pub struct ModeServiceWrapper { pub(crate) service: PusTargetedRequestService< MpscTcReceiver, - TmSender, - TcInMemConverter, + TmTcSender, + EcssTcInMemConverter, VerificationReporter, ModeRequestConverter, ModeReplyHandler, @@ -268,9 +249,24 @@ pub struct ModeServiceWrapper, } -impl TargetedPusService - for ModeServiceWrapper -{ +impl ModeNode for ModeServiceWrapper { + fn id(&self) -> ComponentId { + self.service.service_helper.id() + } +} + +impl ModeParent for ModeServiceWrapper { + type Sender = mpsc::SyncSender>; + + fn add_mode_child(&mut self, id: ComponentId, request_sender: Self::Sender) { + self.service + .request_router + .mode_router_map + .insert(id, request_sender); + } +} + +impl TargetedPusService for ModeServiceWrapper { const SERVICE_ID: u8 = CustomPusServiceId::Mode as u8; const SERVICE_STR: &'static str = "mode"; @@ -346,7 +342,13 @@ mod tests { let (_active_req, req) = testbench .convert(token, &[], TEST_APID, TEST_UNIQUE_ID_0) .expect("conversion has failed"); - assert_eq!(req, ModeRequest::SetMode(mode_and_submode)); + assert_eq!( + req, + ModeRequest::SetMode { + mode_and_submode, + forced: false + } + ); } #[test] diff --git a/satrs-example/src/pus/scheduler.rs b/satrs-example/src/pus/scheduler.rs index eaa03c4..1526326 100644 --- a/satrs-example/src/pus/scheduler.rs +++ b/satrs-example/src/pus/scheduler.rs @@ -2,15 +2,15 @@ use std::sync::mpsc; use std::time::Duration; use crate::pus::create_verification_reporter; +use crate::tmtc::sender::TmTcSender; use log::info; use satrs::pool::{PoolProvider, StaticMemoryPool}; use satrs::pus::scheduler::{PusScheduler, TcInfo}; use satrs::pus::scheduler_srv::PusSchedServiceHandler; use satrs::pus::verification::VerificationReporter; use satrs::pus::{ - DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, - EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTmSender, MpscTcReceiver, - MpscTmAsVecSender, PartialPusHandlingError, PusServiceHelper, + DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, MpscTcReceiver, + PartialPusHandlingError, PusServiceHelper, }; use satrs::spacepackets::ecss::PusServiceId; use satrs::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool}; @@ -19,11 +19,11 @@ use satrs_example::config::components::PUS_SCHED_SERVICE; use super::{DirectPusService, HandlingStatus}; -pub trait TcReleaser { +pub trait TcReleaseProvider { fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool; } -impl TcReleaser for PacketSenderWithSharedPool { +impl TcReleaseProvider for PacketSenderWithSharedPool { fn release( &mut self, sender_id: ComponentId, @@ -48,7 +48,7 @@ impl TcReleaser for PacketSenderWithSharedPool { } } -impl TcReleaser for mpsc::Sender { +impl TcReleaseProvider for mpsc::SyncSender { fn release( &mut self, sender_id: ComponentId, @@ -65,23 +65,35 @@ impl TcReleaser for mpsc::Sender { } } -pub struct SchedulingServiceWrapper -{ +#[allow(dead_code)] +pub enum TcReleaser { + Static(PacketSenderWithSharedPool), + Heap(mpsc::SyncSender), +} + +impl TcReleaseProvider for TcReleaser { + fn release(&mut self, sender_id: ComponentId, enabled: bool, info: &TcInfo, tc: &[u8]) -> bool { + match self { + TcReleaser::Static(sender) => sender.release(sender_id, enabled, info, tc), + TcReleaser::Heap(sender) => sender.release(sender_id, enabled, info, tc), + } + } +} + +pub struct SchedulingServiceWrapper { pub pus_11_handler: PusSchedServiceHandler< MpscTcReceiver, - TmSender, - TcInMemConverter, + TmTcSender, + EcssTcInMemConverter, VerificationReporter, PusScheduler, >, pub sched_tc_pool: StaticMemoryPool, pub releaser_buf: [u8; 4096], - pub tc_releaser: Box, + pub tc_releaser: TcReleaser, } -impl DirectPusService - for SchedulingServiceWrapper -{ +impl DirectPusService for SchedulingServiceWrapper { const SERVICE_ID: u8 = PusServiceId::Verification as u8; const SERVICE_STR: &'static str = "verification"; @@ -134,9 +146,7 @@ impl DirectPusSe } } -impl - SchedulingServiceWrapper -{ +impl SchedulingServiceWrapper { pub fn release_tcs(&mut self) { let id = self.pus_11_handler.service_helper.id(); let releaser = |enabled: bool, info: &TcInfo, tc: &[u8]| -> bool { @@ -162,12 +172,13 @@ impl } } -pub fn create_scheduler_service_static( - tm_sender: PacketSenderWithSharedPool, - tc_releaser: PacketSenderWithSharedPool, +pub fn create_scheduler_service( + tm_sender: TmTcSender, + tc_in_mem_converter: EcssTcInMemConverter, + tc_releaser: TcReleaser, pus_sched_rx: mpsc::Receiver, sched_tc_pool: StaticMemoryPool, -) -> SchedulingServiceWrapper { +) -> SchedulingServiceWrapper { let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5)) .expect("Creating PUS Scheduler failed"); let pus_11_handler = PusSchedServiceHandler::new( @@ -176,7 +187,7 @@ pub fn create_scheduler_service_static( pus_sched_rx, tm_sender, create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid), - EcssTcInSharedStoreConverter::new(tc_releaser.shared_packet_store().0.clone(), 2048), + tc_in_mem_converter, ), scheduler, ); @@ -184,34 +195,6 @@ pub fn create_scheduler_service_static( pus_11_handler, sched_tc_pool, releaser_buf: [0; 4096], - tc_releaser: Box::new(tc_releaser), - } -} - -pub fn create_scheduler_service_dynamic( - tm_funnel_tx: mpsc::Sender, - tc_source_sender: mpsc::Sender, - pus_sched_rx: mpsc::Receiver, - sched_tc_pool: StaticMemoryPool, -) -> SchedulingServiceWrapper { - //let sched_srv_receiver = - //MpscTcReceiver::new(PUS_SCHED_SERVICE.raw(), "PUS_11_TC_RECV", pus_sched_rx); - let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5)) - .expect("Creating PUS Scheduler failed"); - let pus_11_handler = PusSchedServiceHandler::new( - PusServiceHelper::new( - PUS_SCHED_SERVICE.id(), - pus_sched_rx, - tm_funnel_tx, - create_verification_reporter(PUS_SCHED_SERVICE.id(), PUS_SCHED_SERVICE.apid), - EcssTcInVecConverter::default(), - ), - scheduler, - ); - SchedulingServiceWrapper { - pus_11_handler, - sched_tc_pool, - releaser_buf: [0; 4096], - tc_releaser: Box::new(tc_source_sender), + tc_releaser, } } diff --git a/satrs-example/src/pus/stack.rs b/satrs-example/src/pus/stack.rs index 3594bd6..c6d0b1b 100644 --- a/satrs-example/src/pus/stack.rs +++ b/satrs-example/src/pus/stack.rs @@ -1,9 +1,6 @@ use crate::pus::mode::ModeServiceWrapper; use derive_new::new; -use satrs::{ - pus::{EcssTcInMemConverter, EcssTmSender}, - spacepackets::time::{cds, TimeWriter}, -}; +use satrs::spacepackets::time::{cds, TimeWriter}; use super::{ action::ActionServiceWrapper, event::EventServiceWrapper, hk::HkServiceWrapper, @@ -11,21 +8,17 @@ use super::{ HandlingStatus, TargetedPusService, }; -// TODO: For better extensibility, we could create 2 vectors: One for direct PUS services and one -// for targeted services.. #[derive(new)] -pub struct PusStack { - test_srv: TestCustomServiceWrapper, - hk_srv_wrapper: HkServiceWrapper, - event_srv: EventServiceWrapper, - action_srv_wrapper: ActionServiceWrapper, - schedule_srv: SchedulingServiceWrapper, - mode_srv: ModeServiceWrapper, +pub struct PusStack { + pub test_srv: TestCustomServiceWrapper, + pub hk_srv_wrapper: HkServiceWrapper, + pub event_srv: EventServiceWrapper, + pub action_srv_wrapper: ActionServiceWrapper, + pub schedule_srv: SchedulingServiceWrapper, + pub mode_srv: ModeServiceWrapper, } -impl - PusStack -{ +impl PusStack { pub fn periodic_operation(&mut self) { // Release all telecommands which reached their release time before calling the service // handlers. diff --git a/satrs-example/src/pus/test.rs b/satrs-example/src/pus/test.rs index 473dc3e..cf72938 100644 --- a/satrs-example/src/pus/test.rs +++ b/satrs-example/src/pus/test.rs @@ -1,35 +1,34 @@ use crate::pus::create_verification_reporter; +use crate::tmtc::sender::TmTcSender; use log::info; use satrs::event_man::{EventMessage, EventMessageU32}; -use satrs::pool::SharedStaticMemoryPool; use satrs::pus::test::PusService17TestHandler; use satrs::pus::verification::{FailParams, VerificationReporter, VerificationReportingProvider}; +use satrs::pus::PartialPusHandlingError; use satrs::pus::{ - DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConverter, EcssTcInVecConverter, - EcssTmSender, MpscTcReceiver, MpscTmAsVecSender, PusServiceHelper, + DirectPusPacketHandlerResult, EcssTcAndToken, EcssTcInMemConversionProvider, + EcssTcInMemConverter, MpscTcReceiver, PusServiceHelper, }; -use satrs::pus::{EcssTcInSharedStoreConverter, PartialPusHandlingError}; use satrs::spacepackets::ecss::tc::PusTcReader; use satrs::spacepackets::ecss::{PusPacket, PusServiceId}; -use satrs::tmtc::{PacketAsVec, PacketSenderWithSharedPool}; use satrs_example::config::components::PUS_TEST_SERVICE; use satrs_example::config::{tmtc_err, TEST_EVENT}; use std::sync::mpsc; use super::{DirectPusService, HandlingStatus}; -pub fn create_test_service_static( - tm_sender: PacketSenderWithSharedPool, - tc_pool: SharedStaticMemoryPool, +pub fn create_test_service( + tm_sender: TmTcSender, + tc_in_mem_converter: EcssTcInMemConverter, event_sender: mpsc::SyncSender, pus_test_rx: mpsc::Receiver, -) -> TestCustomServiceWrapper { +) -> TestCustomServiceWrapper { let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new( PUS_TEST_SERVICE.id(), pus_test_rx, tm_sender, create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid), - EcssTcInSharedStoreConverter::new(tc_pool, 2048), + tc_in_mem_converter, )); TestCustomServiceWrapper { handler: pus17_handler, @@ -37,34 +36,17 @@ pub fn create_test_service_static( } } -pub fn create_test_service_dynamic( - tm_funnel_tx: mpsc::Sender, - event_sender: mpsc::SyncSender, - pus_test_rx: mpsc::Receiver, -) -> TestCustomServiceWrapper { - let pus17_handler = PusService17TestHandler::new(PusServiceHelper::new( - PUS_TEST_SERVICE.id(), - pus_test_rx, - tm_funnel_tx, - create_verification_reporter(PUS_TEST_SERVICE.id(), PUS_TEST_SERVICE.apid), - EcssTcInVecConverter::default(), - )); - TestCustomServiceWrapper { - handler: pus17_handler, - event_tx: event_sender, - } -} - -pub struct TestCustomServiceWrapper -{ - pub handler: - PusService17TestHandler, +pub struct TestCustomServiceWrapper { + pub handler: PusService17TestHandler< + MpscTcReceiver, + TmTcSender, + EcssTcInMemConverter, + VerificationReporter, + >, pub event_tx: mpsc::SyncSender, } -impl DirectPusService - for TestCustomServiceWrapper -{ +impl DirectPusService for TestCustomServiceWrapper { const SERVICE_ID: u8 = PusServiceId::Test as u8; const SERVICE_STR: &'static str = "test"; diff --git a/satrs-example/src/spi.rs b/satrs-example/src/spi.rs new file mode 100644 index 0000000..165e6da --- /dev/null +++ b/satrs-example/src/spi.rs @@ -0,0 +1,6 @@ +use core::fmt::Debug; + +pub trait SpiInterface { + type Error: Debug; + fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>; +} diff --git a/satrs-example/src/tmtc/mod.rs b/satrs-example/src/tmtc/mod.rs index bfd24c5..6b0f377 100644 --- a/satrs-example/src/tmtc/mod.rs +++ b/satrs-example/src/tmtc/mod.rs @@ -1,2 +1,3 @@ +pub mod sender; pub mod tc_source; pub mod tm_sink; diff --git a/satrs-example/src/tmtc/sender.rs b/satrs-example/src/tmtc/sender.rs new file mode 100644 index 0000000..cb5d4ab --- /dev/null +++ b/satrs-example/src/tmtc/sender.rs @@ -0,0 +1,75 @@ +use std::{cell::RefCell, collections::VecDeque, sync::mpsc}; + +use satrs::{ + pus::EcssTmSender, + queue::GenericSendError, + spacepackets::ecss::WritablePusPacket, + tmtc::{PacketAsVec, PacketSenderRaw, PacketSenderWithSharedPool, StoreAndSendError}, + ComponentId, +}; + +#[derive(Default, Debug, Clone)] +pub struct MockSender(pub RefCell>); + +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub enum TmTcSender { + Static(PacketSenderWithSharedPool), + Heap(mpsc::SyncSender), + Mock(MockSender), +} + +impl TmTcSender { + #[allow(dead_code)] + pub fn get_mock_sender(&mut self) -> Option<&mut MockSender> { + match self { + TmTcSender::Mock(sender) => Some(sender), + _ => None, + } + } +} + +impl EcssTmSender for TmTcSender { + fn send_tm( + &self, + sender_id: satrs::ComponentId, + tm: satrs::pus::PusTmVariant, + ) -> Result<(), satrs::pus::EcssTmtcError> { + match self { + TmTcSender::Static(sync_sender) => sync_sender.send_tm(sender_id, tm), + TmTcSender::Heap(sync_sender) => match tm { + satrs::pus::PusTmVariant::InStore(_) => panic!("can not send TM in store"), + satrs::pus::PusTmVariant::Direct(pus_tm_creator) => sync_sender + .send(PacketAsVec::new(sender_id, pus_tm_creator.to_vec()?)) + .map_err(|_| GenericSendError::RxDisconnected.into()), + }, + TmTcSender::Mock(_) => Ok(()), + } + } +} + +impl PacketSenderRaw for TmTcSender { + type Error = StoreAndSendError; + + fn send_packet(&self, sender_id: ComponentId, packet: &[u8]) -> Result<(), Self::Error> { + match self { + TmTcSender::Static(packet_sender_with_shared_pool) => { + packet_sender_with_shared_pool.send_packet(sender_id, packet) + } + TmTcSender::Heap(sync_sender) => sync_sender + .send_packet(sender_id, packet) + .map_err(StoreAndSendError::Send), + TmTcSender::Mock(sender) => sender.send_packet(sender_id, packet), + } + } +} + +impl PacketSenderRaw for MockSender { + type Error = StoreAndSendError; + + fn send_packet(&self, sender_id: ComponentId, tc_raw: &[u8]) -> Result<(), Self::Error> { + let mut mut_queue = self.0.borrow_mut(); + mut_queue.push_back(PacketAsVec::new(sender_id, tc_raw.to_vec())); + Ok(()) + } +} diff --git a/satrs-example/src/tmtc/tc_source.rs b/satrs-example/src/tmtc/tc_source.rs index 94b642c..e7c467e 100644 --- a/satrs-example/src/tmtc/tc_source.rs +++ b/satrs-example/src/tmtc/tc_source.rs @@ -1,12 +1,10 @@ use satrs::{ pool::PoolProvider, pus::HandlingStatus, - tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool}, + tmtc::{PacketAsVec, PacketInPool, SharedPacketPool}, }; use std::sync::mpsc::{self, TryRecvError}; -use satrs::pus::MpscTmAsVecSender; - use crate::pus::PusTcDistributor; // TC source components where static pools are the backing memory of the received telecommands. @@ -14,14 +12,15 @@ pub struct TcSourceTaskStatic { shared_tc_pool: SharedPacketPool, tc_receiver: mpsc::Receiver, tc_buf: [u8; 4096], - pus_distributor: PusTcDistributor, + pus_distributor: PusTcDistributor, } +#[allow(dead_code)] impl TcSourceTaskStatic { pub fn new( shared_tc_pool: SharedPacketPool, tc_receiver: mpsc::Receiver, - pus_receiver: PusTcDistributor, + pus_receiver: PusTcDistributor, ) -> Self { Self { shared_tc_pool, @@ -67,14 +66,12 @@ impl TcSourceTaskStatic { // TC source components where the heap is the backing memory of the received telecommands. pub struct TcSourceTaskDynamic { pub tc_receiver: mpsc::Receiver, - pus_distributor: PusTcDistributor, + pus_distributor: PusTcDistributor, } +#[allow(dead_code)] impl TcSourceTaskDynamic { - pub fn new( - tc_receiver: mpsc::Receiver, - pus_receiver: PusTcDistributor, - ) -> Self { + pub fn new(tc_receiver: mpsc::Receiver, pus_receiver: PusTcDistributor) -> Self { Self { tc_receiver, pus_distributor: pus_receiver, @@ -105,3 +102,18 @@ impl TcSourceTaskDynamic { } } } + +#[allow(dead_code)] +pub enum TcSourceTask { + Static(TcSourceTaskStatic), + Heap(TcSourceTaskDynamic), +} + +impl TcSourceTask { + pub fn periodic_operation(&mut self) { + match self { + TcSourceTask::Static(task) => task.periodic_operation(), + TcSourceTask::Heap(task) => task.periodic_operation(), + } + } +} diff --git a/satrs-example/src/tmtc/tm_sink.rs b/satrs-example/src/tmtc/tm_sink.rs index 054ff9c..5cfe118 100644 --- a/satrs-example/src/tmtc/tm_sink.rs +++ b/satrs-example/src/tmtc/tm_sink.rs @@ -89,6 +89,7 @@ pub struct TmSinkStatic { tm_server_tx: mpsc::SyncSender, } +#[allow(dead_code)] impl TmSinkStatic { pub fn new( shared_tm_store: SharedPacketPool, @@ -132,14 +133,15 @@ impl TmSinkStatic { pub struct TmSinkDynamic { common: TmFunnelCommon, tm_funnel_rx: mpsc::Receiver, - tm_server_tx: mpsc::Sender, + tm_server_tx: mpsc::SyncSender, } +#[allow(dead_code)] impl TmSinkDynamic { pub fn new( sync_tm_tcp_source: SyncTcpTmSource, tm_funnel_rx: mpsc::Receiver, - tm_server_tx: mpsc::Sender, + tm_server_tx: mpsc::SyncSender, ) -> Self { Self { common: TmFunnelCommon::new(sync_tm_tcp_source), @@ -162,3 +164,18 @@ impl TmSinkDynamic { } } } + +#[allow(dead_code)] +pub enum TmSink { + Static(TmSinkStatic), + Heap(TmSinkDynamic), +} + +impl TmSink { + pub fn operation(&mut self) { + match self { + TmSink::Static(static_sink) => static_sink.operation(), + TmSink::Heap(dynamic_sink) => dynamic_sink.operation(), + } + } +} diff --git a/satrs/CHANGELOG.md b/satrs/CHANGELOG.md index bb92cb7..b50049f 100644 --- a/satrs/CHANGELOG.md +++ b/satrs/CHANGELOG.md @@ -20,6 +20,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `StaticHeaplessMemoryPool` which can be grown with user-provided static buffers. - Scheduling table for systems with a standard runtime +- Mode Tree Feature which allows building a network of mode components which can send mode + messages to each other. +- Added first helper features like the `SubsystemExecutionHelper` and the + `SubsystemCommandingHelper` which allows to build subsystem components. Subsystem components + are able to execute mode sequences and perform target keeping based on a declarative table + format. +- Added `DevManagerCommandingHelper` which performs some of the boilerplate logik required + by Assembly and Device Management components. This includes forwarding mode requests and + handling mode replies. +- First basic health module with `HealthState`s and the `HealthTableProvider` trait. These + components are important for any FDIR components which get added in the future. # [v0.2.1] 2024-05-19 diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index 13ee3b1..09cea4b 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -2,7 +2,7 @@ name = "satrs" version = "0.2.1" edition = "2021" -rust-version = "1.71.1" +rust-version = "1.82.0" authors = ["Robin Mueller "] description = "A framework to build software for remote systems" homepage = "https://absatsw.irs.uni-stuttgart.de/projects/sat-rs/" diff --git a/satrs/src/dev_mgmt.rs b/satrs/src/dev_mgmt.rs new file mode 100644 index 0000000..b9eca0a --- /dev/null +++ b/satrs/src/dev_mgmt.rs @@ -0,0 +1,448 @@ +use crate::{ + mode::{ModeAndSubmode, ModeReply, ModeRequest, ModeRequestSender}, + mode_tree::{ModeStoreProvider, ModeStoreVec}, + queue::{GenericSendError, GenericTargetedMessagingError}, + request::{GenericMessage, RequestId}, + ComponentId, +}; +use core::fmt::Debug; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ActiveModeCommandContext { + pub target_mode: ModeAndSubmode, + pub active_request_id: RequestId, +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub enum DevManagerHelperResult { + #[default] + Idle, + Busy, + ModeCommandingDone(ActiveModeCommandContext), +} + +#[derive(Debug)] +pub enum DevManagerHelperError { + ChildNotInStore, +} + +pub trait DevManagerUserHook: Debug { + fn send_mode_cmd_to_child( + &self, + request_id: RequestId, + target_id: ComponentId, + mode: ModeAndSubmode, + forced: bool, + children_mode_store: &mut ModeStoreVec, + mode_req_sender: &impl ModeRequestSender, + ) -> Result<(), GenericSendError>; + + fn send_mode_cmds_to_children( + &self, + request_id: RequestId, + commanded_parent_mode: ModeAndSubmode, + forced: bool, + children_mode_store: &mut ModeStoreVec, + mode_req_sender: &impl ModeRequestSender, + ) -> Result<(), GenericSendError>; +} + +#[derive(Debug, Default)] +pub struct TransparentDevManagerHook {} + +impl DevManagerUserHook for TransparentDevManagerHook { + fn send_mode_cmds_to_children( + &self, + request_id: RequestId, + commanded_parent_mode: ModeAndSubmode, + forced: bool, + children_mode_store: &mut ModeStoreVec, + mode_req_sender: &impl ModeRequestSender, + ) -> Result<(), GenericSendError> { + for child in children_mode_store { + mode_req_sender.send_mode_request( + request_id, + child.id(), + ModeRequest::SetMode { + mode_and_submode: commanded_parent_mode, + forced, + }, + )?; + child.awaiting_reply = true; + } + Ok(()) + } + + fn send_mode_cmd_to_child( + &self, + request_id: RequestId, + target_id: ComponentId, + mode: ModeAndSubmode, + forced: bool, + children_mode_store: &mut ModeStoreVec, + mode_req_sender: &impl ModeRequestSender, + ) -> Result<(), GenericSendError> { + let mut_val = children_mode_store + .get_mut(target_id) + .ok_or(GenericSendError::TargetDoesNotExist(target_id))?; + mut_val.awaiting_reply = true; + mode_req_sender.send_mode_request( + request_id, + target_id, + ModeRequest::SetMode { + mode_and_submode: mode, + forced, + }, + )?; + Ok(()) + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum DevManagerCommandingState { + #[default] + Idle, + AwaitingReplies(ActiveModeCommandContext), +} + +impl DevManagerCommandingState { + fn new_active_cmd(mode_and_submode: ModeAndSubmode, active_request_id: RequestId) -> Self { + DevManagerCommandingState::AwaitingReplies(ActiveModeCommandContext { + target_mode: mode_and_submode, + active_request_id, + }) + } +} + +/// A generic helper for manager components which manage child components in a mode tree. +/// +/// Mode commands are usually forwarded to all children components transparently. +/// For example, this could be used in an Assembly component which manages multiple redundant +/// child components. It can also be used inside a manager component which only manages one device. +#[derive(Debug, Default)] +pub struct DevManagerCommandingHelper { + /// The IDs, modes and reply awaition status of all children are tracked in this data + /// structure. + pub children_mode_store: ModeStoreVec, + pub user_hook: UserHook, + pub state: DevManagerCommandingState, +} + +impl DevManagerCommandingHelper { + pub fn new(user_hook: UserHook) -> Self { + Self { + children_mode_store: Default::default(), + user_hook, + state: Default::default(), + } + } + + pub fn send_mode_cmd_to_one_child( + &mut self, + request_id: RequestId, + target_id: ComponentId, + mode_and_submode: ModeAndSubmode, + forced: bool, + mode_req_sender: &impl ModeRequestSender, + ) -> Result<(), GenericSendError> { + self.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id); + self.user_hook.send_mode_cmd_to_child( + request_id, + target_id, + mode_and_submode, + forced, + &mut self.children_mode_store, + mode_req_sender, + )?; + Ok(()) + } + + pub fn send_mode_cmd_to_all_children( + &mut self, + request_id: RequestId, + mode_and_submode: ModeAndSubmode, + forced: bool, + mode_req_sender: &impl ModeRequestSender, + ) -> Result<(), GenericSendError> { + self.state = DevManagerCommandingState::new_active_cmd(mode_and_submode, request_id); + self.user_hook.send_mode_cmds_to_children( + request_id, + mode_and_submode, + forced, + &mut self.children_mode_store, + mode_req_sender, + )?; + Ok(()) + } + + pub fn target_mode(&self) -> Option { + match self.state { + DevManagerCommandingState::Idle => None, + DevManagerCommandingState::AwaitingReplies(context) => Some(context.target_mode), + } + } + + pub fn state(&self) -> DevManagerCommandingState { + self.state + } + + pub fn send_announce_mode_cmd_to_children( + &self, + request_id: RequestId, + mode_req_sender: &impl ModeRequestSender, + recursive: bool, + ) -> Result<(), GenericTargetedMessagingError> { + let mut request = ModeRequest::AnnounceMode; + if recursive { + request = ModeRequest::AnnounceModeRecursive; + } + for child in self.children_mode_store.0.iter() { + mode_req_sender.send_mode_request(request_id, child.id(), request)?; + } + Ok(()) + } + + pub fn add_mode_child(&mut self, target_id: ComponentId, mode: ModeAndSubmode) { + self.children_mode_store.add_component(target_id, mode); + } + + /// Helper method which counts the number of children which have a certain mode. + pub fn count_number_of_children_with_mode(&self, mode_and_submode: ModeAndSubmode) -> usize { + let mut children_in_target_mode = 0; + for child in &self.children_mode_store { + if child.mode_and_submode() == mode_and_submode { + children_in_target_mode += 1; + } + } + children_in_target_mode + } + + pub fn handle_mode_reply( + &mut self, + mode_reply: &GenericMessage, + ) -> Result { + let context = match self.state { + DevManagerCommandingState::Idle => return Ok(DevManagerHelperResult::Idle), + DevManagerCommandingState::AwaitingReplies(active_mode_command_context) => { + Some(active_mode_command_context) + } + }; + if !self + .children_mode_store + .has_component(mode_reply.sender_id()) + { + return Err(DevManagerHelperError::ChildNotInStore); + } + let mut generic_mode_reply_handler = |mode_and_submode: Option| { + // Tying the reply awaition to the request ID ensures that something like replies + // belonging to older requests do not interfere with the completion handling of + // the mode commanding. This is important for forced mode commands. + let mut handle_awaition = false; + if let DevManagerCommandingState::AwaitingReplies { .. } = self.state { + handle_awaition = true; + } + let still_awating_replies = self.children_mode_store.mode_reply_handler( + mode_reply.sender_id(), + mode_and_submode, + handle_awaition, + ); + // It is okay to unwrap: If awaition should be handled, the returned value should + // always be some valid value. + if handle_awaition && !still_awating_replies.unwrap() { + self.state = DevManagerCommandingState::Idle; + return Ok(DevManagerHelperResult::ModeCommandingDone(context.unwrap())); + } + Ok(DevManagerHelperResult::Busy) + }; + match mode_reply.message { + ModeReply::ModeInfo(mode_and_submode) | ModeReply::ModeReply(mode_and_submode) => { + generic_mode_reply_handler(Some(mode_and_submode)) + } + ModeReply::CantReachMode(_result_u16) => generic_mode_reply_handler(None), + ModeReply::WrongMode { + expected: _, + reached, + } => generic_mode_reply_handler(Some(reached)), + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + mode::{tests::ModeReqSenderMock, UNKNOWN_MODE}, + request::MessageMetadata, + }; + + use super::*; + + pub enum ExampleId { + Id1 = 1, + Id2 = 2, + } + + pub enum ExampleMode { + Mode1 = 1, + Mode2 = 2, + } + + #[test] + fn test_basic() { + let assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); + assert_eq!(assy_helper.state(), DevManagerCommandingState::Idle); + } + + #[test] + fn test_mode_announce() { + let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); + let mode_req_sender = ModeReqSenderMock::default(); + assy_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE); + assy_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE); + assy_helper + .send_announce_mode_cmd_to_children(1, &mode_req_sender, false) + .unwrap(); + assert_eq!(mode_req_sender.requests.borrow().len(), 2); + let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req.target_id, ExampleId::Id1 as u64); + assert_eq!(req.request_id, 1); + assert_eq!(req.request, ModeRequest::AnnounceMode); + req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req.target_id, ExampleId::Id2 as u64); + assert_eq!(req.request_id, 1); + assert_eq!(req.request, ModeRequest::AnnounceMode); + } + + #[test] + fn test_mode_announce_recursive() { + let mut assy_helper = DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); + let mode_req_sender = ModeReqSenderMock::default(); + assy_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE); + assy_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE); + assy_helper + .send_announce_mode_cmd_to_children(1, &mode_req_sender, true) + .unwrap(); + assert_eq!(mode_req_sender.requests.borrow().len(), 2); + let mut req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req.target_id, ExampleId::Id1 as u64); + assert_eq!(req.request_id, 1); + assert_eq!(req.request, ModeRequest::AnnounceModeRecursive); + req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req.target_id, ExampleId::Id2 as u64); + assert_eq!(req.request_id, 1); + assert_eq!(req.request, ModeRequest::AnnounceModeRecursive); + } + + #[test] + fn test_mode_commanding_one_child() { + let mut dev_mgmt_helper = + DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); + let mode_req_sender = ModeReqSenderMock::default(); + dev_mgmt_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE); + let expected_mode = ModeAndSubmode::new(ExampleMode::Mode1 as u32, 0); + dev_mgmt_helper + .send_mode_cmd_to_one_child( + 1, + ExampleId::Id1 as u64, + expected_mode, + false, + &mode_req_sender, + ) + .unwrap(); + assert_eq!(mode_req_sender.requests.borrow().len(), 1); + let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req.target_id, ExampleId::Id1 as u64); + assert_eq!(req.request_id, 1); + assert_eq!( + req.request, + ModeRequest::SetMode { + mode_and_submode: expected_mode, + forced: false + } + ); + matches!( + dev_mgmt_helper.state(), + DevManagerCommandingState::AwaitingReplies { .. } + ); + if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() { + assert_eq!(ctx.target_mode, expected_mode); + assert_eq!(ctx.active_request_id, 1); + } + let reply = GenericMessage::new( + MessageMetadata::new(1, ExampleId::Id1 as u64), + ModeReply::ModeReply(expected_mode), + ); + if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext { + target_mode, + active_request_id, + }) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap() + { + assert_eq!(target_mode, expected_mode); + assert_eq!(active_request_id, 1); + } + matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle); + } + + #[test] + fn test_mode_commanding_multi_child() { + let mut dev_mgmt_helper = + DevManagerCommandingHelper::new(TransparentDevManagerHook::default()); + let mode_req_sender = ModeReqSenderMock::default(); + dev_mgmt_helper.add_mode_child(ExampleId::Id1 as u64, UNKNOWN_MODE); + dev_mgmt_helper.add_mode_child(ExampleId::Id2 as u64, UNKNOWN_MODE); + let expected_mode = ModeAndSubmode::new(ExampleMode::Mode2 as u32, 0); + dev_mgmt_helper + .send_mode_cmd_to_all_children(1, expected_mode, false, &mode_req_sender) + .unwrap(); + assert_eq!(mode_req_sender.requests.borrow().len(), 2); + let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req.target_id, ExampleId::Id1 as u64); + assert_eq!(req.request_id, 1); + assert_eq!( + req.request, + ModeRequest::SetMode { + mode_and_submode: expected_mode, + forced: false + } + ); + let req = mode_req_sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req.target_id, ExampleId::Id2 as u64); + assert_eq!(req.request_id, 1); + assert_eq!( + req.request, + ModeRequest::SetMode { + mode_and_submode: expected_mode, + forced: false + } + ); + matches!( + dev_mgmt_helper.state(), + DevManagerCommandingState::AwaitingReplies { .. } + ); + if let DevManagerCommandingState::AwaitingReplies(ctx) = dev_mgmt_helper.state() { + assert_eq!(ctx.target_mode, expected_mode); + assert_eq!(ctx.active_request_id, 1); + } + + let reply = GenericMessage::new( + MessageMetadata::new(1, ExampleId::Id1 as u64), + ModeReply::ModeReply(expected_mode), + ); + assert_eq!( + dev_mgmt_helper.handle_mode_reply(&reply).unwrap(), + DevManagerHelperResult::Busy + ); + let reply = GenericMessage::new( + MessageMetadata::new(1, ExampleId::Id2 as u64), + ModeReply::ModeReply(expected_mode), + ); + if let DevManagerHelperResult::ModeCommandingDone(ActiveModeCommandContext { + target_mode, + active_request_id, + }) = dev_mgmt_helper.handle_mode_reply(&reply).unwrap() + { + assert_eq!(target_mode, expected_mode); + assert_eq!(active_request_id, 1); + } + matches!(dev_mgmt_helper.state(), DevManagerCommandingState::Idle); + } +} diff --git a/satrs/src/health.rs b/satrs/src/health.rs new file mode 100644 index 0000000..9d32457 --- /dev/null +++ b/satrs/src/health.rs @@ -0,0 +1,39 @@ +use crate::ComponentId; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum HealthState { + Healthy = 1, + Faulty = 2, + PermanentFaulty = 3, + ExternalControl = 4, + NeedsRecovery = 5, +} + +pub trait HealthTableProvider { + fn health(&self, id: ComponentId) -> Option; + fn set_health(&mut self, id: ComponentId, health: HealthState); +} + +#[cfg(feature = "std")] +#[derive(Debug, Clone)] +pub struct HealthTableMapSync( + std::sync::Arc>>, +); + +#[cfg(feature = "std")] +impl HealthTableMapSync { + pub fn new(health_table: hashbrown::HashMap) -> Self { + Self(std::sync::Arc::new(std::sync::Mutex::new(health_table))) + } +} + +#[cfg(feature = "std")] +impl HealthTableProvider for HealthTableMapSync { + fn health(&self, id: ComponentId) -> Option { + self.0.lock().unwrap().get(&id).copied() + } + + fn set_health(&mut self, id: ComponentId, health: HealthState) { + self.0.lock().unwrap().insert(id, health); + } +} diff --git a/satrs/src/lib.rs b/satrs/src/lib.rs index 1801be5..5b81fe2 100644 --- a/satrs/src/lib.rs +++ b/satrs/src/lib.rs @@ -22,14 +22,21 @@ extern crate downcast_rs; #[cfg(any(feature = "std", test))] extern crate std; +pub mod action; +#[cfg(feature = "alloc")] +pub mod dev_mgmt; pub mod encoding; pub mod event_man; pub mod events; #[cfg(feature = "std")] pub mod executable; pub mod hal; +pub mod health; +pub mod hk; +pub mod mode; #[cfg(feature = "std")] pub mod mode_tree; +pub mod params; pub mod pool; pub mod power; pub mod pus; @@ -38,14 +45,11 @@ pub mod request; pub mod res_code; #[cfg(feature = "alloc")] pub mod scheduling; +#[cfg(feature = "alloc")] +pub mod subsystem; pub mod time; pub mod tmtc; -pub mod action; -pub mod hk; -pub mod mode; -pub mod params; - pub use spacepackets; use spacepackets::PacketId; diff --git a/satrs/src/mode.rs b/satrs/src/mode.rs index 01beac9..b9bfa84 100644 --- a/satrs/src/mode.rs +++ b/satrs/src/mode.rs @@ -11,8 +11,10 @@ pub use alloc_mod::*; pub use std_mod::*; use crate::{ - queue::GenericTargetedMessagingError, - request::{GenericMessage, MessageMetadata, MessageReceiver, MessageReceiverWithId, RequestId}, + queue::{GenericReceiveError, GenericSendError}, + request::{ + GenericMessage, MessageMetadata, MessageReceiverProvider, MessageReceiverWithId, RequestId, + }, ComponentId, }; @@ -26,6 +28,11 @@ pub struct ModeAndSubmode { submode: Submode, } +pub const INVALID_MODE_VAL: Mode = Mode::MAX; +pub const UNKNOWN_MODE_VAL: Mode = Mode::MAX - 1; +pub const INVALID_MODE: ModeAndSubmode = ModeAndSubmode::new(INVALID_MODE_VAL, 0); +pub const UNKNOWN_MODE: ModeAndSubmode = ModeAndSubmode::new(UNKNOWN_MODE_VAL, 0); + impl ModeAndSubmode { pub const RAW_LEN: usize = size_of::() + size_of::(); @@ -111,7 +118,10 @@ impl TargetedModeCommand { pub enum ModeRequest { /// Mode information. Can be used to notify other components of changed modes. ModeInfo(ModeAndSubmode), - SetMode(ModeAndSubmode), + SetMode { + mode_and_submode: ModeAndSubmode, + forced: bool, + }, ReadMode, AnnounceMode, AnnounceModeRecursive, @@ -127,6 +137,8 @@ pub struct TargetedModeRequest { #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ModeReply { + /// Mode information. Can be used to notify other components of changed modes. + ModeInfo(ModeAndSubmode), /// Reply to a mode request to confirm the commanded mode was reached. ModeReply(ModeAndSubmode), // Can not reach the commanded mode. Contains a reason as a [ResultU16]. @@ -147,34 +159,33 @@ pub trait ModeRequestSender { request_id: RequestId, target_id: ComponentId, request: ModeRequest, - ) -> Result<(), GenericTargetedMessagingError>; + ) -> Result<(), GenericSendError>; } pub trait ModeRequestReceiver { fn try_recv_mode_request( &self, - ) -> Result>, GenericTargetedMessagingError>; + ) -> Result>, GenericReceiveError>; } -impl> ModeRequestReceiver +impl> ModeRequestReceiver for MessageReceiverWithId { fn try_recv_mode_request( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.try_recv_message() } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, thiserror::Error)] pub enum ModeError { - Messaging(GenericTargetedMessagingError), -} - -impl From for ModeError { - fn from(value: GenericTargetedMessagingError) -> Self { - Self::Messaging(value) - } + #[error("Messaging send error: {0}")] + Send(#[from] GenericSendError), + #[error("Messaging receive error: {0}")] + Receive(#[from] GenericReceiveError), + #[error("busy with other mode request")] + Busy, } pub trait ModeProvider { @@ -196,6 +207,7 @@ pub trait ModeRequestHandler: ModeProvider { &mut self, requestor: MessageMetadata, mode_and_submode: ModeAndSubmode, + forced: bool, ) -> Result<(), Self::Error>; fn announce_mode(&self, requestor_info: Option, recursive: bool); @@ -222,9 +234,10 @@ pub trait ModeRequestHandler: ModeProvider { request: GenericMessage, ) -> Result<(), Self::Error> { match request.message { - ModeRequest::SetMode(mode_and_submode) => { - self.start_transition(request.requestor_info, mode_and_submode) - } + ModeRequest::SetMode { + mode_and_submode, + forced, + } => self.start_transition(request.requestor_info, mode_and_submode, forced), ModeRequest::ReadMode => self.send_mode_reply( request.requestor_info, ModeReply::ModeReply(self.mode_and_submode()), @@ -243,15 +256,16 @@ pub trait ModeRequestHandler: ModeProvider { } pub trait ModeReplyReceiver { - fn try_recv_mode_reply( - &self, - ) -> Result>, GenericTargetedMessagingError>; + fn try_recv_mode_reply(&self) + -> Result>, GenericReceiveError>; } -impl> ModeReplyReceiver for MessageReceiverWithId { +impl> ModeReplyReceiver + for MessageReceiverWithId +{ fn try_recv_mode_reply( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.try_recv_message() } } @@ -264,24 +278,28 @@ pub trait ModeReplySender { &self, requestor_info: MessageMetadata, reply: ModeReply, - ) -> Result<(), GenericTargetedMessagingError>; + ) -> Result<(), GenericSendError>; } #[cfg(feature = "alloc")] pub mod alloc_mod { - use crate::request::{ - MessageSender, MessageSenderAndReceiver, MessageSenderMap, RequestAndReplySenderAndReceiver, + use crate::{ + queue::{GenericReceiveError, GenericSendError}, + request::{ + MessageSenderAndReceiver, MessageSenderMap, MessageSenderProvider, + MessageSenderStoreProvider, RequestAndReplySenderAndReceiver, + }, }; use super::*; - impl> MessageSenderMap { + impl> MessageSenderMap { pub fn send_mode_reply( &self, requestor_info: MessageMetadata, target_id: ComponentId, request: ModeReply, - ) -> Result<(), GenericTargetedMessagingError> { + ) -> Result<(), GenericSendError> { self.send_message(requestor_info, target_id, request) } @@ -290,8 +308,13 @@ pub mod alloc_mod { } } - impl, R: MessageReceiver> ModeReplySender - for MessageSenderAndReceiver + impl< + From, + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + SenderStore: MessageSenderStoreProvider, + > ModeReplySender + for MessageSenderAndReceiver { fn local_channel_id(&self) -> ComponentId { self.local_channel_id_generic() @@ -301,8 +324,8 @@ pub mod alloc_mod { &self, requestor_info: MessageMetadata, request: ModeReply, - ) -> Result<(), GenericTargetedMessagingError> { - self.message_sender_map.send_mode_reply( + ) -> Result<(), GenericSendError> { + self.message_sender_store.send_message( MessageMetadata::new(requestor_info.request_id(), self.local_channel_id()), requestor_info.sender_id(), request, @@ -310,37 +333,67 @@ pub mod alloc_mod { } } - impl, R: MessageReceiver> ModeReplyReceiver - for MessageSenderAndReceiver + impl< + To, + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + SenderStore: MessageSenderStoreProvider, + > ModeReplyReceiver + for MessageSenderAndReceiver { fn try_recv_mode_reply( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.message_receiver.try_recv_message() } } impl< - REQUEST, - S0: MessageSender, - R0: MessageReceiver, - S1: MessageSender, - R1: MessageReceiver, - > RequestAndReplySenderAndReceiver + Request, + ReqSender: MessageSenderProvider, + ReqReceiver: MessageReceiverProvider, + ReqSenderStore: MessageSenderStoreProvider, + Reply, + ReplySender: MessageSenderProvider, + ReplyReceiver: MessageReceiverProvider, + ReplySenderStore: MessageSenderStoreProvider, + > + RequestAndReplySenderAndReceiver< + Request, + ReqSender, + ReqReceiver, + ReqSenderStore, + Reply, + ReplySender, + ReplyReceiver, + ReplySenderStore, + > { - pub fn add_reply_target(&mut self, target_id: ComponentId, reply_sender: S1) { - self.reply_sender_map + pub fn add_reply_target(&mut self, target_id: ComponentId, reply_sender: ReplySender) { + self.reply_sender_store .add_message_target(target_id, reply_sender) } } impl< - REQUEST, - S0: MessageSender, - R0: MessageReceiver, - S1: MessageSender, - R1: MessageReceiver, - > ModeReplySender for RequestAndReplySenderAndReceiver + Request, + ReqSender: MessageSenderProvider, + ReqReceiver: MessageReceiverProvider, + ReqSenderStore: MessageSenderStoreProvider, + ReplySender: MessageSenderProvider, + ReplyReceiver: MessageReceiverProvider, + ReplySenderStore: MessageSenderStoreProvider, + > ModeReplySender + for RequestAndReplySenderAndReceiver< + Request, + ReqSender, + ReqReceiver, + ReqSenderStore, + ModeReply, + ReplySender, + ReplyReceiver, + ReplySenderStore, + > { fn local_channel_id(&self) -> ComponentId { self.local_channel_id_generic() @@ -349,42 +402,56 @@ pub mod alloc_mod { fn send_mode_reply( &self, requestor_info: MessageMetadata, - request: ModeReply, - ) -> Result<(), GenericTargetedMessagingError> { - self.reply_sender_map.send_mode_reply( + reply: ModeReply, + ) -> Result<(), GenericSendError> { + self.reply_sender_store.send_message( MessageMetadata::new(requestor_info.request_id(), self.local_channel_id()), requestor_info.sender_id(), - request, + reply, ) } } impl< - REQUEST, - S0: MessageSender, - R0: MessageReceiver, - S1: MessageSender, - R1: MessageReceiver, + Request, + ReqSender: MessageSenderProvider, + ReqReceiver: MessageReceiverProvider, + ReqSenderStore: MessageSenderStoreProvider, + ReplySender: MessageSenderProvider, + ReplyReceiver: MessageReceiverProvider, + ReplySenderStore: MessageSenderStoreProvider, > ModeReplyReceiver - for RequestAndReplySenderAndReceiver + for RequestAndReplySenderAndReceiver< + Request, + ReqSender, + ReqReceiver, + ReqSenderStore, + ModeReply, + ReplySender, + ReplyReceiver, + ReplySenderStore, + > { fn try_recv_mode_reply( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.reply_receiver.try_recv_message() } } /// Helper type definition for a mode handler which can handle mode requests. - pub type ModeRequestHandlerInterface = - MessageSenderAndReceiver; + pub type ModeRequestHandlerInterface = + MessageSenderAndReceiver; - impl, R: MessageReceiver> - ModeRequestHandlerInterface + impl< + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + ReplySenderStore: MessageSenderStoreProvider, + > ModeRequestHandlerInterface { pub fn try_recv_mode_request( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.try_recv_message() } @@ -392,7 +459,7 @@ pub mod alloc_mod { &self, requestor_info: MessageMetadata, reply: ModeReply, - ) -> Result<(), GenericTargetedMessagingError> { + ) -> Result<(), GenericSendError> { self.send_message( requestor_info.request_id(), requestor_info.sender_id(), @@ -403,12 +470,18 @@ pub mod alloc_mod { /// Helper type defintion for a mode handler object which can send mode requests and receive /// mode replies. - pub type ModeRequestorInterface = MessageSenderAndReceiver; + pub type ModeRequestorInterface = + MessageSenderAndReceiver; - impl, R: MessageReceiver> ModeRequestorInterface { + impl< + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + RequestSenderStore: MessageSenderStoreProvider, + > ModeRequestorInterface + { pub fn try_recv_mode_reply( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.try_recv_message() } @@ -417,23 +490,38 @@ pub mod alloc_mod { request_id: RequestId, target_id: ComponentId, reply: ModeRequest, - ) -> Result<(), GenericTargetedMessagingError> { + ) -> Result<(), GenericSendError> { self.send_message(request_id, target_id, reply) } } /// Helper type defintion for a mode handler object which can both send mode requests and /// process mode requests. - pub type ModeInterface = - RequestAndReplySenderAndReceiver; + pub type ModeInterface< + ReqSender, + ReqReceiver, + ReqSenderStore, + ReplySender, + ReplyReceiver, + ReplySenderStore, + > = RequestAndReplySenderAndReceiver< + ModeRequest, + ReqSender, + ReqReceiver, + ReqSenderStore, + ModeReply, + ReplySender, + ReplyReceiver, + ReplySenderStore, + >; - impl> MessageSenderMap { + impl> MessageSenderMap { pub fn send_mode_request( &self, requestor_info: MessageMetadata, target_id: ComponentId, request: ModeRequest, - ) -> Result<(), GenericTargetedMessagingError> { + ) -> Result<(), GenericSendError> { self.send_message(requestor_info, target_id, request) } @@ -442,35 +530,28 @@ pub mod alloc_mod { } } - /* - impl> ModeRequestSender for MessageSenderMapWithId { - fn local_channel_id(&self) -> ComponentId { - self.local_channel_id - } - - fn send_mode_request( - &self, - request_id: RequestId, - target_id: ComponentId, - request: ModeRequest, - ) -> Result<(), GenericTargetedMessagingError> { - self.send_message(request_id, target_id, request) - } - } - */ - - impl, R: MessageReceiver> ModeRequestReceiver - for MessageSenderAndReceiver + impl< + To, + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + SenderStore: MessageSenderStoreProvider, + > ModeRequestReceiver + for MessageSenderAndReceiver { fn try_recv_mode_request( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.message_receiver.try_recv_message() } } - impl, R: MessageReceiver> ModeRequestSender - for MessageSenderAndReceiver + impl< + From, + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + SenderStore: MessageSenderStoreProvider, + > ModeRequestSender + for MessageSenderAndReceiver { fn local_channel_id(&self) -> ComponentId { self.local_channel_id_generic() @@ -481,8 +562,8 @@ pub mod alloc_mod { request_id: RequestId, target_id: ComponentId, request: ModeRequest, - ) -> Result<(), GenericTargetedMessagingError> { - self.message_sender_map.send_mode_request( + ) -> Result<(), GenericSendError> { + self.message_sender_store.send_message( MessageMetadata::new(request_id, self.local_channel_id()), target_id, request, @@ -491,27 +572,50 @@ pub mod alloc_mod { } impl< - REPLY, - S0: MessageSender, - R0: MessageReceiver, - S1: MessageSender, - R1: MessageReceiver, - > RequestAndReplySenderAndReceiver + ReqSender: MessageSenderProvider, + ReqReceiver: MessageReceiverProvider, + ReqSenderStore: MessageSenderStoreProvider, + Reply, + ReplySender: MessageSenderProvider, + ReplyReceiver: MessageReceiverProvider, + ReplySenderStore: MessageSenderStoreProvider, + > + RequestAndReplySenderAndReceiver< + ModeRequest, + ReqSender, + ReqReceiver, + ReqSenderStore, + Reply, + ReplySender, + ReplyReceiver, + ReplySenderStore, + > { - pub fn add_request_target(&mut self, target_id: ComponentId, request_sender: S0) { - self.request_sender_map + pub fn add_request_target(&mut self, target_id: ComponentId, request_sender: ReqSender) { + self.request_sender_store .add_message_target(target_id, request_sender) } } impl< - REPLY, - S0: MessageSender, - R0: MessageReceiver, - S1: MessageSender, - R1: MessageReceiver, + ReqSender: MessageSenderProvider, + ReqReceiver: MessageReceiverProvider, + ReqSenderStore: MessageSenderStoreProvider, + Reply, + ReplySender: MessageSenderProvider, + ReplyReceiver: MessageReceiverProvider, + ReplySenderStore: MessageSenderStoreProvider, > ModeRequestSender - for RequestAndReplySenderAndReceiver + for RequestAndReplySenderAndReceiver< + ModeRequest, + ReqSender, + ReqReceiver, + ReqSenderStore, + Reply, + ReplySender, + ReplyReceiver, + ReplySenderStore, + > { fn local_channel_id(&self) -> ComponentId { self.local_channel_id_generic() @@ -522,8 +626,8 @@ pub mod alloc_mod { request_id: RequestId, target_id: ComponentId, request: ModeRequest, - ) -> Result<(), GenericTargetedMessagingError> { - self.request_sender_map.send_mode_request( + ) -> Result<(), GenericSendError> { + self.request_sender_store.send_message( MessageMetadata::new(request_id, self.local_channel_id()), target_id, request, @@ -532,17 +636,28 @@ pub mod alloc_mod { } impl< - REPLY, - S0: MessageSender, - R0: MessageReceiver, - S1: MessageSender, - R1: MessageReceiver, + ReqSender: MessageSenderProvider, + ReqReceiver: MessageReceiverProvider, + ReqSenderStore: MessageSenderStoreProvider, + Reply, + ReplySender: MessageSenderProvider, + ReplyReceiver: MessageReceiverProvider, + ReplySenderStore: MessageSenderStoreProvider, > ModeRequestReceiver - for RequestAndReplySenderAndReceiver + for RequestAndReplySenderAndReceiver< + ModeRequest, + ReqSender, + ReqReceiver, + ReqSenderStore, + Reply, + ReplySender, + ReplyReceiver, + ReplySenderStore, + > { fn try_recv_mode_request( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.request_receiver.try_recv_message() } } @@ -552,39 +667,97 @@ pub mod alloc_mod { pub mod std_mod { use std::sync::mpsc; + use crate::request::{MessageSenderList, OneMessageSender}; + use super::*; pub type ModeRequestHandlerMpsc = ModeRequestHandlerInterface< mpsc::Sender>, mpsc::Receiver>, + MessageSenderList>>, >; pub type ModeRequestHandlerMpscBounded = ModeRequestHandlerInterface< mpsc::SyncSender>, mpsc::Receiver>, + MessageSenderList>>, >; - pub type ModeRequestorMpsc = ModeRequestorInterface< + pub type ModeRequestorOneChildMpsc = ModeRequestorInterface< mpsc::Sender>, mpsc::Receiver>, + OneMessageSender>>, >; - pub type ModeRequestorBoundedMpsc = ModeRequestorInterface< + pub type ModeRequestorOneChildBoundedMpsc = ModeRequestorInterface< mpsc::SyncSender>, mpsc::Receiver>, + OneMessageSender>>, + >; + pub type ModeRequestorChildListMpsc = ModeRequestorInterface< + mpsc::Sender>, + mpsc::Receiver>, + MessageSenderList>>, + >; + pub type ModeRequestorChildListBoundedMpsc = ModeRequestorInterface< + mpsc::SyncSender>, + mpsc::Receiver>, + MessageSenderList>>, >; pub type ModeRequestorAndHandlerMpsc = ModeInterface< mpsc::Sender>, - mpsc::Receiver>, - mpsc::Sender>, mpsc::Receiver>, + MessageSenderList>>, + mpsc::Sender>, + mpsc::Receiver>, + MessageSenderList>>, >; pub type ModeRequestorAndHandlerMpscBounded = ModeInterface< mpsc::SyncSender>, - mpsc::Receiver>, - mpsc::SyncSender>, mpsc::Receiver>, + MessageSenderList>>, + mpsc::SyncSender>, + mpsc::Receiver>, + MessageSenderList>>, >; } #[cfg(test)] -mod tests {} +pub(crate) mod tests { + use core::cell::RefCell; + use std::collections::VecDeque; + + use crate::{request::RequestId, ComponentId}; + + use super::*; + + pub struct ModeReqWrapper { + pub request_id: RequestId, + pub target_id: ComponentId, + pub request: ModeRequest, + } + + #[derive(Default)] + pub struct ModeReqSenderMock { + pub requests: RefCell>, + } + + impl ModeRequestSender for ModeReqSenderMock { + fn local_channel_id(&self) -> crate::ComponentId { + 0 + } + + fn send_mode_request( + &self, + request_id: RequestId, + target_id: ComponentId, + request: ModeRequest, + ) -> Result<(), GenericSendError> { + self.requests.borrow_mut().push_back(ModeReqWrapper { + request_id, + target_id, + request, + }); + Ok(()) + } + } +} diff --git a/satrs/src/mode_tree.rs b/satrs/src/mode_tree.rs index 1cddd32..e058378 100644 --- a/satrs/src/mode_tree.rs +++ b/satrs/src/mode_tree.rs @@ -2,10 +2,57 @@ use alloc::vec::Vec; use hashbrown::HashMap; use crate::{ - mode::{Mode, ModeAndSubmode, Submode}, + mode::{Mode, ModeAndSubmode, ModeReply, ModeRequest, Submode}, + request::MessageSenderProvider, ComponentId, }; +#[cfg(feature = "alloc")] +pub use alloc_mod::*; + +/// Common trait for node modes which can have mode parents or mode children. +pub trait ModeNode { + fn id(&self) -> ComponentId; +} +/// Trait which denotes that an object is a parent in a mode tree. +/// +/// A mode parent is capable of sending mode requests to child objects and has a unique component +/// ID. +pub trait ModeParent: ModeNode { + type Sender: MessageSenderProvider; + + fn add_mode_child(&mut self, id: ComponentId, request_sender: Self::Sender); +} + +/// Trait which denotes that an object is a child in a mode tree. +/// +/// A child is capable of sending mode replies to parent objects and has a unique component ID. +pub trait ModeChild: ModeNode { + type Sender: MessageSenderProvider; + + fn add_mode_parent(&mut self, id: ComponentId, reply_sender: Self::Sender); +} + +/// Utility method which connects a mode tree parent object to a child object by calling +/// [ModeParent::add_mode_child] on the [parent][ModeParent] and calling +/// [ModeChild::add_mode_parent] on the [child][ModeChild]. +/// +/// # Arguments +/// +/// * `parent` - The parent object which implements [ModeParent]. +/// * `request_sender` - Sender object to send mode requests to the child. +/// * `child` - The child object which implements [ModeChild]. +/// * `reply_sender` - Sender object to send mode replies to the parent. +pub fn connect_mode_nodes( + parent: &mut impl ModeParent, + request_sender: ReqSender, + child: &mut impl ModeChild, + reply_sender: ReplySender, +) { + parent.add_mode_child(child.id(), request_sender); + child.add_mode_parent(parent.id(), reply_sender); +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TableEntryType { /// Target table containing information of the expected children modes for given mode. @@ -15,23 +62,553 @@ pub enum TableEntryType { Sequence, } -pub struct ModeTableEntry { +/// Common fields required for both target and sequence table entries. +/// +/// The most important parameters here are the target ID which this entry belongs to, and the mode +/// and submode the entry either will be commanded to for sequence table entries or which will be +/// monitored for target table entries. +#[derive(Debug, Copy, Clone)] +pub struct ModeTableEntryCommon { /// Name of respective table entry. pub name: &'static str, - /// Target channel ID. - pub channel_id: ComponentId, + /// Target component ID. + pub target_id: ComponentId, + /// Has a different meaning depending on whether this is a sequence table or a target table. + /// + /// - For sequence tables, this denotes the mode which will be commanded + /// - For target tables, this is the mode which the target children should have and which + /// might be monitored depending on configuration. pub mode_submode: ModeAndSubmode, + /// This mask allows to specify multiple allowed submodes for a given mode. pub allowed_submode_mask: Option, +} + +impl ModeTableEntryCommon { + pub fn set_allowed_submode_mask(&mut self, mask: Submode) { + self.allowed_submode_mask = Some(mask); + } + + pub fn allowed_submode_mask(&self) -> Option { + self.allowed_submode_mask + } +} + +/// An entry for the target tables. +#[derive(Debug)] +pub struct TargetTableEntry { + pub common: ModeTableEntryCommon, + pub monitor_state: bool, +} + +impl TargetTableEntry { + pub fn new( + name: &'static str, + target_id: ComponentId, + mode_submode: ModeAndSubmode, + allowed_submode_mask: Option, + ) -> Self { + Self { + common: ModeTableEntryCommon { + name, + target_id, + mode_submode, + allowed_submode_mask, + }, + monitor_state: true, + } + } + + pub fn new_with_precise_submode( + name: &'static str, + target_id: ComponentId, + mode_submode: ModeAndSubmode, + ) -> Self { + Self { + common: ModeTableEntryCommon { + name, + target_id, + mode_submode, + allowed_submode_mask: None, + }, + monitor_state: true, + } + } + + delegate::delegate! { + to self.common { + pub fn set_allowed_submode_mask(&mut self, mask: Submode); + pub fn allowed_submode_mask(&self) -> Option; + } + } +} + +/// An entry for the sequence tables. +/// +/// The [Self::check_success] field specifies that a mode sequence executor should check that the +/// target mode was actually reached before executing the next sequence. +#[derive(Debug)] +pub struct SequenceTableEntry { + pub common: ModeTableEntryCommon, pub check_success: bool, } -pub struct ModeTableMapValue { - /// Name for a given mode table entry. - pub name: &'static str, - pub entries: Vec, +impl SequenceTableEntry { + pub fn new( + name: &'static str, + target_id: ComponentId, + mode_submode: ModeAndSubmode, + check_success: bool, + ) -> Self { + Self { + common: ModeTableEntryCommon { + name, + target_id, + mode_submode, + allowed_submode_mask: None, + }, + check_success, + } + } + + delegate::delegate! { + to self.common { + pub fn set_allowed_submode_mask(&mut self, mask: Submode); + pub fn allowed_submode_mask(&self) -> Option; + } + } } -pub type ModeTable = HashMap; +#[derive(Debug, thiserror::Error)] +#[error("target {0} not in mode store")] +pub struct TargetNotInModeStoreError(pub ComponentId); + +/// Mode store value type. +#[derive(Debug, Copy, Clone)] +pub struct ModeStoreValue { + /// ID of the mode component. + id: ComponentId, + /// Current mode and submode of the component. + pub mode_and_submode: ModeAndSubmode, + /// State information to track whether a reply should be awaited for the mode component. + pub awaiting_reply: bool, +} + +impl ModeStoreValue { + pub fn new(id: ComponentId, mode_and_submode: ModeAndSubmode) -> Self { + Self { + id, + mode_and_submode, + awaiting_reply: false, + } + } + + pub fn id(&self) -> ComponentId { + self.id + } + + pub fn mode_and_submode(&self) -> ModeAndSubmode { + self.mode_and_submode + } +} + +pub trait ModeStoreProvider { + fn add_component(&mut self, target_id: ComponentId, mode: ModeAndSubmode); + + fn has_component(&self, target_id: ComponentId) -> bool; + + fn get(&self, target_id: ComponentId) -> Option<&ModeStoreValue>; + + fn get_mut(&mut self, target_id: ComponentId) -> Option<&mut ModeStoreValue>; + + /// Generic handler for mode replies received from child components. + /// + /// Implementation should clear the awaition flag if the `handle_reply_awaition` argument is + /// true and returns whether any children are still awaiting replies. If the flag is not set + fn mode_reply_handler_with_reply_awaition( + &mut self, + sender_id: ComponentId, + reported_mode_and_submode: Option, + ) -> bool { + self.mode_reply_handler(sender_id, reported_mode_and_submode, true) + .unwrap_or(false) + } + + fn mode_reply_handler_without_reply_awaition( + &mut self, + sender_id: ComponentId, + reported_mode_and_submode: Option, + ) { + self.mode_reply_handler(sender_id, reported_mode_and_submode, false); + } + + fn mode_reply_handler( + &mut self, + sender_id: ComponentId, + reported_mode_and_submode: Option, + with_reply_awaition: bool, + ) -> Option; +} + +#[cfg(feature = "alloc")] +pub mod alloc_mod { + use super::*; + + #[derive(Debug)] + pub struct TargetTablesMapValue { + /// Name for a given mode table entry. + pub name: &'static str, + /// Optional fallback mode if the target mode can not be kept. + pub fallback_mode: Option, + /// These are the rows of the a target table. + pub entries: Vec, + } + + impl TargetTablesMapValue { + pub fn new(name: &'static str, fallback_mode: Option) -> Self { + Self { + name, + fallback_mode, + entries: Default::default(), + } + } + + pub fn add_entry(&mut self, entry: TargetTableEntry) { + self.entries.push(entry); + } + } + + /// One sequence of a [SequenceTablesMapValue] in a [SequenceModeTables]. + /// + /// It contains all mode requests which need to be executed for a sequence step and it also + /// associates a [Self::name] with the sequence. + #[derive(Debug)] + pub struct SequenceTableMapTable { + /// Name for a given mode sequence. + pub name: &'static str, + /// These are the rows of the a sequence table. + pub entries: Vec, + } + + impl SequenceTableMapTable { + pub fn new(name: &'static str) -> Self { + Self { + name, + entries: Default::default(), + } + } + + pub fn add_entry(&mut self, entry: SequenceTableEntry) { + self.entries.push(entry); + } + } + + /// A sequence table entry. + /// + /// This is simply a list of [SequenceTableMapTable]s which also associates a [Self::name] + /// with the sequence. The order of sub-tables in the list also specifies the execution order + /// in the mode sequence. + #[derive(Debug)] + pub struct SequenceTablesMapValue { + /// Name for a given mode sequence. + pub name: &'static str, + /// Each sequence can consists of multiple sequences that are executed consecutively. + pub entries: Vec, + } + + impl SequenceTablesMapValue { + pub fn new(name: &'static str) -> Self { + Self { + name, + entries: Default::default(), + } + } + + pub fn add_sequence_table(&mut self, entry: SequenceTableMapTable) { + self.entries.push(entry); + } + } + + #[derive(Debug, Default)] + pub struct TargetModeTables(pub HashMap); + + impl TargetModeTables { + pub fn name(&self, mode: Mode) -> Option<&'static str> { + self.0.get(&mode).map(|value| value.name) + } + } + + impl SequenceModeTables { + pub fn name(&self, mode: Mode) -> Option<&'static str> { + self.0.get(&mode).map(|value| value.name) + } + + pub fn name_of_sequence(&self, mode: Mode, seq_idx: usize) -> Option<&'static str> { + self.0 + .get(&mode) + .map(|value| value.entries.get(seq_idx).map(|v| v.name))? + } + } + + /// This is the core data structure used to store mode sequence tables. + /// + /// A mode sequence table specifies which commands have to be sent in which order + /// to reach a certain [Mode]. Therefore, it simply maps a [Mode] to a [SequenceTablesMapValue]. + #[derive(Debug, Default)] + pub struct SequenceModeTables(pub HashMap); + + /// Mode store which tracks the [mode information][ModeStoreValue] inside a [Vec] + #[derive(Debug, Default)] + pub struct ModeStoreVec(pub alloc::vec::Vec); + + impl<'a> IntoIterator for &'a ModeStoreVec { + type Item = &'a ModeStoreValue; + type IntoIter = std::slice::Iter<'a, ModeStoreValue>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } + } + + impl<'a> IntoIterator for &'a mut ModeStoreVec { + type Item = &'a mut ModeStoreValue; + type IntoIter = std::slice::IterMut<'a, ModeStoreValue>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } + } + + /// Mode store which tracks the mode information inside a [hashbrown::HashMap] + #[derive(Debug, Default)] + pub struct ModeStoreMap(pub hashbrown::HashMap); + + impl<'a> IntoIterator for &'a ModeStoreMap { + type Item = (&'a ComponentId, &'a ModeStoreValue); + type IntoIter = hashbrown::hash_map::Iter<'a, ComponentId, ModeStoreValue>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } + } + + impl ModeStoreProvider for ModeStoreVec { + fn add_component(&mut self, target_id: ComponentId, mode: ModeAndSubmode) { + self.0.push(ModeStoreValue::new(target_id, mode)); + } + + fn has_component(&self, target_id: ComponentId) -> bool { + self.0.iter().any(|val| val.id == target_id) + } + + fn get(&self, target_id: ComponentId) -> Option<&ModeStoreValue> { + self.0.iter().find(|val| val.id == target_id) + } + + fn get_mut(&mut self, target_id: ComponentId) -> Option<&mut ModeStoreValue> { + self.0.iter_mut().find(|val| val.id == target_id) + } + + fn mode_reply_handler( + &mut self, + sender_id: ComponentId, + reported_mode_and_submode: Option, + handle_reply_awaition: bool, + ) -> Option { + let mut still_awating_replies = None; + if handle_reply_awaition { + still_awating_replies = Some(false); + } + self.0.iter_mut().for_each(|val| { + if val.id() == sender_id { + if let Some(mode_and_submode) = reported_mode_and_submode { + val.mode_and_submode = mode_and_submode; + } + if handle_reply_awaition { + val.awaiting_reply = false; + } + } + if handle_reply_awaition && val.awaiting_reply { + still_awating_replies = Some(true); + } + }); + still_awating_replies + } + } + + impl ModeStoreProvider for ModeStoreMap { + fn add_component(&mut self, target_id: ComponentId, mode: ModeAndSubmode) { + self.0 + .insert(target_id, ModeStoreValue::new(target_id, mode)); + } + + fn has_component(&self, target_id: ComponentId) -> bool { + self.0.contains_key(&target_id) + } + + fn get(&self, target_id: ComponentId) -> Option<&ModeStoreValue> { + self.0.get(&target_id) + } + + fn get_mut(&mut self, target_id: ComponentId) -> Option<&mut ModeStoreValue> { + self.0.get_mut(&target_id) + } + + fn mode_reply_handler( + &mut self, + sender_id: ComponentId, + reported_mode_and_submode: Option, + handle_reply_awaition: bool, + ) -> Option { + let mut still_awating_replies = None; + if handle_reply_awaition { + still_awating_replies = Some(false); + } + for val in self.0.values_mut() { + if val.id() == sender_id { + if let Some(mode_and_submode) = reported_mode_and_submode { + val.mode_and_submode = mode_and_submode; + } + if handle_reply_awaition { + val.awaiting_reply = false; + } + } + if handle_reply_awaition && val.awaiting_reply { + still_awating_replies = Some(true); + } + } + still_awating_replies + } + } +} #[cfg(test)] -mod tests {} +mod tests { + use super::*; + + fn generic_test(mode_store: &mut impl ModeStoreProvider) { + mode_store.add_component(1, ModeAndSubmode::new(0, 0)); + mode_store.add_component(2, ModeAndSubmode::new(1, 0)); + assert!(mode_store.has_component(1)); + assert!(mode_store.has_component(2)); + assert_eq!( + mode_store.get(1).unwrap().mode_and_submode(), + ModeAndSubmode::new(0, 0) + ); + assert!(!mode_store.get(1).unwrap().awaiting_reply); + assert!(!mode_store.get(2).unwrap().awaiting_reply); + assert_eq!(mode_store.get(1).unwrap().id, 1); + assert_eq!(mode_store.get(2).unwrap().id, 2); + assert!(mode_store.get(3).is_none()); + assert!(mode_store.get_mut(3).is_none()); + } + + fn generic_reply_handling_with_reply_awaition(mode_store: &mut impl ModeStoreProvider) { + mode_store.add_component(1, ModeAndSubmode::new(0, 0)); + mode_store.add_component(2, ModeAndSubmode::new(1, 0)); + mode_store.get_mut(1).unwrap().awaiting_reply = true; + mode_store.get_mut(2).unwrap().awaiting_reply = true; + let mut reply_awation_pending = + mode_store.mode_reply_handler_with_reply_awaition(1, Some(ModeAndSubmode::new(2, 0))); + assert!(reply_awation_pending); + reply_awation_pending = mode_store.mode_reply_handler_with_reply_awaition(2, None); + assert!(!reply_awation_pending); + assert!(!mode_store.get(1).unwrap().awaiting_reply); + assert!(!mode_store.get(2).unwrap().awaiting_reply); + assert_eq!( + mode_store.get(1).unwrap().mode_and_submode(), + ModeAndSubmode::new(2, 0) + ); + assert_eq!( + mode_store.get(2).unwrap().mode_and_submode(), + ModeAndSubmode::new(1, 0) + ); + } + + fn generic_reply_handling_test_no_reply_awaition(mode_store: &mut impl ModeStoreProvider) { + mode_store.add_component(1, ModeAndSubmode::new(0, 0)); + mode_store.add_component(2, ModeAndSubmode::new(1, 0)); + mode_store.get_mut(1).unwrap().awaiting_reply = true; + mode_store.get_mut(2).unwrap().awaiting_reply = true; + mode_store.mode_reply_handler_without_reply_awaition(1, Some(ModeAndSubmode::new(2, 0))); + mode_store.mode_reply_handler_without_reply_awaition(2, None); + assert!(mode_store.get(1).unwrap().awaiting_reply); + assert!(mode_store.get(2).unwrap().awaiting_reply); + assert_eq!( + mode_store.get(1).unwrap().mode_and_submode(), + ModeAndSubmode::new(2, 0) + ); + assert_eq!( + mode_store.get(2).unwrap().mode_and_submode(), + ModeAndSubmode::new(1, 0) + ); + } + + fn generic_reply_handling_with_reply_awaition_2(mode_store: &mut impl ModeStoreProvider) { + mode_store.add_component(1, ModeAndSubmode::new(0, 0)); + mode_store.add_component(2, ModeAndSubmode::new(1, 0)); + mode_store.get_mut(1).unwrap().awaiting_reply = true; + mode_store.get_mut(2).unwrap().awaiting_reply = true; + let mut reply_awation_pending = + mode_store.mode_reply_handler(1, Some(ModeAndSubmode::new(2, 0)), true); + assert!(reply_awation_pending.unwrap()); + reply_awation_pending = mode_store.mode_reply_handler(2, None, true); + assert!(!reply_awation_pending.unwrap()); + assert!(!mode_store.get(1).unwrap().awaiting_reply); + assert!(!mode_store.get(2).unwrap().awaiting_reply); + assert_eq!( + mode_store.get(1).unwrap().mode_and_submode(), + ModeAndSubmode::new(2, 0) + ); + assert_eq!( + mode_store.get(2).unwrap().mode_and_submode(), + ModeAndSubmode::new(1, 0) + ); + } + + #[test] + fn test_vec_mode_store() { + let mut mode_store = ModeStoreVec::default(); + generic_test(&mut mode_store); + } + + #[test] + fn test_map_mode_store() { + let mut mode_store = ModeStoreMap::default(); + generic_test(&mut mode_store); + } + + #[test] + fn test_generic_reply_handler_vec_with_reply_awaition() { + let mut mode_store = ModeStoreVec::default(); + generic_reply_handling_with_reply_awaition(&mut mode_store); + } + + #[test] + fn test_generic_reply_handler_vec_with_reply_awaition_2() { + let mut mode_store = ModeStoreVec::default(); + generic_reply_handling_with_reply_awaition_2(&mut mode_store); + } + + #[test] + fn test_generic_reply_handler_map_with_reply_awaition() { + let mut mode_store = ModeStoreMap::default(); + generic_reply_handling_with_reply_awaition(&mut mode_store); + } + + #[test] + fn test_generic_reply_handler_map_with_reply_awaition_2() { + let mut mode_store = ModeStoreMap::default(); + generic_reply_handling_with_reply_awaition_2(&mut mode_store); + } + + #[test] + fn test_generic_reply_handler_vec_no_reply_awaition() { + let mut mode_store = ModeStoreVec::default(); + generic_reply_handling_test_no_reply_awaition(&mut mode_store); + } + #[test] + fn test_generic_reply_handler_map_no_reply_awaition() { + let mut mode_store = ModeStoreMap::default(); + generic_reply_handling_test_no_reply_awaition(&mut mode_store); + } +} diff --git a/satrs/src/pool.rs b/satrs/src/pool.rs index 1b40191..f9b7fdd 100644 --- a/satrs/src/pool.rs +++ b/satrs/src/pool.rs @@ -775,7 +775,7 @@ mod alloc_mod { /// if the next fitting subpool is full. This is useful to ensure the pool remains useful /// for all data sizes as long as possible. However, an undesirable side-effect might be /// the chocking of larger subpools by underdimensioned smaller subpools. - #[derive(Clone)] + #[derive(Debug, Clone)] pub struct StaticPoolConfig { cfg: Vec, spill_to_higher_subpools: bool, @@ -834,6 +834,7 @@ mod alloc_mod { /// [address][PoolAddr] type. Adding any data to the pool will yield a store address. /// Modification and read operations are done using a reference to a store address. Deletion /// will consume the store address. + #[derive(Debug)] pub struct StaticMemoryPool { pool_cfg: StaticPoolConfig, pool: Vec>, diff --git a/satrs/src/pus/action.rs b/satrs/src/pus/action.rs index 6bcd270..7a048b1 100644 --- a/satrs/src/pus/action.rs +++ b/satrs/src/pus/action.rs @@ -66,9 +66,10 @@ impl GenericActionReplyPus { pub mod alloc_mod { use crate::{ action::ActionRequest, - queue::GenericTargetedMessagingError, + queue::{GenericReceiveError, GenericSendError}, request::{ - GenericMessage, MessageReceiver, MessageSender, MessageSenderAndReceiver, RequestId, + GenericMessage, MessageReceiverProvider, MessageSenderAndReceiver, + MessageSenderProvider, MessageSenderStoreProvider, RequestId, }, ComponentId, }; @@ -76,15 +77,18 @@ pub mod alloc_mod { use super::ActionReplyPus; /// Helper type definition for a mode handler which can handle mode requests. - pub type ActionRequestHandlerInterface = - MessageSenderAndReceiver; + pub type ActionRequestHandlerInterface = + MessageSenderAndReceiver; - impl, R: MessageReceiver> - ActionRequestHandlerInterface + impl< + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + ReplySender: MessageSenderStoreProvider, + > ActionRequestHandlerInterface { pub fn try_recv_action_request( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.try_recv_message() } @@ -93,22 +97,31 @@ pub mod alloc_mod { request_id: RequestId, target_id: ComponentId, reply: ActionReplyPus, - ) -> Result<(), GenericTargetedMessagingError> { + ) -> Result<(), GenericSendError> { self.send_message(request_id, target_id, reply) } } /// Helper type defintion for a mode handler object which can send mode requests and receive /// mode replies. - pub type ActionRequestorInterface = - MessageSenderAndReceiver; + pub type ActionRequestorInterface = + MessageSenderAndReceiver< + ActionRequest, + ActionReplyPus, + Sender, + Receiver, + RequestSenderStore, + >; - impl, R: MessageReceiver> - ActionRequestorInterface + impl< + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + RequestSenderStore: MessageSenderStoreProvider, + > ActionRequestorInterface { pub fn try_recv_action_reply( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.try_recv_message() } @@ -117,7 +130,7 @@ pub mod alloc_mod { request_id: RequestId, target_id: ComponentId, request: ActionRequest, - ) -> Result<(), GenericTargetedMessagingError> { + ) -> Result<(), GenericSendError> { self.send_message(request_id, target_id, request) } } @@ -132,6 +145,7 @@ pub mod std_mod { verification::{self, TcStateToken}, ActivePusRequestStd, ActiveRequestProvider, DefaultActiveRequestMap, }, + request::{MessageSenderMap, OneMessageSender}, ComponentId, }; @@ -174,22 +188,38 @@ pub mod std_mod { } pub type DefaultActiveActionRequestMap = DefaultActiveRequestMap; - pub type ActionRequestHandlerMpsc = ActionRequestHandlerInterface< + pub type ActionRequestHandlerOneSenderMpsc = ActionRequestHandlerInterface< mpsc::Sender>, mpsc::Receiver>, + OneMessageSender< + GenericMessage, + mpsc::Sender>, + >, >; - pub type ActionRequestHandlerMpscBounded = ActionRequestHandlerInterface< + pub type ActionRequestHandlerOneSenderMpscBounded = ActionRequestHandlerInterface< mpsc::SyncSender>, mpsc::Receiver>, + OneMessageSender< + GenericMessage, + mpsc::SyncSender>, + >, >; - pub type ActionRequestorMpsc = ActionRequestorInterface< + pub type ActionRequestorWithSenderMapMpsc = ActionRequestorInterface< mpsc::Sender>, mpsc::Receiver>, + MessageSenderMap< + GenericMessage, + mpsc::Sender>, + >, >; - pub type ActionRequestorBoundedMpsc = ActionRequestorInterface< + pub type ActionRequestorWithSenderMapBoundedMpsc = ActionRequestorInterface< mpsc::SyncSender>, mpsc::Receiver>, + MessageSenderMap< + GenericMessage, + mpsc::SyncSender>, + >, >; } diff --git a/satrs/src/pus/event_srv.rs b/satrs/src/pus/event_srv.rs index cb1bcb5..dbf2861 100644 --- a/satrs/src/pus/event_srv.rs +++ b/satrs/src/pus/event_srv.rs @@ -9,14 +9,14 @@ use std::sync::mpsc::Sender; use super::verification::VerificationReportingProvider; use super::{ - EcssTcInMemConverter, EcssTcReceiver, EcssTmSender, GenericConversionError, + EcssTcInMemConversionProvider, EcssTcReceiver, EcssTmSender, GenericConversionError, GenericRoutingError, HandlingStatus, PusServiceHelper, }; pub struct PusEventServiceHandler< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, > { pub service_helper: @@ -27,7 +27,7 @@ pub struct PusEventServiceHandler< impl< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, > PusEventServiceHandler { @@ -170,7 +170,7 @@ mod tests { event_man::EventRequestWithToken, tests::PusServiceHandlerWithSharedStoreCommon, verification::{TcStateAccepted, VerificationToken}, - DirectPusPacketHandlerResult, EcssTcInSharedStoreConverter, PusPacketHandlingError, + DirectPusPacketHandlerResult, EcssTcInSharedPoolConverter, PusPacketHandlingError, }, }; @@ -183,7 +183,7 @@ mod tests { handler: PusEventServiceHandler< MpscTcReceiver, PacketSenderWithSharedPool, - EcssTcInSharedStoreConverter, + EcssTcInSharedPoolConverter, VerificationReporter, >, } diff --git a/satrs/src/pus/mod.rs b/satrs/src/pus/mod.rs index d4c5ff6..b254138 100644 --- a/satrs/src/pus/mod.rs +++ b/satrs/src/pus/mod.rs @@ -947,7 +947,7 @@ pub mod std_mod { } } - pub trait EcssTcInMemConverter { + pub trait EcssTcInMemConversionProvider { fn cache(&mut self, possible_packet: &TcInMemory) -> Result<(), PusTcFromMemError>; fn tc_slice_raw(&self) -> &[u8]; @@ -980,7 +980,7 @@ pub mod std_mod { pub pus_tc_raw: Option>, } - impl EcssTcInMemConverter for EcssTcInVecConverter { + impl EcssTcInMemConversionProvider for EcssTcInVecConverter { fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> { self.pus_tc_raw = None; match tc_in_memory { @@ -1011,24 +1011,25 @@ pub mod std_mod { /// [SharedStaticMemoryPool] structure. This is useful if run-time allocation for these /// packets should be avoided. Please note that this structure is not able to convert TCs which /// are stored as a `Vec`. - pub struct EcssTcInSharedStoreConverter { + #[derive(Clone)] + pub struct EcssTcInSharedPoolConverter { sender_id: Option, - shared_tc_store: SharedStaticMemoryPool, + shared_tc_pool: SharedStaticMemoryPool, pus_buf: Vec, } - impl EcssTcInSharedStoreConverter { + impl EcssTcInSharedPoolConverter { pub fn new(shared_tc_store: SharedStaticMemoryPool, max_expected_tc_size: usize) -> Self { Self { sender_id: None, - shared_tc_store, + shared_tc_pool: shared_tc_store, pus_buf: alloc::vec![0; max_expected_tc_size], } } pub fn copy_tc_to_buf(&mut self, addr: PoolAddr) -> Result<(), PusTcFromMemError> { // Keep locked section as short as possible. - let mut tc_pool = self.shared_tc_store.write().map_err(|_| { + let mut tc_pool = self.shared_tc_pool.write().map_err(|_| { PusTcFromMemError::EcssTmtc(EcssTmtcError::Store(PoolError::LockError)) })?; let tc_size = tc_pool.len_of_data(&addr).map_err(EcssTmtcError::Store)?; @@ -1048,7 +1049,7 @@ pub mod std_mod { } } - impl EcssTcInMemConverter for EcssTcInSharedStoreConverter { + impl EcssTcInMemConversionProvider for EcssTcInSharedPoolConverter { fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> { match tc_in_memory { super::TcInMemory::Pool(packet_in_pool) => { @@ -1071,6 +1072,44 @@ pub mod std_mod { } } + // TODO: alloc feature flag? + #[derive(Clone)] + pub enum EcssTcInMemConverter { + Static(EcssTcInSharedPoolConverter), + Heap(EcssTcInVecConverter), + } + + impl EcssTcInMemConverter { + pub fn new_static(static_store_converter: EcssTcInSharedPoolConverter) -> Self { + EcssTcInMemConverter::Static(static_store_converter) + } + + pub fn new_heap(heap_converter: EcssTcInVecConverter) -> Self { + EcssTcInMemConverter::Heap(heap_converter) + } + } + + impl EcssTcInMemConversionProvider for EcssTcInMemConverter { + fn cache(&mut self, tc_in_memory: &TcInMemory) -> Result<(), PusTcFromMemError> { + match self { + EcssTcInMemConverter::Static(converter) => converter.cache(tc_in_memory), + EcssTcInMemConverter::Heap(converter) => converter.cache(tc_in_memory), + } + } + fn tc_slice_raw(&self) -> &[u8] { + match self { + EcssTcInMemConverter::Static(converter) => converter.tc_slice_raw(), + EcssTcInMemConverter::Heap(converter) => converter.tc_slice_raw(), + } + } + fn sender_id(&self) -> Option { + match self { + EcssTcInMemConverter::Static(converter) => converter.sender_id(), + EcssTcInMemConverter::Heap(converter) => converter.sender_id(), + } + } + } + pub struct PusServiceBase< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, @@ -1094,7 +1133,7 @@ pub mod std_mod { pub struct PusServiceHelper< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, > { pub common: PusServiceBase, @@ -1104,7 +1143,7 @@ pub mod std_mod { impl< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, > PusServiceHelper { @@ -1221,9 +1260,10 @@ pub(crate) fn source_buffer_large_enough( #[cfg(any(feature = "test_util", test))] pub mod test_util { - use crate::request::UniqueApidTargetId; use spacepackets::ecss::{tc::PusTcCreator, tm::PusTmReader}; + use crate::request::UniqueApidTargetId; + use super::{ verification::{self, TcStateAccepted, VerificationToken}, DirectPusPacketHandlerResult, PusPacketHandlingError, @@ -1232,6 +1272,7 @@ pub mod test_util { pub const TEST_APID: u16 = 0x101; pub const TEST_UNIQUE_ID_0: u32 = 0x05; pub const TEST_UNIQUE_ID_1: u32 = 0x06; + pub const TEST_COMPONENT_ID_0: UniqueApidTargetId = UniqueApidTargetId::new(TEST_APID, TEST_UNIQUE_ID_0); pub const TEST_COMPONENT_ID_1: UniqueApidTargetId = @@ -1268,14 +1309,13 @@ pub mod tests { use spacepackets::ecss::tm::{GenericPusTmSecondaryHeader, PusTmCreator, PusTmReader}; use spacepackets::ecss::{PusPacket, WritablePusPacket}; use spacepackets::CcsdsPacket; + use test_util::{TEST_APID, TEST_COMPONENT_ID_0}; use crate::pool::{PoolProvider, SharedStaticMemoryPool, StaticMemoryPool, StaticPoolConfig}; use crate::pus::verification::{RequestId, VerificationReporter}; use crate::tmtc::{PacketAsVec, PacketInPool, PacketSenderWithSharedPool, SharedPacketPool}; use crate::ComponentId; - use super::test_util::{TEST_APID, TEST_COMPONENT_ID_0}; - use super::verification::test_util::TestVerificationReporter; use super::verification::{ TcStateAccepted, VerificationReporterCfg, VerificationReportingProvider, VerificationToken, @@ -1346,7 +1386,7 @@ pub mod tests { pub type PusServiceHelperStatic = PusServiceHelper< MpscTcReceiver, PacketSenderWithSharedPool, - EcssTcInSharedStoreConverter, + EcssTcInSharedPoolConverter, VerificationReporter, >; @@ -1373,8 +1413,7 @@ pub mod tests { VerificationReporter::new(TEST_COMPONENT_ID_0.id(), &verif_cfg); let test_srv_tm_sender = PacketSenderWithSharedPool::new(tm_tx, shared_tm_pool_wrapper.clone()); - let in_store_converter = - EcssTcInSharedStoreConverter::new(shared_tc_pool.clone(), 2048); + let in_store_converter = EcssTcInSharedPoolConverter::new(shared_tc_pool.clone(), 2048); ( Self { pus_buf: RefCell::new([0; 2048]), diff --git a/satrs/src/pus/mode.rs b/satrs/src/pus/mode.rs index 5ff78bf..773ad90 100644 --- a/satrs/src/pus/mode.rs +++ b/satrs/src/pus/mode.rs @@ -39,7 +39,7 @@ mod tests { use crate::{ mode::{ ModeAndSubmode, ModeReply, ModeReplySender, ModeRequest, ModeRequestSender, - ModeRequestorAndHandlerMpsc, ModeRequestorMpsc, + ModeRequestorAndHandlerMpsc, ModeRequestorOneChildMpsc, }, request::{GenericMessage, MessageMetadata}, }; @@ -52,7 +52,8 @@ mod tests { fn test_simple_mode_requestor() { let (reply_sender, reply_receiver) = mpsc::channel(); let (request_sender, request_receiver) = mpsc::channel(); - let mut mode_requestor = ModeRequestorMpsc::new(TEST_COMPONENT_ID_0, reply_receiver); + let mut mode_requestor = + ModeRequestorOneChildMpsc::new(TEST_COMPONENT_ID_0, reply_receiver); mode_requestor.add_message_target(TEST_COMPONENT_ID_1, request_sender); // Send a request and verify it arrives at the receiver. diff --git a/satrs/src/pus/scheduler_srv.rs b/satrs/src/pus/scheduler_srv.rs index f84a0c6..922ab97 100644 --- a/satrs/src/pus/scheduler_srv.rs +++ b/satrs/src/pus/scheduler_srv.rs @@ -1,7 +1,7 @@ use super::scheduler::PusSchedulerProvider; use super::verification::{VerificationReporter, VerificationReportingProvider}; use super::{ - DirectPusPacketHandlerResult, EcssTcInMemConverter, EcssTcInSharedStoreConverter, + DirectPusPacketHandlerResult, EcssTcInMemConversionProvider, EcssTcInSharedPoolConverter, EcssTcInVecConverter, EcssTcReceiver, EcssTmSender, HandlingStatus, MpscTcReceiver, PartialPusHandlingError, PusServiceHelper, }; @@ -24,7 +24,7 @@ use std::sync::mpsc; pub struct PusSchedServiceHandler< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, PusScheduler: PusSchedulerProvider, > { @@ -36,7 +36,7 @@ pub struct PusSchedServiceHandler< impl< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, Scheduler: PusSchedulerProvider, > @@ -229,7 +229,7 @@ pub type PusService11SchedHandlerDynWithBoundedMpsc = PusSchedServ pub type PusService11SchedHandlerStaticWithMpsc = PusSchedServiceHandler< MpscTcReceiver, PacketSenderWithSharedPool, - EcssTcInSharedStoreConverter, + EcssTcInSharedPoolConverter, VerificationReporter, PusScheduler, >; @@ -238,7 +238,7 @@ pub type PusService11SchedHandlerStaticWithMpsc = PusSchedServiceH pub type PusService11SchedHandlerStaticWithBoundedMpsc = PusSchedServiceHandler< MpscTcReceiver, PacketSenderWithSharedPool, - EcssTcInSharedStoreConverter, + EcssTcInSharedPoolConverter, VerificationReporter, PusScheduler, >; @@ -253,7 +253,7 @@ mod tests { scheduler::{self, PusSchedulerProvider, TcInfo}, tests::PusServiceHandlerWithSharedStoreCommon, verification::{RequestId, TcStateAccepted, VerificationToken}, - EcssTcInSharedStoreConverter, + EcssTcInSharedPoolConverter, }; use crate::pus::{DirectPusPacketHandlerResult, MpscTcReceiver, PusPacketHandlingError}; use crate::tmtc::PacketSenderWithSharedPool; @@ -276,7 +276,7 @@ mod tests { handler: PusSchedServiceHandler< MpscTcReceiver, PacketSenderWithSharedPool, - EcssTcInSharedStoreConverter, + EcssTcInSharedPoolConverter, VerificationReporter, TestScheduler, >, diff --git a/satrs/src/pus/test.rs b/satrs/src/pus/test.rs index 5094be9..8b00453 100644 --- a/satrs/src/pus/test.rs +++ b/satrs/src/pus/test.rs @@ -9,8 +9,9 @@ use std::sync::mpsc; use super::verification::{VerificationReporter, VerificationReportingProvider}; use super::{ - EcssTcInMemConverter, EcssTcInSharedStoreConverter, EcssTcInVecConverter, EcssTcReceiver, - EcssTmSender, GenericConversionError, HandlingStatus, MpscTcReceiver, PusServiceHelper, + EcssTcInMemConversionProvider, EcssTcInSharedPoolConverter, EcssTcInVecConverter, + EcssTcReceiver, EcssTmSender, GenericConversionError, HandlingStatus, MpscTcReceiver, + PusServiceHelper, }; /// This is a helper class for [std] environments to handle generic PUS 17 (test service) packets. @@ -18,7 +19,7 @@ use super::{ pub struct PusService17TestHandler< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, > { pub service_helper: @@ -28,7 +29,7 @@ pub struct PusService17TestHandler< impl< TcReceiver: EcssTcReceiver, TmSender: EcssTmSender, - TcInMemConverter: EcssTcInMemConverter, + TcInMemConverter: EcssTcInMemConversionProvider, VerificationReporter: VerificationReportingProvider, > PusService17TestHandler { @@ -127,7 +128,7 @@ pub type PusService17TestHandlerDynWithBoundedMpsc = PusService17TestHandler< pub type PusService17TestHandlerStaticWithBoundedMpsc = PusService17TestHandler< MpscTcReceiver, PacketSenderWithSharedPool, - EcssTcInSharedStoreConverter, + EcssTcInSharedPoolConverter, VerificationReporter, >; @@ -142,7 +143,7 @@ mod tests { }; use crate::pus::verification::{TcStateAccepted, VerificationToken}; use crate::pus::{ - DirectPusPacketHandlerResult, EcssTcInSharedStoreConverter, EcssTcInVecConverter, + DirectPusPacketHandlerResult, EcssTcInSharedPoolConverter, EcssTcInVecConverter, GenericConversionError, HandlingStatus, MpscTcReceiver, MpscTmAsVecSender, PartialPusHandlingError, PusPacketHandlingError, }; @@ -162,7 +163,7 @@ mod tests { handler: PusService17TestHandler< MpscTcReceiver, PacketSenderWithSharedPool, - EcssTcInSharedStoreConverter, + EcssTcInSharedPoolConverter, VerificationReporter, >, } diff --git a/satrs/src/queue.rs b/satrs/src/queue.rs index 93c8ec8..c167114 100644 --- a/satrs/src/queue.rs +++ b/satrs/src/queue.rs @@ -1,6 +1,3 @@ -use core::fmt::{Display, Formatter}; -#[cfg(feature = "std")] -use std::error::Error; #[cfg(feature = "std")] use std::sync::mpsc; @@ -10,89 +7,31 @@ use crate::ComponentId; pub type ChannelId = u32; /// Generic error type for sending something via a message queue. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] pub enum GenericSendError { + #[error("rx side has disconnected")] RxDisconnected, + #[error("queue with max capacity of {0:?} is full")] QueueFull(Option), + #[error("target queue with ID {0} does not exist")] TargetDoesNotExist(ComponentId), } -impl Display for GenericSendError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self { - GenericSendError::RxDisconnected => { - write!(f, "rx side has disconnected") - } - GenericSendError::QueueFull(max_cap) => { - write!(f, "queue with max capacity of {max_cap:?} is full") - } - GenericSendError::TargetDoesNotExist(target) => { - write!(f, "target queue with ID {target} does not exist") - } - } - } -} - -#[cfg(feature = "std")] -impl Error for GenericSendError {} - /// Generic error type for sending something via a message queue. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] pub enum GenericReceiveError { + #[error("nothing to receive")] Empty, + #[error("tx side with id {0:?} has disconnected")] TxDisconnected(Option), } -impl Display for GenericReceiveError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self { - Self::TxDisconnected(channel_id) => { - write!(f, "tx side with id {channel_id:?} has disconnected") - } - Self::Empty => { - write!(f, "nothing to receive") - } - } - } -} - -#[cfg(feature = "std")] -impl Error for GenericReceiveError {} - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, thiserror::Error)] pub enum GenericTargetedMessagingError { - Send(GenericSendError), - Receive(GenericReceiveError), -} -impl From for GenericTargetedMessagingError { - fn from(value: GenericSendError) -> Self { - Self::Send(value) - } -} - -impl From for GenericTargetedMessagingError { - fn from(value: GenericReceiveError) -> Self { - Self::Receive(value) - } -} - -impl Display for GenericTargetedMessagingError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self { - Self::Send(err) => write!(f, "generic targeted messaging error: {}", err), - Self::Receive(err) => write!(f, "generic targeted messaging error: {}", err), - } - } -} - -#[cfg(feature = "std")] -impl Error for GenericTargetedMessagingError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - GenericTargetedMessagingError::Send(send) => Some(send), - GenericTargetedMessagingError::Receive(receive) => Some(receive), - } - } + #[error("generic targeted messaging send error: {0}")] + Send(#[from] GenericSendError), + #[error("generic targeted messaging receive error: {0}")] + Receive(#[from] GenericReceiveError), } #[cfg(feature = "std")] diff --git a/satrs/src/request.rs b/satrs/src/request.rs index f188798..180ce00 100644 --- a/satrs/src/request.rs +++ b/satrs/src/request.rs @@ -13,7 +13,10 @@ use spacepackets::{ ByteConversionError, }; -use crate::{queue::GenericTargetedMessagingError, ComponentId}; +use crate::{ + queue::{GenericReceiveError, GenericSendError}, + ComponentId, +}; /// Generic request ID type. Requests can be associated with an ID to have a unique identifier /// for them. This can be useful for tasks like tracking their progress. @@ -140,37 +143,38 @@ impl GenericMessage { } /// Generic trait for objects which can send targeted messages. -pub trait MessageSender: Send { - fn send(&self, message: GenericMessage) -> Result<(), GenericTargetedMessagingError>; +pub trait MessageSenderProvider: Send { + fn send(&self, message: GenericMessage) -> Result<(), GenericSendError>; } // Generic trait for objects which can receive targeted messages. -pub trait MessageReceiver { - fn try_recv(&self) -> Result>, GenericTargetedMessagingError>; +pub trait MessageReceiverProvider { + fn try_recv(&self) -> Result>, GenericReceiveError>; } -pub struct MessageWithSenderIdReceiver>(pub R, PhantomData); +pub struct MessageWithSenderIdReceiver>( + pub Receiver, + PhantomData, +); -impl> From for MessageWithSenderIdReceiver { +impl> From for MessageWithSenderIdReceiver { fn from(receiver: R) -> Self { MessageWithSenderIdReceiver(receiver, PhantomData) } } -impl> MessageWithSenderIdReceiver { - pub fn try_recv_message( - &self, - ) -> Result>, GenericTargetedMessagingError> { +impl> MessageWithSenderIdReceiver { + pub fn try_recv_message(&self) -> Result>, GenericReceiveError> { self.0.try_recv() } } -pub struct MessageReceiverWithId> { +pub struct MessageReceiverWithId> { local_channel_id: ComponentId, reply_receiver: MessageWithSenderIdReceiver, } -impl> MessageReceiverWithId { +impl> MessageReceiverWithId { pub fn new(local_channel_id: ComponentId, reply_receiver: R) -> Self { Self { local_channel_id, @@ -183,43 +187,129 @@ impl> MessageReceiverWithId { } } -impl> MessageReceiverWithId { - pub fn try_recv_message( - &self, - ) -> Result>, GenericTargetedMessagingError> { +impl> MessageReceiverWithId { + pub fn try_recv_message(&self) -> Result>, GenericReceiveError> { self.reply_receiver.0.try_recv() } } +pub trait MessageSenderStoreProvider: Default { + fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender); + + fn send_message( + &self, + requestor_info: MessageMetadata, + target_channel_id: ComponentId, + message: Message, + ) -> Result<(), GenericSendError>; +} + #[cfg(feature = "alloc")] pub mod alloc_mod { use crate::queue::GenericSendError; + use std::convert::From; use super::*; use hashbrown::HashMap; - pub struct MessageSenderMap>( - pub HashMap, + pub struct OneMessageSender> { + pub id_and_sender: Option<(ComponentId, S)>, + pub(crate) phantom: PhantomData, + } + + impl> Default for OneMessageSender { + fn default() -> Self { + Self { + id_and_sender: Default::default(), + phantom: Default::default(), + } + } + } + + impl> MessageSenderStoreProvider + for OneMessageSender + { + fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender) { + if self.id_and_sender.is_some() { + return; + } + self.id_and_sender = Some((target_id, message_sender)); + } + + fn send_message( + &self, + requestor_info: MessageMetadata, + target_channel_id: ComponentId, + message: Msg, + ) -> Result<(), GenericSendError> { + if let Some((current_id, sender)) = &self.id_and_sender { + if *current_id == target_channel_id { + sender.send(GenericMessage::new(requestor_info, message))?; + return Ok(()); + } + } + Err(GenericSendError::TargetDoesNotExist(target_channel_id)) + } + } + + pub struct MessageSenderList>( + pub alloc::vec::Vec<(ComponentId, S)>, pub(crate) PhantomData, ); - impl> Default for MessageSenderMap { + impl> Default for MessageSenderList { fn default() -> Self { Self(Default::default(), PhantomData) } } - impl> MessageSenderMap { - pub fn add_message_target(&mut self, target_id: ComponentId, message_sender: S) { - self.0.insert(target_id, message_sender); + impl> MessageSenderStoreProvider + for MessageSenderList + { + fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender) { + self.0.push((target_id, message_sender)); } - pub fn send_message( + fn send_message( &self, requestor_info: MessageMetadata, target_channel_id: ComponentId, - message: MSG, - ) -> Result<(), GenericTargetedMessagingError> { + message: Msg, + ) -> Result<(), GenericSendError> { + for (current_id, sender) in &self.0 { + if *current_id == target_channel_id { + sender.send(GenericMessage::new(requestor_info, message))?; + return Ok(()); + } + } + Err(GenericSendError::TargetDoesNotExist(target_channel_id)) + } + } + + pub struct MessageSenderMap>( + pub HashMap, + pub(crate) PhantomData, + ); + + impl> Default for MessageSenderMap { + fn default() -> Self { + Self(Default::default(), PhantomData) + } + } + + impl> MessageSenderStoreProvider + for MessageSenderMap + { + fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender) { + self.0.insert(target_id, message_sender); + } + + fn send_message( + &self, + requestor_info: MessageMetadata, + target_channel_id: ComponentId, + message: Msg, + ) -> Result<(), GenericSendError> { if self.0.contains_key(&target_channel_id) { return self .0 @@ -227,29 +317,42 @@ pub mod alloc_mod { .unwrap() .send(GenericMessage::new(requestor_info, message)); } - Err(GenericSendError::TargetDoesNotExist(target_channel_id).into()) + Err(GenericSendError::TargetDoesNotExist(target_channel_id)) } } - pub struct MessageSenderAndReceiver, R: MessageReceiver> { + pub struct MessageSenderAndReceiver< + To, + From, + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + SenderStore: MessageSenderStoreProvider, + > { pub local_channel_id: ComponentId, - pub message_sender_map: MessageSenderMap, - pub message_receiver: MessageWithSenderIdReceiver, + pub message_sender_store: SenderStore, + pub message_receiver: MessageWithSenderIdReceiver, + pub(crate) phantom: PhantomData<(To, Sender)>, } - impl, R: MessageReceiver> - MessageSenderAndReceiver + impl< + To, + From, + Sender: MessageSenderProvider, + Receiver: MessageReceiverProvider, + SenderStore: MessageSenderStoreProvider, + > MessageSenderAndReceiver { - pub fn new(local_channel_id: ComponentId, message_receiver: R) -> Self { + pub fn new(local_channel_id: ComponentId, message_receiver: Receiver) -> Self { Self { local_channel_id, - message_sender_map: Default::default(), + message_sender_store: Default::default(), message_receiver: MessageWithSenderIdReceiver::from(message_receiver), + phantom: PhantomData, } } - pub fn add_message_target(&mut self, target_id: ComponentId, message_sender: S) { - self.message_sender_map + pub fn add_message_target(&mut self, target_id: ComponentId, message_sender: Sender) { + self.message_sender_store .add_message_target(target_id, message_sender) } @@ -262,9 +365,9 @@ pub mod alloc_mod { &self, request_id: RequestId, target_id: ComponentId, - message: TO, - ) -> Result<(), GenericTargetedMessagingError> { - self.message_sender_map.send_message( + message: To, + ) -> Result<(), GenericSendError> { + self.message_sender_store.send_message( MessageMetadata::new(request_id, self.local_channel_id_generic()), target_id, message, @@ -274,48 +377,64 @@ pub mod alloc_mod { /// Try to receive a message, which can be a reply or a request, depending on the generics. pub fn try_recv_message( &self, - ) -> Result>, GenericTargetedMessagingError> { + ) -> Result>, GenericReceiveError> { self.message_receiver.try_recv_message() } } pub struct RequestAndReplySenderAndReceiver< - REQUEST, - REPLY, - S0: MessageSender, - R0: MessageReceiver, - S1: MessageSender, - R1: MessageReceiver, + Request, + ReqSender: MessageSenderProvider, + ReqReceiver: MessageReceiverProvider, + ReqSenderStore: MessageSenderStoreProvider, + Reply, + ReplySender: MessageSenderProvider, + ReplyReceiver: MessageReceiverProvider, + ReplySenderStore: MessageSenderStoreProvider, > { pub local_channel_id: ComponentId, // These 2 are a functional group. - pub request_sender_map: MessageSenderMap, - pub reply_receiver: MessageWithSenderIdReceiver, + pub request_sender_store: ReqSenderStore, + pub reply_receiver: MessageWithSenderIdReceiver, // These 2 are a functional group. - pub request_receiver: MessageWithSenderIdReceiver, - pub reply_sender_map: MessageSenderMap, + pub request_receiver: MessageWithSenderIdReceiver, + pub reply_sender_store: ReplySenderStore, + phantom: PhantomData<(ReqSender, ReplySender)>, } impl< - REQUEST, - REPLY, - S0: MessageSender, - R0: MessageReceiver, - S1: MessageSender, - R1: MessageReceiver, - > RequestAndReplySenderAndReceiver + Request, + ReqSender: MessageSenderProvider, + ReqReceiver: MessageReceiverProvider, + ReqSenderStore: MessageSenderStoreProvider, + Reply, + ReplySender: MessageSenderProvider, + ReplyReceiver: MessageReceiverProvider, + ReplySenderStore: MessageSenderStoreProvider, + > + RequestAndReplySenderAndReceiver< + Request, + ReqSender, + ReqReceiver, + ReqSenderStore, + Reply, + ReplySender, + ReplyReceiver, + ReplySenderStore, + > { pub fn new( local_channel_id: ComponentId, - request_receiver: R1, - reply_receiver: R0, + request_receiver: ReqReceiver, + reply_receiver: ReplyReceiver, ) -> Self { Self { local_channel_id, request_receiver: request_receiver.into(), reply_receiver: reply_receiver.into(), - request_sender_map: Default::default(), - reply_sender_map: Default::default(), + request_sender_store: Default::default(), + reply_sender_store: Default::default(), + phantom: PhantomData, } } @@ -333,21 +452,19 @@ pub mod std_mod { use crate::queue::{GenericReceiveError, GenericSendError}; - impl MessageSender for mpsc::Sender> { - fn send(&self, message: GenericMessage) -> Result<(), GenericTargetedMessagingError> { + impl MessageSenderProvider for mpsc::Sender> { + fn send(&self, message: GenericMessage) -> Result<(), GenericSendError> { self.send(message) .map_err(|_| GenericSendError::RxDisconnected)?; Ok(()) } } - impl MessageSender for mpsc::SyncSender> { - fn send(&self, message: GenericMessage) -> Result<(), GenericTargetedMessagingError> { + impl MessageSenderProvider for mpsc::SyncSender> { + fn send(&self, message: GenericMessage) -> Result<(), GenericSendError> { if let Err(e) = self.try_send(message) { return match e { - mpsc::TrySendError::Full(_) => Err(GenericSendError::QueueFull(None).into()), - mpsc::TrySendError::Disconnected(_) => { - Err(GenericSendError::RxDisconnected.into()) - } + mpsc::TrySendError::Full(_) => Err(GenericSendError::QueueFull(None)), + mpsc::TrySendError::Disconnected(_) => Err(GenericSendError::RxDisconnected), }; } Ok(()) @@ -357,14 +474,14 @@ pub mod std_mod { pub type MessageSenderMapMpsc = MessageReceiverWithId>; pub type MessageSenderMapBoundedMpsc = MessageReceiverWithId>; - impl MessageReceiver for mpsc::Receiver> { - fn try_recv(&self) -> Result>, GenericTargetedMessagingError> { + impl MessageReceiverProvider for mpsc::Receiver> { + fn try_recv(&self) -> Result>, GenericReceiveError> { match self.try_recv() { Ok(msg) => Ok(Some(msg)), Err(e) => match e { mpsc::TryRecvError::Empty => Ok(None), mpsc::TryRecvError::Disconnected => { - Err(GenericReceiveError::TxDisconnected(None).into()) + Err(GenericReceiveError::TxDisconnected(None)) } }, } @@ -385,8 +502,8 @@ mod tests { }; use crate::{ - queue::{GenericReceiveError, GenericSendError, GenericTargetedMessagingError}, - request::{MessageMetadata, MessageSenderMap}, + queue::{GenericReceiveError, GenericSendError}, + request::{MessageMetadata, MessageSenderMap, MessageSenderStoreProvider}, }; use super::{GenericMessage, MessageReceiverWithId, UniqueApidTargetId}; @@ -478,9 +595,7 @@ mod tests { let reply = receiver.try_recv_message(); assert!(reply.is_err()); let error = reply.unwrap_err(); - if let GenericTargetedMessagingError::Receive(GenericReceiveError::TxDisconnected(None)) = - error - { + if let GenericReceiveError::TxDisconnected(None) = error { } else { panic!("unexpected error type"); } @@ -529,9 +644,7 @@ mod tests { ); assert!(result.is_err()); let error = result.unwrap_err(); - if let GenericTargetedMessagingError::Send(GenericSendError::TargetDoesNotExist(target)) = - error - { + if let GenericSendError::TargetDoesNotExist(target) = error { assert_eq!(target, TEST_CHANNEL_ID_2); } else { panic!("Unexpected error type"); @@ -556,7 +669,7 @@ mod tests { ); assert!(result.is_err()); let error = result.unwrap_err(); - if let GenericTargetedMessagingError::Send(GenericSendError::QueueFull(capacity)) = error { + if let GenericSendError::QueueFull(capacity) = error { assert!(capacity.is_none()); } else { panic!("Unexpected error type {}", error); @@ -576,7 +689,7 @@ mod tests { ); assert!(result.is_err()); let error = result.unwrap_err(); - if let GenericTargetedMessagingError::Send(GenericSendError::RxDisconnected) = error { + if let GenericSendError::RxDisconnected = error { } else { panic!("Unexpected error type {}", error); } diff --git a/satrs/src/scheduling.rs b/satrs/src/scheduling.rs index 5df7033..ff2c0a1 100644 --- a/satrs/src/scheduling.rs +++ b/satrs/src/scheduling.rs @@ -154,7 +154,7 @@ pub mod std_mod { } /// Can be used to set the start of the slot to the current time. This is useful if a custom - /// runner implementation is used instead of the [Self::start] method. + /// runner implementation is used instead of the [Self::run_one_task_cycle] method. pub fn init_start_of_slot(&mut self) { self.start_of_slot = Instant::now(); } diff --git a/satrs/src/subsystem.rs b/satrs/src/subsystem.rs new file mode 100644 index 0000000..0212f76 --- /dev/null +++ b/satrs/src/subsystem.rs @@ -0,0 +1,1610 @@ +use crate::{ + health::{HealthState, HealthTableProvider}, + mode::{Mode, ModeAndSubmode, ModeReply, ModeRequest, ModeRequestSender, UNKNOWN_MODE_VAL}, + mode_tree::{ + ModeStoreProvider, ModeStoreVec, SequenceModeTables, SequenceTableMapTable, + SequenceTablesMapValue, TargetModeTables, TargetNotInModeStoreError, TargetTablesMapValue, + }, + queue::GenericTargetedMessagingError, + request::{GenericMessage, RequestId}, + ComponentId, +}; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum SequenceExecutionHelperState { + /// The sequence execution is IDLE, no command is loaded or the sequence exection has + /// finished + Idle, + /// The sequence helper is executing a sequence and no replies need to be awaited. + Busy, + /// The sequence helper is still awaiting a reply from a mode children. The reply awaition + /// is a property of a mode commanding sequence + AwaitingSuccessCheck, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ModeCommandingResult { + /// The commanding of all children is finished + Done, + /// One step of a commanding chain is finished + StepDone, + /// Reply awaition is required for some children + AwaitingSuccessCheck, +} + +#[derive(Debug, thiserror::Error)] +#[error("mode {0} does not exist")] +pub struct ModeDoesNotExistError(Mode); + +#[derive(Debug, thiserror::Error)] +pub enum StartSequenceError { + #[error("mode {0} does not exist")] + ModeDoesNotExist(#[from] ModeDoesNotExistError), + #[error("invalid request ID")] + InvalidRequestId(RequestId), +} + +/// This sequence execution helper includes some boilerplate logic to +/// execute [SequenceModeTables]. +/// +/// It takes care of commanding the [ModeRequest]s specified in those tables and also includes the +/// states required to track the current progress of a sequence execution and take care of +/// reply and success awaition. +#[derive(Debug)] +pub struct SequenceExecutionHelper { + target_mode: Option, + state: SequenceExecutionHelperState, + request_id: Option, + current_sequence_index: Option, + last_sequence_index: Option, +} + +impl Default for SequenceExecutionHelper { + fn default() -> Self { + Self { + target_mode: None, + state: SequenceExecutionHelperState::Idle, + request_id: None, + current_sequence_index: None, + last_sequence_index: None, + } + } +} + +pub trait IsChildCommandable { + fn is_commandable(&self, id: ComponentId) -> bool; +} + +impl IsChildCommandable for T +where + T: HealthTableProvider, +{ + fn is_commandable(&self, id: ComponentId) -> bool { + self.health(id) + .is_none_or(|h| h != HealthState::ExternalControl) + } +} + +impl SequenceExecutionHelper { + pub fn new() -> Self { + Default::default() + } + + /// Load a new mode sequence to be executed + pub fn load( + &mut self, + mode: Mode, + request_id: RequestId, + sequence_tables: &SequenceModeTables, + ) -> Result<(), ModeDoesNotExistError> { + if !sequence_tables.0.contains_key(&mode) { + return Err(ModeDoesNotExistError(mode)); + } + self.target_mode = Some(mode); + self.request_id = Some(request_id); + self.state = SequenceExecutionHelperState::Busy; + self.current_sequence_index = None; + Ok(()) + } + + /// Run the sequence execution helper. + /// + /// This function will execute the sequence in the given [SequenceModeTables] based on the + /// mode loaded in [Self::load]. It calls [Self::execute_sequence_and_map_to_result] and + /// automatically takes care of state management, including increments of the sequence table + /// index. + /// + /// The returnvalues of the helper have the following meaning. + /// + /// * [ModeCommandingResult::AwaitingSuccessCheck] - The sequence is still awaiting a success. + /// The user should check whether all children have reached the commanded target mode, for + /// example by checking [mode replies][ModeReply] received by the children components, and + /// then calling [Self::confirm_sequence_done] to advance to the sequence or complete the + /// sequence. + /// * [ModeCommandingResult::CommandingDone] - The sequence is done. The user can load a new + /// sequence now without overwriting the last one. The sequence executor is in + /// [SequenceExecutionHelperState::Idle] again. + /// * [ModeCommandingResult::CommandingStepDone] - The sequence has advanced one step. The user + /// can now call [Self::run] again to immediately execute the next step in the sequence. + /// + /// Generally, periodic execution of the [Self::run] method should be performed while + /// [Self::state] is not [SequenceExecutionHelperState::Idle]. + /// + /// # Arguments + /// + /// * `table` - This table contains the sequence tables to reach the mode previously loaded + /// with [Self::load] + /// * `sender` - The sender to send mode requests to the components + /// * `children_mode_store` - The mode store vector to keep track of the mode states of + /// children components + pub fn run( + &mut self, + table: &SequenceModeTables, + sender: &impl ModeRequestSender, + children_mode_store: &mut ModeStoreVec, + is_commandable: &impl IsChildCommandable, + ) -> Result { + if self.state == SequenceExecutionHelperState::Idle { + return Ok(ModeCommandingResult::Done); + } + if self.state == SequenceExecutionHelperState::AwaitingSuccessCheck { + return Ok(ModeCommandingResult::AwaitingSuccessCheck); + } + if self.target_mode.is_none() { + return Ok(ModeCommandingResult::Done); + } + match self.current_sequence_index { + Some(idx) => { + // Execute the sequence. + let seq_table_value = table.0.get(&self.target_mode.unwrap()).unwrap(); + self.execute_sequence_and_map_to_result( + seq_table_value, + idx, + sender, + children_mode_store, + is_commandable, + ) + } + None => { + // Find the first sequence + let seq_table_value = table.0.get(&self.target_mode.unwrap()).unwrap(); + self.last_sequence_index = Some(seq_table_value.entries.len() - 1); + if seq_table_value.entries.is_empty() { + Ok(ModeCommandingResult::Done) + } else { + self.current_sequence_index = Some(0); + self.execute_sequence_and_map_to_result( + seq_table_value, + 0, + sender, + children_mode_store, + is_commandable, + ) + } + } + } + } + + /// Retrieve the currently loaded target mode + pub fn target_mode(&self) -> Option { + self.target_mode + } + + /// Confirm that a sequence which is awaiting a success check is done + pub fn confirm_sequence_done(&mut self) { + if let SequenceExecutionHelperState::AwaitingSuccessCheck = self.state { + self.state = SequenceExecutionHelperState::Busy; + if let (Some(last_sequence_index), Some(current_sequence_index)) = + (self.last_sequence_index, self.current_sequence_index) + { + if current_sequence_index == last_sequence_index { + self.state = SequenceExecutionHelperState::Idle; + } + } + self.current_sequence_index = Some(self.current_sequence_index.unwrap() + 1); + } + } + + /// Internal state of the execution helper. + pub fn state(&self) -> SequenceExecutionHelperState { + self.state + } + + pub fn request_id(&self) -> Option { + self.request_id + } + + pub fn set_request_id(&mut self, request_id: RequestId) { + self.request_id = Some(request_id); + } + + pub fn awaiting_success_check(&self) -> bool { + self.state == SequenceExecutionHelperState::AwaitingSuccessCheck + } + + pub fn current_sequence_index(&self) -> Option { + self.current_sequence_index + } + + /// Execute a sequence at the given sequence index for a given [SequenceTablesMapValue]. + /// + /// This method calls [Self::execute_sequence] and maps the result to a [ModeCommandingResult]. + /// It is also called by the [Self::run] method of this helper. + pub fn execute_sequence_and_map_to_result( + &mut self, + seq_table_value: &SequenceTablesMapValue, + sequence_idx: usize, + sender: &impl ModeRequestSender, + mode_store_vec: &mut ModeStoreVec, + is_commandable: &impl IsChildCommandable, + ) -> Result { + if self.state() == SequenceExecutionHelperState::Idle || self.request_id.is_none() { + return Ok(ModeCommandingResult::Done); + } + if Self::execute_sequence( + self.request_id.unwrap(), + &seq_table_value.entries[sequence_idx], + sender, + mode_store_vec, + is_commandable, + )? { + self.state = SequenceExecutionHelperState::AwaitingSuccessCheck; + Ok(ModeCommandingResult::AwaitingSuccessCheck) + } else if seq_table_value.entries.len() - 1 == sequence_idx { + self.state = SequenceExecutionHelperState::Idle; + return Ok(ModeCommandingResult::Done); + } else { + self.current_sequence_index = Some(sequence_idx + 1); + return Ok(ModeCommandingResult::StepDone); + } + } + + /// Generic stateless execution helper method. + /// + /// The [RequestId] and the [SequenceTableMapTable] to be executed are passed explicitely + /// here. This method is called by [Self::execute_sequence_and_map_to_result]. + /// + /// This method itereates through the entries of the given sequence table and sends out + /// [ModeRequest]s to set the modes of the children according to the table entries. + /// It also sets the reply awaition field in the children mode store where a success + /// check is required to true. + /// + /// It returns whether any commanding success check is required by any entry in the table. + pub fn execute_sequence( + request_id: RequestId, + map_table: &SequenceTableMapTable, + sender: &impl ModeRequestSender, + children_mode_store: &mut ModeStoreVec, + commandable: &impl IsChildCommandable, + ) -> Result { + let mut some_succes_check_required = false; + for entry in &map_table.entries { + if !commandable.is_commandable(entry.common.target_id) { + continue; + } + sender.send_mode_request( + request_id, + entry.common.target_id, + ModeRequest::SetMode { + mode_and_submode: entry.common.mode_submode, + forced: false, + }, + )?; + if entry.check_success { + children_mode_store.0.iter_mut().for_each(|val| { + if val.id() == entry.common.target_id { + val.awaiting_reply = true; + } + }); + some_succes_check_required = true; + } + } + Ok(some_succes_check_required) + } +} + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +pub enum ModeTreeHelperState { + #[default] + Idle, + /// The helper is currently trying to keep a target mode. + TargetKeeping, + /// The helper is currently busy to command a mode. + ModeCommanding, +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub enum SubsystemHelperResult { + #[default] + Idle, + /// Busy with target keeping. + TargetKeeping, + /// Result of a mode commanding operation + ModeCommanding(ModeCommandingResult), +} + +impl From for SubsystemHelperResult { + fn from(value: ModeCommandingResult) -> Self { + Self::ModeCommanding(value) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ModeTreeHelperError { + #[error("generic targeted messaging error: {0}")] + Message(#[from] GenericTargetedMessagingError), + #[error("current mode {0} is not contained in target table")] + CurrentModeNotInTargetTable(Mode), + /// Mode command has failed, for example while executing a mode table. + #[error("mode command failed")] + ModeCommmandFailure { + /// Table index of the sequence table entry which failed. + seq_table_index: Option, + }, + /// Target mode keeping violation. + #[error("target keeping violation")] + TargetKeepingViolation { + /// Table index of the sequence table entry which failed. + fallback_mode: Option, + }, +} + +/// This is a helper object which can be used by a subsystem component to execute mode sequences +/// and perform target keeping. +/// +/// This helper object tries to compose as much data and state information as possible which is +/// required for this process. +pub struct SubsystemCommandingHelper { + /// State of the helper. + state: ModeTreeHelperState, + /// Current mode of the owner subsystem. + current_mode: Mode, + /// This data structure is used to track all mode children. + pub children_mode_store: ModeStoreVec, + /// This field is set when a mode sequence is executed. It is used to determine whether mode + /// replies are relevant for reply awaition logic. + active_internal_request_id: Option, + /// The primary data structure to keep the target state information for subsystem + /// [modes][Mode]. it specifies the mode each child should have for a certain subsystem mode + /// and is relevant for target keeping. + pub target_tables: TargetModeTables, + /// The primary data structure to keep the sequence commanding information for commanded + /// subsystem [modes][Mode]. It specifies the actual commands and the order they should be + /// sent in to reach a certain [mode][Mode]. + pub sequence_tables: SequenceModeTables, + /// The sequence execution helper is used to execute sequences in the [Self::sequence_tables]. + pub seq_exec_helper: SequenceExecutionHelper, +} + +impl Default for SubsystemCommandingHelper { + fn default() -> Self { + Self { + current_mode: UNKNOWN_MODE_VAL, + state: Default::default(), + children_mode_store: Default::default(), + active_internal_request_id: None, + target_tables: Default::default(), + sequence_tables: Default::default(), + seq_exec_helper: Default::default(), + } + } +} + +impl SubsystemCommandingHelper { + /// Create a new substem commanding helper with an intial [ModeTreeHelperState::Idle] state, + /// an empty mode children store and empty target and sequence mode tables. + pub fn new( + children_mode_store: ModeStoreVec, + target_tables: TargetModeTables, + sequence_tables: SequenceModeTables, + ) -> Self { + Self { + current_mode: UNKNOWN_MODE_VAL, + state: ModeTreeHelperState::Idle, + children_mode_store, + active_internal_request_id: None, + target_tables, + sequence_tables, + seq_exec_helper: Default::default(), + } + } + + pub fn state(&self) -> ModeTreeHelperState { + self.state + } + + pub fn mode(&self) -> Mode { + self.current_mode + } + + pub fn request_id(&self) -> Option { + self.active_internal_request_id.map(|v| v >> 8) + } + + /// This returns the internal request ID, which is the regular [Self::request_id] specified + /// by the user shifter 8 to the right and then increment with the current sequence commanding + /// step. The value can still be retrieved because it might be required for reply verification. + /// + /// The state machine specifies this request ID for all mode commands related to the + /// current step of sequence commanding. + pub fn internal_request_id(&self) -> Option { + self.active_internal_request_id + } + + /// Retrieve the fallback mode for the current mode of the subsystem by trying to retrieve + /// it from the target table. + /// + /// If the current mode does not have a fallback mode, returns [None]. + /// If the current mode is not inside the target table, returns a [ModeDoesNotExistError]. + /// The fallback mode can and should be commanded when a target keeping violation was detected + /// or after self-commanding to the current mode has failed, which can happen after a failed + /// mode table execution. + pub fn fallback_mode(&self) -> Result, ModeDoesNotExistError> { + self.target_tables + .0 + .get(&self.current_mode) + .ok_or(ModeDoesNotExistError(self.current_mode)) + .map(|v| v.fallback_mode) + } + + /// Add a mode child to the internal [Self::children_mode_store]. + pub fn add_mode_child(&mut self, child: ComponentId, mode: ModeAndSubmode) { + self.children_mode_store.add_component(child, mode); + } + + /// Add a target mode table and an associated sequence mode table. + pub fn add_target_and_sequence_table( + &mut self, + mode: Mode, + target_table_val: TargetTablesMapValue, + sequence_table_val: SequenceTablesMapValue, + ) { + self.target_tables.0.insert(mode, target_table_val); + self.sequence_tables.0.insert(mode, sequence_table_val); + } + + /// Starts a command sequence for a given [mode][Mode]. + /// + /// # Arguments + /// + /// - `mode` - The mode to command + /// - `request_id` - Request ID associated with the command sequence. The value of this value + /// should not be larger than the maximum possible value for 24 bits: (2 ^ 24) - 1 = 16777215 + /// because 8 bits are reserved for internal sequence index tracking. + pub fn start_command_sequence( + &mut self, + mode: Mode, + request_id: RequestId, + ) -> Result<(), StartSequenceError> { + if request_id > 2_u32.pow(24) - 1 { + return Err(StartSequenceError::InvalidRequestId(request_id)); + } + self.active_internal_request_id = Some(request_id << 8); + self.seq_exec_helper.load( + mode, + self.active_internal_request_id.unwrap(), + &self.sequence_tables, + )?; + self.state = ModeTreeHelperState::ModeCommanding; + Ok(()) + } + + pub fn send_announce_mode_cmd_to_children( + &self, + request_id: RequestId, + req_sender: &impl ModeRequestSender, + recursive: bool, + ) -> Result<(), GenericTargetedMessagingError> { + let mut request = ModeRequest::AnnounceMode; + if recursive { + request = ModeRequest::AnnounceModeRecursive; + } + for child in &self.children_mode_store.0 { + req_sender.send_mode_request(request_id, child.id(), request)?; + } + Ok(()) + } + + pub fn state_machine( + &mut self, + opt_reply: Option>, + req_sender: &impl ModeRequestSender, + is_commandable: &impl IsChildCommandable, + ) -> Result { + if let Some(reply) = opt_reply { + if self.handle_mode_reply(&reply)? { + if self.seq_exec_helper.state() == SequenceExecutionHelperState::Idle { + self.transition_to_target_keeping(); + return Ok(SubsystemHelperResult::ModeCommanding( + ModeCommandingResult::Done, + )); + } + return Ok(SubsystemHelperResult::ModeCommanding( + ModeCommandingResult::StepDone, + )); + } + } + match self.state { + ModeTreeHelperState::Idle => Ok(SubsystemHelperResult::Idle), + ModeTreeHelperState::TargetKeeping => { + // We check whether the current mode is modelled by a target table first. + if let Some(target_table) = self.target_tables.0.get(&self.current_mode) { + self.perform_target_keeping(target_table)?; + } + Ok(SubsystemHelperResult::TargetKeeping) + } + ModeTreeHelperState::ModeCommanding => { + let result = self.seq_exec_helper.run( + &self.sequence_tables, + req_sender, + &mut self.children_mode_store, + is_commandable, + )?; + match result { + ModeCommandingResult::Done => { + // By default, the helper will automatically transition into the target keeping + // mode after an executed sequence. + self.transition_to_target_keeping(); + } + ModeCommandingResult::StepDone => { + // Normally, this step is done after all replies were received, but if no + // reply checking is required for a command sequence, the step would never + // be performed, so this function needs to be called here as well. + self.update_internal_req_id(); + } + ModeCommandingResult::AwaitingSuccessCheck => (), + } + Ok(result.into()) + } + } + } + + fn transition_to_target_keeping(&mut self) { + self.state = ModeTreeHelperState::TargetKeeping; + self.current_mode = self.seq_exec_helper.target_mode().unwrap(); + } + + fn perform_target_keeping( + &self, + target_table: &TargetTablesMapValue, + ) -> Result<(), ModeTreeHelperError> { + for entry in &target_table.entries { + if !entry.monitor_state { + continue; + } + let mut target_mode_violated = false; + self.children_mode_store.0.iter().for_each(|val| { + if val.id() == entry.common.target_id { + target_mode_violated = + if let Some(allowed_submode_mask) = entry.allowed_submode_mask() { + let fixed_bits = !allowed_submode_mask; + (val.mode_and_submode().mode() != entry.common.mode_submode.mode()) + && (val.mode_and_submode().submode() & fixed_bits + != entry.common.mode_submode.submode() & fixed_bits) + } else { + val.mode_and_submode() != entry.common.mode_submode + }; + } + }); + if target_mode_violated { + // Target keeping violated. Report violation and fallback mode to user. + return Err(ModeTreeHelperError::TargetKeepingViolation { + fallback_mode: target_table.fallback_mode, + }); + } + } + Ok(()) + } + + fn update_internal_req_id(&mut self) { + let new_internal_req_id = self.request_id().unwrap() << 8 + | self.seq_exec_helper.current_sequence_index().unwrap() as u32; + self.seq_exec_helper.set_request_id(new_internal_req_id); + self.active_internal_request_id = Some(new_internal_req_id); + } + + // Handles a mode reply message and returns whether the reply completes a step of sequence + // commanding. + fn handle_mode_reply( + &mut self, + reply: &GenericMessage, + ) -> Result { + if !self.children_mode_store.has_component(reply.sender_id()) { + return Ok(false); + } + let mut generic_mode_reply_handler = + |sender_id, mode_and_submode: Option, success: bool| { + let mut partial_step_done = false; + // Tying the reply awaition to the request ID ensures that something like replies + // belonging to older requests do not interfere with the completion handling of + // the mode commanding. This is important for forced mode commands. + let mut handle_awaition = false; + if self.state == ModeTreeHelperState::ModeCommanding + && self.active_internal_request_id.is_some() + && reply.request_id() == self.active_internal_request_id.unwrap() + { + handle_awaition = true; + } + let still_awating_replies = self.children_mode_store.mode_reply_handler( + sender_id, + mode_and_submode, + handle_awaition, + ); + if self.state == ModeTreeHelperState::ModeCommanding + && handle_awaition + && !still_awating_replies.unwrap_or(false) + { + self.seq_exec_helper.confirm_sequence_done(); + self.update_internal_req_id(); + partial_step_done = true; + } + if !success && self.state == ModeTreeHelperState::ModeCommanding { + // The user has to decide how to proceed. + self.state = ModeTreeHelperState::Idle; + return Err(ModeTreeHelperError::ModeCommmandFailure { + seq_table_index: self.seq_exec_helper.current_sequence_index(), + }); + } + Ok(partial_step_done) + }; + match reply.message { + ModeReply::ModeInfo(mode_and_submode) => { + generic_mode_reply_handler(reply.sender_id(), Some(mode_and_submode), true) + } + ModeReply::ModeReply(mode_and_submode) => { + generic_mode_reply_handler(reply.sender_id(), Some(mode_and_submode), true) + } + ModeReply::CantReachMode(_) => { + generic_mode_reply_handler(reply.sender_id(), None, false) + } + ModeReply::WrongMode { reached, .. } => { + generic_mode_reply_handler(reply.sender_id(), Some(reached), true) + } + } + } + + pub fn update_child_mode( + &mut self, + child: ComponentId, + mode: ModeAndSubmode, + ) -> Result<(), TargetNotInModeStoreError> { + let val_mut = self + .children_mode_store + .get_mut(child) + .ok_or(TargetNotInModeStoreError(child))?; + val_mut.mode_and_submode = mode; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::{ + mode::{ + tests::{ModeReqSenderMock, ModeReqWrapper}, + Mode, ModeAndSubmode, ModeReply, ModeRequest, UNKNOWN_MODE, + }, + mode_tree::{ + ModeStoreProvider, ModeStoreVec, SequenceModeTables, SequenceTableEntry, + SequenceTableMapTable, SequenceTablesMapValue, TargetModeTables, + }, + queue::GenericTargetedMessagingError, + request::{GenericMessage, MessageMetadata, RequestId}, + subsystem::{ModeCommandingResult, ModeTreeHelperState, SequenceExecutionHelperState}, + ComponentId, + }; + + #[derive(Debug)] + pub enum ExampleTargetId { + Target0 = 1, + Target1 = 2, + Target2 = 3, + } + + #[derive(Debug)] + pub enum ExampleMode { + Mode0 = 1, + Mode1 = 2, + Mode2 = 3, + } + + #[derive(Debug, Default)] + pub struct IsCommandableMock { + pub commandable_map: std::collections::HashMap, + } + + impl IsChildCommandable for IsCommandableMock { + fn is_commandable(&self, id: ComponentId) -> bool { + self.commandable_map.get(&id).copied().unwrap_or(true) + } + } + + pub struct SequenceExecutorTestbench { + pub sender: ModeReqSenderMock, + pub mode_store: ModeStoreVec, + pub seq_tables: SequenceModeTables, + pub execution_helper: SequenceExecutionHelper, + pub is_commandable_mock: IsCommandableMock, + } + + impl SequenceExecutorTestbench { + pub fn new() -> Self { + let mode_store = create_default_mode_store(); + let (seq_tables, _) = create_simple_sample_seq_tables(); + Self { + sender: ModeReqSenderMock::default(), + mode_store, + seq_tables, + execution_helper: SequenceExecutionHelper::new(), + is_commandable_mock: IsCommandableMock::default(), + } + } + + pub fn get_mode_table(&mut self, mode: ExampleMode) -> &mut SequenceTablesMapValue { + self.seq_tables.0.get_mut(&(mode as Mode)).unwrap() + } + + pub fn run(&mut self) -> Result { + self.execution_helper.run( + &self.seq_tables, + &self.sender, + &mut self.mode_store, + &self.is_commandable_mock, + ) + } + + fn check_run_is_no_op(&mut self) { + // Assure that no unexpected behaviour occurs. + assert_eq!( + self.execution_helper + .run( + &self.seq_tables, + &self.sender, + &mut self.mode_store, + &self.is_commandable_mock + ) + .unwrap(), + ModeCommandingResult::Done + ); + assert_eq!( + self.execution_helper.state(), + SequenceExecutionHelperState::Idle + ); + assert!(self.sender.requests.borrow().is_empty()); + } + + fn generic_checks_subsystem_md1_step0(&mut self, expected_req_id: RequestId) { + assert_eq!( + self.execution_helper.target_mode().unwrap(), + ExampleMode::Mode1 as Mode + ); + assert_eq!(self.sender.requests.borrow().len(), 2); + let req_0 = self.sender.requests.get_mut().pop_front().unwrap(); + assert_eq!(req_0.target_id, ExampleTargetId::Target0 as u64); + assert_eq!(req_0.request_id, expected_req_id); + assert_eq!( + req_0.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD1_ST0_TGT0_MODE, + forced: false + } + ); + let req_1 = self.sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req_1.target_id, ExampleTargetId::Target1 as u64); + assert_eq!( + req_1.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD1_ST0_TGT1_MODE, + forced: false + } + ); + } + fn generic_checks_subsystem_md1_step1(&mut self, expected_req_id: RequestId) { + assert_eq!( + self.execution_helper.target_mode().unwrap(), + ExampleMode::Mode1 as Mode + ); + assert_eq!(self.sender.requests.borrow().len(), 1); + let req_0 = self.sender.requests.get_mut().pop_front().unwrap(); + assert_eq!(req_0.target_id, ExampleTargetId::Target2 as u64); + assert_eq!(req_0.request_id, expected_req_id); + assert_eq!( + req_0.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD1_ST1_TGT2_MODE, + forced: false + } + ); + } + + fn generic_checks_subsystem_md0(&mut self, expected_req_id: RequestId) { + assert_eq!( + self.execution_helper.target_mode().unwrap(), + ExampleMode::Mode0 as Mode + ); + assert_eq!(self.execution_helper.current_sequence_index().unwrap(), 0); + assert_eq!(self.sender.requests.borrow().len(), 2); + let req_0 = self.sender.requests.get_mut().pop_front().unwrap(); + assert_eq!(req_0.target_id, ExampleTargetId::Target0 as u64); + assert_eq!(req_0.request_id, expected_req_id); + assert_eq!( + req_0.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD0_TGT0_MODE, + forced: false + } + ); + let req_1 = self.sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req_1.target_id, ExampleTargetId::Target1 as u64); + assert_eq!( + req_1.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD0_TGT1_MODE, + forced: false + } + ); + } + } + + fn create_default_mode_store() -> ModeStoreVec { + let mut mode_store = ModeStoreVec::default(); + mode_store.add_component(ExampleTargetId::Target0 as u64, UNKNOWN_MODE); + mode_store.add_component(ExampleTargetId::Target1 as u64, UNKNOWN_MODE); + mode_store.add_component(ExampleTargetId::Target2 as u64, UNKNOWN_MODE); + mode_store + } + + fn create_simple_sample_seq_tables() -> (SequenceModeTables, TargetModeTables) { + let mut seq_tables = SequenceModeTables::default(); + // Mode 0 - One step command + let mut table_val = SequenceTablesMapValue::new("MODE_0"); + let mut table_seq_0 = SequenceTableMapTable::new("MODE_0_SEQ_0"); + table_seq_0.add_entry(SequenceTableEntry::new( + "TARGET_0", + ExampleTargetId::Target0 as u64, + SUBSYSTEM_MD0_TGT0_MODE, + false, + )); + table_seq_0.add_entry(SequenceTableEntry::new( + "TARGET_1", + ExampleTargetId::Target1 as u64, + SUBSYSTEM_MD0_TGT1_MODE, + false, + )); + table_val.add_sequence_table(table_seq_0); + seq_tables.0.insert(ExampleMode::Mode0 as u32, table_val); + + // Mode 1 - Multi Step command + let mut table_val = SequenceTablesMapValue::new("MODE_1"); + let mut table_seq_0 = SequenceTableMapTable::new("MODE_1_SEQ_0"); + table_seq_0.add_entry(SequenceTableEntry::new( + "MD1_SEQ0_TGT0", + ExampleTargetId::Target0 as u64, + SUBSYSTEM_MD1_ST0_TGT0_MODE, + false, + )); + table_seq_0.add_entry(SequenceTableEntry::new( + "MD1_SEQ0_TGT1", + ExampleTargetId::Target1 as u64, + SUBSYSTEM_MD1_ST0_TGT1_MODE, + false, + )); + table_val.add_sequence_table(table_seq_0); + let mut table_seq_1 = SequenceTableMapTable::new("MODE_1_SEQ_1"); + table_seq_1.add_entry(SequenceTableEntry::new( + "MD1_SEQ1_TGT2", + ExampleTargetId::Target2 as u64, + SUBSYSTEM_MD1_ST1_TGT2_MODE, + false, + )); + table_val.add_sequence_table(table_seq_1); + seq_tables.0.insert(ExampleMode::Mode1 as u32, table_val); + + let mode_tables = TargetModeTables::default(); + // TODO: Write mode tables. + (seq_tables, mode_tables) + } + + pub struct SubsystemHelperTestbench { + pub sender: ModeReqSenderMock, + pub helper: SubsystemCommandingHelper, + pub is_commandable_mock: IsCommandableMock, + } + + impl SubsystemHelperTestbench { + pub fn new() -> Self { + let (sequence_tables, target_tables) = create_simple_sample_seq_tables(); + Self { + sender: ModeReqSenderMock::default(), + helper: SubsystemCommandingHelper::new( + create_default_mode_store(), + target_tables, + sequence_tables, + ), + is_commandable_mock: IsCommandableMock::default(), + } + } + + pub fn start_command_sequence( + &mut self, + mode: ExampleMode, + request_id: RequestId, + ) -> Result<(), StartSequenceError> { + self.helper.start_command_sequence(mode as Mode, request_id) + } + + pub fn send_announce_mode_cmd_to_children( + &mut self, + request_id: RequestId, + recursive: bool, + ) -> Result<(), GenericTargetedMessagingError> { + self.helper + .send_announce_mode_cmd_to_children(request_id, &self.sender, recursive) + } + + pub fn get_sequence_tables(&mut self, mode: ExampleMode) -> &mut SequenceTablesMapValue { + self.helper + .sequence_tables + .0 + .get_mut(&(mode as Mode)) + .unwrap() + } + + pub fn state_machine( + &mut self, + opt_reply: Option>, + ) -> Result { + self.helper + .state_machine(opt_reply, &self.sender, &self.is_commandable_mock) + } + + pub fn generic_checks_subsystem_md0(&mut self, expected_req_id: RequestId) { + assert_eq!(self.sender.requests.borrow().len(), 2); + let req0 = self.sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req0.request_id, expected_req_id); + assert_eq!(req0.target_id, ExampleTargetId::Target0 as ComponentId); + assert_eq!( + req0.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD0_TGT0_MODE, + forced: false + } + ); + + let req1 = self.sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req1.request_id, expected_req_id); + assert_eq!(req1.target_id, ExampleTargetId::Target1 as ComponentId); + assert_eq!( + req1.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD0_TGT1_MODE, + forced: false + } + ); + } + + pub fn generic_checks_subsystem_md1_step0(&mut self, expected_req_id: RequestId) { + assert_eq!(self.sender.requests.borrow().len(), 2); + let req0 = self.sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req0.request_id, expected_req_id); + assert_eq!(req0.target_id, ExampleTargetId::Target0 as ComponentId); + assert_eq!( + req0.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD1_ST0_TGT0_MODE, + forced: false + } + ); + + let req1 = self.sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req1.request_id, expected_req_id); + assert_eq!(req1.target_id, ExampleTargetId::Target1 as ComponentId); + assert_eq!( + req1.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD1_ST0_TGT1_MODE, + forced: false + } + ); + } + + pub fn generic_checks_subsystem_md1_step1(&mut self, expected_req_id: RequestId) { + assert_eq!(self.sender.requests.borrow().len(), 1); + let req0 = self.sender.requests.borrow_mut().pop_front().unwrap(); + assert_eq!(req0.request_id, expected_req_id); + assert_eq!(req0.target_id, ExampleTargetId::Target2 as ComponentId); + assert_eq!( + req0.request, + ModeRequest::SetMode { + mode_and_submode: SUBSYSTEM_MD1_ST1_TGT2_MODE, + forced: false + } + ); + } + } + + const SUBSYSTEM_MD0_TGT0_MODE: ModeAndSubmode = + ModeAndSubmode::new(ExampleMode::Mode0 as u32, 0); + const SUBSYSTEM_MD0_TGT1_MODE: ModeAndSubmode = + ModeAndSubmode::new(ExampleMode::Mode1 as u32, 0); + + const SUBSYSTEM_MD1_ST0_TGT0_MODE: ModeAndSubmode = + ModeAndSubmode::new(ExampleMode::Mode2 as u32, 0); + const SUBSYSTEM_MD1_ST0_TGT1_MODE: ModeAndSubmode = + ModeAndSubmode::new(ExampleMode::Mode0 as u32, 0); + const SUBSYSTEM_MD1_ST1_TGT2_MODE: ModeAndSubmode = + ModeAndSubmode::new(ExampleMode::Mode1 as u32, 0); + + #[test] + fn test_init_state() { + let execution_helper = SequenceExecutionHelper::new(); + assert_eq!(execution_helper.state(), SequenceExecutionHelperState::Idle); + assert!(!execution_helper.awaiting_success_check()); + assert!(execution_helper.target_mode().is_none()); + assert!(execution_helper.current_sequence_index().is_none()); + } + + #[test] + fn test_sequence_execution_helper_no_success_check() { + let mut tb = SequenceExecutorTestbench::new(); + let expected_req_id = 1; + tb.execution_helper + .load(ExampleMode::Mode0 as u32, expected_req_id, &tb.seq_tables) + .unwrap(); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Busy + ); + assert!(!tb.execution_helper.awaiting_success_check()); + assert_eq!( + tb.execution_helper.target_mode().unwrap(), + ExampleMode::Mode0 as Mode + ); + assert_eq!( + tb.run().expect("sequence exeecution helper run failure"), + ModeCommandingResult::Done + ); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Idle + ); + assert!(!tb.execution_helper.awaiting_success_check()); + tb.generic_checks_subsystem_md0(expected_req_id); + tb.check_run_is_no_op(); + } + + #[test] + fn test_sequence_execution_helper_with_success_check() { + let mut tb = SequenceExecutorTestbench::new(); + let mode0_table = tb.get_mode_table(ExampleMode::Mode0); + mode0_table.entries[0].entries[0].check_success = true; + mode0_table.entries[0].entries[1].check_success = true; + let expected_req_id = 1; + tb.execution_helper + .load(ExampleMode::Mode0 as u32, expected_req_id, &tb.seq_tables) + .unwrap(); + + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Busy + ); + assert!(!tb.execution_helper.awaiting_success_check()); + assert_eq!( + tb.execution_helper.target_mode().unwrap(), + ExampleMode::Mode0 as Mode + ); + assert_eq!( + tb.run().expect("sequence exeecution helper run failure"), + ModeCommandingResult::AwaitingSuccessCheck + ); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::AwaitingSuccessCheck + ); + // These are not cleared, even if the execution helper is already IDLE. This is okay for + // now. + assert!(tb.execution_helper.awaiting_success_check()); + tb.generic_checks_subsystem_md0(expected_req_id); + tb.execution_helper.confirm_sequence_done(); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Idle + ); + + tb.check_run_is_no_op(); + } + + #[test] + fn test_sequence_execution_helper_with_partial_check() { + let mut tb = SequenceExecutorTestbench::new(); + let mode0_table = tb.get_mode_table(ExampleMode::Mode0); + mode0_table.entries[0].entries[0].check_success = true; + let expected_req_id = 1; + tb.execution_helper + .load(ExampleMode::Mode0 as u32, expected_req_id, &tb.seq_tables) + .unwrap(); + + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Busy + ); + assert!(!tb.execution_helper.awaiting_success_check()); + assert_eq!( + tb.run().expect("sequence execution helper run failure"), + ModeCommandingResult::AwaitingSuccessCheck + ); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::AwaitingSuccessCheck + ); + // These are not cleared, even if the execution helper is already IDLE. This is okay for + // now. + assert!(tb.execution_helper.awaiting_success_check()); + tb.generic_checks_subsystem_md0(expected_req_id); + tb.execution_helper.confirm_sequence_done(); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Idle + ); + tb.check_run_is_no_op(); + } + + #[test] + fn test_sequence_execution_helper_multi_step_no_success_check() { + let mut tb = SequenceExecutorTestbench::new(); + let expected_req_id = 1; + tb.execution_helper + .load(ExampleMode::Mode1 as u32, expected_req_id, &tb.seq_tables) + .unwrap(); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Busy + ); + assert!(!tb.execution_helper.awaiting_success_check()); + assert_eq!( + tb.execution_helper.target_mode().unwrap(), + ExampleMode::Mode1 as Mode + ); + assert_eq!( + tb.run().expect("sequence execution helper run failure"), + ModeCommandingResult::StepDone + ); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Busy + ); + assert!(!tb.execution_helper.awaiting_success_check()); + tb.generic_checks_subsystem_md1_step0(expected_req_id); + assert_eq!(tb.execution_helper.current_sequence_index().unwrap(), 1); + + assert_eq!( + tb.run().expect("sequence execution helper run failure"), + ModeCommandingResult::Done + ); + tb.generic_checks_subsystem_md1_step1(expected_req_id); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Idle + ); + tb.check_run_is_no_op(); + } + + #[test] + fn test_sequence_execution_helper_multi_step_full_success_check() { + let mut tb = SequenceExecutorTestbench::new(); + let expected_req_id = 1; + tb.execution_helper + .load(ExampleMode::Mode1 as u32, expected_req_id, &tb.seq_tables) + .unwrap(); + let mode1_table = tb.get_mode_table(ExampleMode::Mode1); + mode1_table.entries[0].entries[0].check_success = true; + mode1_table.entries[0].entries[1].check_success = true; + mode1_table.entries[1].entries[0].check_success = true; + + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::Busy + ); + assert!(!tb.execution_helper.awaiting_success_check()); + assert_eq!( + tb.execution_helper.target_mode().unwrap(), + ExampleMode::Mode1 as Mode + ); + assert_eq!( + tb.run().expect("sequence execution helper run failure"), + ModeCommandingResult::AwaitingSuccessCheck + ); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::AwaitingSuccessCheck + ); + assert!(tb.execution_helper.awaiting_success_check()); + tb.generic_checks_subsystem_md1_step0(expected_req_id); + assert_eq!(tb.execution_helper.current_sequence_index().unwrap(), 0); + tb.execution_helper.confirm_sequence_done(); + + assert_eq!( + tb.run().expect("sequence execution helper run failure"), + ModeCommandingResult::AwaitingSuccessCheck + ); + assert_eq!( + tb.execution_helper.state(), + SequenceExecutionHelperState::AwaitingSuccessCheck + ); + assert!(tb.execution_helper.awaiting_success_check()); + assert_eq!(tb.execution_helper.current_sequence_index().unwrap(), 1); + tb.generic_checks_subsystem_md1_step1(expected_req_id); + tb.execution_helper.confirm_sequence_done(); + tb.check_run_is_no_op(); + } + + // TODO: Test subsystem commanding helper + #[test] + fn test_subsystem_helper_basic_state() { + let tb = SubsystemHelperTestbench::new(); + assert_eq!(tb.helper.state(), ModeTreeHelperState::Idle); + assert!(tb.helper.active_internal_request_id.is_none()); + assert_eq!(tb.helper.mode(), UNKNOWN_MODE_VAL); + assert!(tb.helper.request_id().is_none()); + } + + #[test] + fn test_subsystem_helper_announce_recursive() { + let mut tb = SubsystemHelperTestbench::new(); + let expected_req_id = 1; + tb.send_announce_mode_cmd_to_children(expected_req_id, true) + .unwrap(); + assert_eq!(tb.sender.requests.borrow().len(), 3); + let check_req = |req: ModeReqWrapper, target_id: ComponentId| { + assert_eq!(req.target_id, target_id); + assert_eq!(req.request_id, expected_req_id); + assert_eq!(req.request, ModeRequest::AnnounceModeRecursive); + }; + let req0 = tb.sender.requests.borrow_mut().pop_front().unwrap(); + check_req(req0, ExampleTargetId::Target0 as u64); + let req1 = tb.sender.requests.borrow_mut().pop_front().unwrap(); + check_req(req1, ExampleTargetId::Target1 as u64); + let req2 = tb.sender.requests.borrow_mut().pop_front().unwrap(); + check_req(req2, ExampleTargetId::Target2 as u64); + } + + #[test] + fn test_subsystem_helper_announce() { + let mut tb = SubsystemHelperTestbench::new(); + let expected_req_id = 1; + tb.send_announce_mode_cmd_to_children(expected_req_id, false) + .unwrap(); + assert_eq!(tb.sender.requests.borrow().len(), 3); + let check_req = |req: ModeReqWrapper, target_id: ComponentId| { + assert_eq!(req.target_id, target_id); + assert_eq!(req.request_id, expected_req_id); + assert_eq!(req.request, ModeRequest::AnnounceMode); + }; + let req0 = tb.sender.requests.borrow_mut().pop_front().unwrap(); + check_req(req0, ExampleTargetId::Target0 as u64); + let req1 = tb.sender.requests.borrow_mut().pop_front().unwrap(); + check_req(req1, ExampleTargetId::Target1 as u64); + let req2 = tb.sender.requests.borrow_mut().pop_front().unwrap(); + check_req(req2, ExampleTargetId::Target2 as u64); + } + + #[test] + fn test_subsystem_helper_cmd_mode0_no_success_checks() { + let mut tb = SubsystemHelperTestbench::new(); + let expected_req_id = 1; + tb.start_command_sequence(ExampleMode::Mode0, expected_req_id) + .unwrap(); + assert_eq!(tb.helper.request_id().unwrap(), 1); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.sender.requests.borrow().len(), 0); + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::Done) + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::TargetKeeping); + assert_eq!(tb.helper.mode(), ExampleMode::Mode0 as Mode); + tb.generic_checks_subsystem_md0(tb.helper.internal_request_id().unwrap()); + // FSM call should be a no-op. + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::TargetKeeping + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::TargetKeeping); + assert_eq!(tb.helper.mode(), ExampleMode::Mode0 as Mode); + } + + #[test] + fn test_subsystem_helper_cmd_mode1_no_success_checks() { + let mut tb = SubsystemHelperTestbench::new(); + let expected_req_id = 1; + tb.start_command_sequence(ExampleMode::Mode1, expected_req_id) + .unwrap(); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.sender.requests.borrow().len(), 0); + // Need to cache this before it is incremented, because it is incremented + // immediately in the state machine (no reply checking) + let expected_req_id = tb.helper.internal_request_id().unwrap(); + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::StepDone) + ); + // Assert that this was already incremented because no reply checking is necessary. + assert_eq!( + tb.helper.internal_request_id().unwrap(), + expected_req_id + 1 + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.helper.mode(), UNKNOWN_MODE_VAL); + tb.generic_checks_subsystem_md1_step0(expected_req_id); + // Second commanding step. + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::Done) + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::TargetKeeping); + assert_eq!(tb.helper.mode(), ExampleMode::Mode1 as Mode); + tb.generic_checks_subsystem_md1_step1(tb.helper.internal_request_id().unwrap()); + + // FSM call should be a no-op. + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::TargetKeeping + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::TargetKeeping); + assert_eq!(tb.helper.mode(), ExampleMode::Mode1 as Mode); + } + + #[test] + fn test_subsystem_helper_cmd_mode0_with_success_checks() { + let mut tb = SubsystemHelperTestbench::new(); + let expected_req_id = 1; + let seq_tables = tb.get_sequence_tables(ExampleMode::Mode0); + seq_tables.entries[0].entries[0].check_success = true; + seq_tables.entries[0].entries[1].check_success = true; + tb.start_command_sequence(ExampleMode::Mode0, expected_req_id) + .unwrap(); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.sender.requests.borrow().len(), 0); + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::AwaitingSuccessCheck) + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.helper.mode(), UNKNOWN_MODE_VAL); + tb.generic_checks_subsystem_md0(tb.helper.internal_request_id().unwrap()); + let mode_reply_ok_0 = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target0 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD0_TGT0_MODE), + ); + let mode_reply_ok_1 = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target1 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD0_TGT1_MODE), + ); + // One success reply still expected. + assert_eq!( + tb.state_machine(Some(mode_reply_ok_0)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::AwaitingSuccessCheck) + ); + assert_eq!( + tb.state_machine(Some(mode_reply_ok_1)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::Done) + ); + + // FSM call should be a no-op. + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::TargetKeeping + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::TargetKeeping); + assert_eq!(tb.helper.mode(), ExampleMode::Mode0 as Mode); + } + + #[test] + fn test_subsystem_helper_cmd_mode1_with_success_checks() { + let mut tb = SubsystemHelperTestbench::new(); + let expected_req_id = 1; + let seq_tables = tb.get_sequence_tables(ExampleMode::Mode1); + seq_tables.entries[0].entries[0].check_success = true; + seq_tables.entries[0].entries[1].check_success = true; + seq_tables.entries[1].entries[0].check_success = true; + tb.start_command_sequence(ExampleMode::Mode1, expected_req_id) + .unwrap(); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.sender.requests.borrow().len(), 0); + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::AwaitingSuccessCheck) + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.helper.mode(), UNKNOWN_MODE_VAL); + tb.generic_checks_subsystem_md1_step0(tb.helper.internal_request_id().unwrap()); + let mode_reply_ok_0 = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target0 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD0_TGT0_MODE), + ); + let mode_reply_ok_1 = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target1 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD0_TGT1_MODE), + ); + // One success reply still expected. + assert_eq!( + tb.state_machine(Some(mode_reply_ok_0)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::AwaitingSuccessCheck) + ); + assert_eq!( + tb.state_machine(Some(mode_reply_ok_1)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::StepDone) + ); + + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::AwaitingSuccessCheck) + ); + let mode_reply_ok = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target2 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD1_ST1_TGT2_MODE), + ); + assert_eq!( + tb.state_machine(Some(mode_reply_ok)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::Done) + ); + + // FSM call should be a no-op. + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::TargetKeeping + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::TargetKeeping); + assert_eq!(tb.helper.mode(), ExampleMode::Mode1 as Mode); + } + + #[test] + fn test_subsystem_helper_cmd_mode1_with_partial_success_checks_0() { + let mut tb = SubsystemHelperTestbench::new(); + let expected_req_id = 1; + let seq_tables = tb.get_sequence_tables(ExampleMode::Mode1); + seq_tables.entries[0].entries[0].check_success = true; + seq_tables.entries[0].entries[1].check_success = false; + seq_tables.entries[1].entries[0].check_success = false; + tb.start_command_sequence(ExampleMode::Mode1, expected_req_id) + .unwrap(); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.sender.requests.borrow().len(), 0); + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::AwaitingSuccessCheck) + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.helper.mode(), UNKNOWN_MODE_VAL); + tb.generic_checks_subsystem_md1_step0(tb.helper.internal_request_id().unwrap()); + let mode_reply_ok_0 = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target0 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD0_TGT0_MODE), + ); + let mode_reply_ok_1 = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target1 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD0_TGT1_MODE), + ); + // One success reply still expected. + assert_eq!( + tb.state_machine(Some(mode_reply_ok_1)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::AwaitingSuccessCheck) + ); + assert_eq!( + tb.state_machine(Some(mode_reply_ok_0)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::StepDone) + ); + + // Inserting the reply makes no difference: This call completes the sequence commanding. + let mode_reply_ok = GenericMessage::new( + MessageMetadata::new(expected_req_id, ExampleTargetId::Target2 as ComponentId), + ModeReply::ModeInfo(SUBSYSTEM_MD1_ST1_TGT2_MODE), + ); + assert_eq!( + tb.state_machine(Some(mode_reply_ok)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::Done) + ); + // The internal request ID is still cached. + tb.generic_checks_subsystem_md1_step1(tb.helper.internal_request_id().unwrap()); + + // FSM call should be a no-op. + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::TargetKeeping + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::TargetKeeping); + assert_eq!(tb.helper.mode(), ExampleMode::Mode1 as Mode); + } + + #[test] + fn test_subsystem_helper_cmd_mode1_with_partial_success_checks_1() { + let mut tb = SubsystemHelperTestbench::new(); + let expected_req_id = 1; + let seq_tables = tb.get_sequence_tables(ExampleMode::Mode1); + seq_tables.entries[0].entries[0].check_success = true; + seq_tables.entries[0].entries[1].check_success = false; + seq_tables.entries[1].entries[0].check_success = false; + tb.start_command_sequence(ExampleMode::Mode1, expected_req_id) + .unwrap(); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.sender.requests.borrow().len(), 0); + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::AwaitingSuccessCheck) + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::ModeCommanding); + assert_eq!(tb.helper.mode(), UNKNOWN_MODE_VAL); + tb.generic_checks_subsystem_md1_step0(tb.helper.internal_request_id().unwrap()); + let mode_reply_ok_0 = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target0 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD0_TGT0_MODE), + ); + let mode_reply_ok_1 = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target1 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD0_TGT1_MODE), + ); + // This completes the step, so the next FSM call will perform the next step + // in sequence commanding. + assert_eq!( + tb.state_machine(Some(mode_reply_ok_0)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::StepDone) + ); + assert_eq!( + tb.state_machine(Some(mode_reply_ok_1)).unwrap(), + SubsystemHelperResult::ModeCommanding(ModeCommandingResult::Done) + ); + + // Inserting the reply makes no difference: Sequence command is done and target keeping + // is performed. + let mode_reply_ok = GenericMessage::new( + MessageMetadata::new( + tb.helper.internal_request_id().unwrap(), + ExampleTargetId::Target2 as ComponentId, + ), + ModeReply::ModeInfo(SUBSYSTEM_MD1_ST1_TGT2_MODE), + ); + assert_eq!( + tb.state_machine(Some(mode_reply_ok)).unwrap(), + SubsystemHelperResult::TargetKeeping + ); + // The internal request ID is still cached. + tb.generic_checks_subsystem_md1_step1(tb.helper.internal_request_id().unwrap()); + + // FSM call should be a no-op. + assert_eq!( + tb.state_machine(None).unwrap(), + SubsystemHelperResult::TargetKeeping + ); + assert_eq!(tb.helper.state(), ModeTreeHelperState::TargetKeeping); + assert_eq!(tb.helper.mode(), ExampleMode::Mode1 as Mode); + } +} diff --git a/satrs/src/tmtc/mod.rs b/satrs/src/tmtc/mod.rs index 498c1bd..f2993ce 100644 --- a/satrs/src/tmtc/mod.rs +++ b/satrs/src/tmtc/mod.rs @@ -14,6 +14,7 @@ use crate::{ }; #[cfg(feature = "std")] pub use alloc_mod::*; +use core::fmt::Debug; #[cfg(feature = "alloc")] use downcast_rs::{impl_downcast, Downcast}; use spacepackets::{ @@ -170,7 +171,7 @@ where } /// Helper trait for any generic (static) store which allows storing raw or CCSDS packets. -pub trait CcsdsPacketPool { +pub trait CcsdsPacketPool: Debug { fn add_ccsds_tc(&mut self, _: &SpHeader, tc_raw: &[u8]) -> Result { self.add_raw_tc(tc_raw) } @@ -190,7 +191,7 @@ pub trait PusTmPool { } /// Generic trait for any sender component able to send packets stored inside a pool structure. -pub trait PacketInPoolSender: Send { +pub trait PacketInPoolSender: Debug + Send { fn send_packet( &self, sender_id: ComponentId, @@ -235,7 +236,7 @@ pub mod std_mod { /// Newtype wrapper around the [SharedStaticMemoryPool] to enable extension helper traits on /// top of the regular shared memory pool API. - #[derive(Clone)] + #[derive(Debug, Clone)] pub struct SharedPacketPool(pub SharedStaticMemoryPool); impl SharedPacketPool { @@ -287,7 +288,6 @@ pub mod std_mod { } } - #[cfg(feature = "std")] impl PacketSenderRaw for mpsc::Sender { type Error = GenericSendError; @@ -297,7 +297,6 @@ pub mod std_mod { } } - #[cfg(feature = "std")] impl PacketSenderRaw for mpsc::SyncSender { type Error = GenericSendError; @@ -362,7 +361,7 @@ pub mod std_mod { /// This is the primary structure used to send packets stored in a dedicated memory pool /// structure. - #[derive(Clone)] + #[derive(Debug, Clone)] pub struct PacketSenderWithSharedPool< Sender: PacketInPoolSender = mpsc::SyncSender, PacketPool: CcsdsPacketPool = SharedPacketPool, diff --git a/satrs/tests/mode_tree.rs b/satrs/tests/mode_tree.rs index 17f9836..06c1b2f 100644 --- a/satrs/tests/mode_tree.rs +++ b/satrs/tests/mode_tree.rs @@ -1,103 +1,826 @@ use core::cell::Cell; -use std::{println, sync::mpsc}; - -use satrs::mode::{ - ModeError, ModeProvider, ModeReplyReceiver, ModeReplySender, ModeRequestHandler, - ModeRequestHandlerMpscBounded, ModeRequestReceiver, ModeRequestorAndHandlerMpscBounded, - ModeRequestorBoundedMpsc, +use num_enum::TryFromPrimitive; +use satrs::dev_mgmt::{ + DevManagerCommandingHelper, DevManagerHelperResult, TransparentDevManagerHook, +}; +use satrs::mode::{ + Mode, ModeError, ModeProvider, ModeReplyReceiver, ModeReplySender, ModeRequestHandler, + ModeRequestHandlerMpscBounded, ModeRequestReceiver, ModeRequestorAndHandlerMpscBounded, + ModeRequestorOneChildBoundedMpsc, INVALID_MODE, UNKNOWN_MODE, +}; +use satrs::mode_tree::{ + connect_mode_nodes, ModeChild, ModeNode, ModeParent, ModeStoreProvider, SequenceTableEntry, + SequenceTableMapTable, TargetTableEntry, +}; +use satrs::mode_tree::{SequenceTablesMapValue, TargetTablesMapValue}; +use satrs::request::{MessageMetadata, RequestId}; +use satrs::res_code::ResultU16; +use satrs::subsystem::{ + IsChildCommandable, ModeCommandingResult, ModeTreeHelperError, ModeTreeHelperState, + StartSequenceError, SubsystemCommandingHelper, SubsystemHelperResult, }; -use satrs::request::MessageMetadata; use satrs::{ mode::{ModeAndSubmode, ModeReply, ModeRequest}, queue::GenericTargetedMessagingError, request::GenericMessage, ComponentId, }; -use std::string::{String, ToString}; +use std::borrow::{Borrow, BorrowMut}; +use std::cell::RefCell; +use std::collections::{HashMap, VecDeque}; +use std::convert::Infallible; +use std::{println, sync::mpsc}; +pub enum DefaultMode { + OFF = 0, + ON = 1, + NORMAL = 2, +} + +#[derive(Debug)] +pub enum AcsMode { + OFF = 0, + SAFE = 1, + IDLE = 2, +} + +#[derive(Debug, TryFromPrimitive)] +#[repr(u64)] pub enum TestComponentId { - Device1 = 1, - Device2 = 2, - Assembly = 3, - PusModeService = 4, + MagnetometerDevice0 = 1, + MagnetometerDevice1 = 2, + MagnetorquerDevice = 5, + ReactionWheelDevice = 6, + StartrackerDevice = 7, + MgtDevManager = 8, + ReactionWheelAssembly = 10, + MagnetometerAssembly = 11, + AcsController = 14, + AcsSubsystem = 15, + PusModeService = 16, +} + +pub type RequestSenderType = mpsc::SyncSender>; +pub type ReplySenderType = mpsc::SyncSender>; + +#[derive(Default, Debug)] +pub struct ModeRequestHandlerMock { + pub id: ComponentId, + get_mode_calls: RefCell, + start_transition_calls: VecDeque<(MessageMetadata, ModeAndSubmode)>, + announce_mode_calls: RefCell>, + handle_mode_info_calls: VecDeque<(MessageMetadata, ModeAndSubmode)>, + handle_mode_reached_calls: RefCell>>, + send_mode_reply_calls: RefCell>, +} + +impl ModeRequestHandlerMock { + pub fn new(id: ComponentId) -> Self { + Self { + id, + ..Default::default() + } + } +} + +impl ModeRequestHandlerMock { + pub fn clear(&mut self) { + self.get_mode_calls.replace(0); + self.start_transition_calls.clear(); + self.announce_mode_calls.borrow_mut().clear(); + self.handle_mode_reached_calls.borrow_mut().clear(); + self.handle_mode_info_calls.clear(); + self.send_mode_reply_calls.borrow_mut().clear(); + } + pub fn mode_messages_received(&self) -> usize { + *self.get_mode_calls.borrow() + + self.start_transition_calls.borrow().len() + + self.announce_mode_calls.borrow().len() + + self.handle_mode_info_calls.borrow().len() + + self.handle_mode_reached_calls.borrow().len() + + self.send_mode_reply_calls.borrow().len() + } +} + +impl ModeProvider for ModeRequestHandlerMock { + fn mode_and_submode(&self) -> ModeAndSubmode { + *self.get_mode_calls.borrow_mut() += 1; + INVALID_MODE + } +} + +impl ModeRequestHandler for ModeRequestHandlerMock { + type Error = Infallible; + + fn start_transition( + &mut self, + requestor: MessageMetadata, + mode_and_submode: ModeAndSubmode, + _forced: bool, + ) -> Result<(), Self::Error> { + self.start_transition_calls + .push_back((requestor, mode_and_submode)); + Ok(()) + } + + fn announce_mode(&self, requestor_info: Option, recursive: bool) { + self.announce_mode_calls + .borrow_mut() + .push_back(AnnounceModeInfo { + requestor: requestor_info, + recursive, + }); + } + + fn handle_mode_reached( + &mut self, + requestor_info: Option, + ) -> Result<(), Self::Error> { + self.handle_mode_reached_calls + .borrow_mut() + .push_back(requestor_info); + Ok(()) + } + + fn handle_mode_info( + &mut self, + requestor_info: MessageMetadata, + info: ModeAndSubmode, + ) -> Result<(), Self::Error> { + self.handle_mode_info_calls + .push_back((requestor_info, info)); + todo!() + } + + fn send_mode_reply( + &self, + requestor_info: MessageMetadata, + reply: ModeReply, + ) -> Result<(), Self::Error> { + self.send_mode_reply_calls + .borrow_mut() + .push_back((requestor_info, reply)); + Ok(()) + } +} + +#[derive(Debug)] +pub struct ModeReplyHandlerMock { + pub id: ComponentId, + mode_info_messages: VecDeque<(MessageMetadata, ModeAndSubmode)>, + mode_reply_messages: VecDeque<(MessageMetadata, ModeAndSubmode)>, + cant_reach_mode_messages: VecDeque<(MessageMetadata, ResultU16)>, + wrong_mode_messages: VecDeque<(MessageMetadata, ModeAndSubmode, ModeAndSubmode)>, +} + +impl ModeReplyHandlerMock { + pub fn new(id: ComponentId) -> Self { + Self { + id, + mode_info_messages: Default::default(), + mode_reply_messages: Default::default(), + cant_reach_mode_messages: Default::default(), + wrong_mode_messages: Default::default(), + } + } + + pub fn num_of_received_mode_replies(&self) -> usize { + self.mode_info_messages.len() + + self.mode_reply_messages.len() + + self.cant_reach_mode_messages.len() + + self.wrong_mode_messages.len() + } + + pub fn handle_mode_reply(&mut self, request: &GenericMessage) { + match request.message { + ModeReply::ModeInfo(mode_and_submode) => { + self.mode_info_messages + .push_back((request.requestor_info, mode_and_submode)); + } + ModeReply::ModeReply(mode_and_submode) => { + self.mode_reply_messages + .push_back((request.requestor_info, mode_and_submode)); + } + ModeReply::CantReachMode(result_u16) => { + self.cant_reach_mode_messages + .push_back((request.requestor_info, result_u16)); + } + ModeReply::WrongMode { expected, reached } => { + self.wrong_mode_messages + .push_back((request.requestor_info, expected, reached)); + } + } + } } struct PusModeService { pub request_id_counter: Cell, - pub mode_node: ModeRequestorBoundedMpsc, + pub mode_reply_mock: ModeReplyHandlerMock, + mode_node: ModeRequestorOneChildBoundedMpsc, } impl PusModeService { - pub fn send_announce_mode_cmd_to_assy(&self) { + pub fn new(init_req_count: u32, mode_node: ModeRequestorOneChildBoundedMpsc) -> Self { + Self { + request_id_counter: Cell::new(init_req_count), + mode_reply_mock: ModeReplyHandlerMock::new( + TestComponentId::PusModeService as ComponentId, + ), + mode_node, + } + } + pub fn run(&mut self) { + while let Some(reply) = self.mode_node.try_recv_mode_reply().unwrap() { + self.mode_reply_mock.handle_mode_reply(&reply); + } + } + + pub fn announce_modes_recursively(&self) { self.mode_node .send_mode_request( self.request_id_counter.get(), - TestComponentId::Assembly as ComponentId, + TestComponentId::AcsSubsystem as ComponentId, ModeRequest::AnnounceModeRecursive, ) .unwrap(); self.request_id_counter .replace(self.request_id_counter.get() + 1); } + + pub fn send_mode_cmd(&self, mode: ModeAndSubmode) -> RequestId { + let request_id = self.request_id_counter.get(); + self.mode_node + .send_mode_request( + request_id, + TestComponentId::AcsSubsystem as ComponentId, + ModeRequest::SetMode { + mode_and_submode: mode, + forced: false, + }, + ) + .unwrap(); + self.request_id_counter.replace(request_id + 1); + request_id + } } -struct TestDevice { - pub name: String, - pub mode_node: ModeRequestHandlerMpscBounded, - pub mode_and_submode: ModeAndSubmode, +impl ModeNode for PusModeService { + fn id(&self) -> ComponentId { + TestComponentId::PusModeService as ComponentId + } } -impl TestDevice { - pub fn run(&mut self) { - self.check_mode_requests().expect("mode messaging error"); +impl ModeParent for PusModeService { + type Sender = RequestSenderType; + + fn add_mode_child(&mut self, id: ComponentId, request_sender: Self::Sender) { + self.mode_node.add_message_target(id, request_sender); + } +} + +#[derive(Debug, Default)] +struct IsCommandableDummy {} + +impl IsChildCommandable for IsCommandableDummy { + fn is_commandable(&self, _id: ComponentId) -> bool { + true + } +} + +struct AcsSubsystem { + pub mode_node: ModeRequestorAndHandlerMpscBounded, + pub mode_requestor_info: Option, + pub target_mode_and_submode: Option, + pub subsystem_helper: SubsystemCommandingHelper, + is_commandable_dummy: IsCommandableDummy, + pub mode_req_mock: ModeRequestHandlerMock, + pub mode_reply_mock: ModeReplyHandlerMock, +} + +impl AcsSubsystem { + pub fn id() -> ComponentId { + TestComponentId::AcsSubsystem as u64 } - pub fn check_mode_requests(&mut self) -> Result<(), ModeError> { - if let Some(request) = self.mode_node.try_recv_mode_request()? { - self.handle_mode_request(request)? + pub fn new(mode_node: ModeRequestorAndHandlerMpscBounded) -> Self { + Self { + mode_node, + mode_requestor_info: None, + target_mode_and_submode: None, + is_commandable_dummy: IsCommandableDummy::default(), + subsystem_helper: SubsystemCommandingHelper::default(), + mode_req_mock: ModeRequestHandlerMock::new(Self::id()), + mode_reply_mock: ModeReplyHandlerMock::new(Self::id()), + } + } + + pub fn get_num_mode_requests(&mut self) -> usize { + self.mode_req_mock.mode_messages_received() + } + + pub fn handle_subsystem_helper_result( + &mut self, + result: Result, + ) { + match result { + Ok(result) => { + if let SubsystemHelperResult::ModeCommanding(ModeCommandingResult::Done) = result { + self.handle_mode_reached(self.mode_requestor_info) + .expect("mode reply handling failed"); + } + } + Err(error) => match error { + ModeTreeHelperError::Message(_generic_targeted_messaging_error) => { + panic!("messaging error") + } + ModeTreeHelperError::CurrentModeNotInTargetTable(_) => panic!("mode not found"), + ModeTreeHelperError::ModeCommmandFailure { seq_table_index: _ } => { + // TODO: Cache the command failure. + } + ModeTreeHelperError::TargetKeepingViolation { fallback_mode } => { + if let Some(fallback_mode) = fallback_mode { + self.subsystem_helper + .start_command_sequence(fallback_mode, 0) + .unwrap(); + } + } + }, + } + } + + pub fn run(&mut self) { + while let Some(request) = self.mode_node.try_recv_mode_request().unwrap() { + self.handle_mode_request(request) + .expect("mode messaging error"); + } + + let mut received_reply = false; + while let Some(mode_reply) = self.mode_node.try_recv_mode_reply().unwrap() { + received_reply = true; + self.mode_reply_mock.handle_mode_reply(&mode_reply); + let result = self.subsystem_helper.state_machine( + Some(mode_reply), + &self.mode_node, + &self.is_commandable_dummy, + ); + self.handle_subsystem_helper_result(result); + } + if !received_reply { + let result = self.subsystem_helper.state_machine( + None, + &self.mode_node, + &self.is_commandable_dummy, + ); + self.handle_subsystem_helper_result(result); + } + } + + pub fn add_target_and_sequence_table( + &mut self, + mode: Mode, + target_table_val: TargetTablesMapValue, + sequence_table_val: SequenceTablesMapValue, + ) { + self.subsystem_helper.add_target_and_sequence_table( + mode, + target_table_val, + sequence_table_val, + ); + } +} + +impl ModeNode for AcsSubsystem { + fn id(&self) -> ComponentId { + Self::id() + } +} + +impl ModeParent for AcsSubsystem { + type Sender = RequestSenderType; + + fn add_mode_child(&mut self, id: ComponentId, request_sender: RequestSenderType) { + self.subsystem_helper.add_mode_child(id, UNKNOWN_MODE); + self.mode_node.add_request_target(id, request_sender); + } +} + +impl ModeChild for AcsSubsystem { + type Sender = ReplySenderType; + + fn add_mode_parent(&mut self, id: ComponentId, reply_sender: ReplySenderType) { + self.mode_node.add_reply_target(id, reply_sender); + } +} + +impl ModeProvider for AcsSubsystem { + fn mode_and_submode(&self) -> ModeAndSubmode { + ModeAndSubmode::new(self.subsystem_helper.mode(), 0) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum SubsystemModeError { + #[error("messaging error: {0:?}")] + Mode(#[from] ModeError), + #[error("start sequence error: {0}")] + StartError(#[from] StartSequenceError), +} + +impl ModeRequestHandler for AcsSubsystem { + type Error = SubsystemModeError; + + fn start_transition( + &mut self, + requestor: MessageMetadata, + mode_and_submode: ModeAndSubmode, + forced: bool, + ) -> Result<(), Self::Error> { + if !forced && self.subsystem_helper.state() == ModeTreeHelperState::ModeCommanding { + return Err(ModeError::Busy.into()); + } + self.mode_requestor_info = Some(requestor); + self.target_mode_and_submode = Some(mode_and_submode); + self.mode_req_mock + .start_transition(requestor, mode_and_submode, forced) + .unwrap(); + println!("subsystem: mode req with ID {}", requestor.request_id()); + self.subsystem_helper + .start_command_sequence(mode_and_submode.mode(), requestor.request_id())?; + Ok(()) + } + + fn announce_mode(&self, requestor_info: Option, recursive: bool) { + println!( + "TestAssembly: Announcing mode (recursively: {}): {:?}", + recursive, + self.subsystem_helper.mode() + ); + let request_id = requestor_info.map_or(0, |info| info.request_id()); + self.subsystem_helper + .send_announce_mode_cmd_to_children(request_id, &self.mode_node, recursive) + .expect("sending mode request failed"); + self.mode_req_mock.announce_mode(requestor_info, recursive); + } + + fn handle_mode_reached( + &mut self, + requestor_info: Option, + ) -> Result<(), Self::Error> { + self.mode_req_mock + .handle_mode_reached(requestor_info) + .unwrap(); + if let Some(requestor) = requestor_info { + self.send_mode_reply( + requestor, + ModeReply::ModeReply(ModeAndSubmode::new(self.subsystem_helper.mode(), 0)), + )?; + } + Ok(()) + } + + fn handle_mode_info( + &mut self, + requestor_info: MessageMetadata, + info: ModeAndSubmode, + ) -> Result<(), Self::Error> { + self.mode_req_mock + .handle_mode_info(requestor_info, info) + .unwrap(); + // TODO: Need to check whether mode table execution is finished. + // This works by checking the children modes received through replies against the + // mode table after all transition tables were executed. + Ok(()) + } + + fn send_mode_reply( + &self, + requestor_info: MessageMetadata, + reply: ModeReply, + ) -> Result<(), Self::Error> { + self.mode_req_mock + .send_mode_reply(requestor_info, reply) + .unwrap(); + self.mode_node + .send_mode_reply(requestor_info, reply) + .map_err(ModeError::Send)?; + Ok(()) + } +} + +struct MgmAssembly { + pub mode_node: ModeRequestorAndHandlerMpscBounded, + pub mode_requestor_info: Option, + pub mode_and_submode: ModeAndSubmode, + pub commanding_helper: DevManagerCommandingHelper, + pub mode_req_mock: ModeRequestHandlerMock, + pub mode_reply_mock: ModeReplyHandlerMock, +} + +impl MgmAssembly { + pub fn id() -> ComponentId { + TestComponentId::MagnetometerAssembly as u64 + } + pub fn new(mode_node: ModeRequestorAndHandlerMpscBounded) -> Self { + Self { + mode_node, + mode_requestor_info: None, + mode_and_submode: UNKNOWN_MODE, + commanding_helper: DevManagerCommandingHelper::new(TransparentDevManagerHook::default()), + mode_req_mock: ModeRequestHandlerMock::new(Self::id()), + mode_reply_mock: ModeReplyHandlerMock::new(Self::id()), + } + } + + pub fn run(&mut self) { + self.check_mode_requests().expect("mode messaging error"); + self.check_mode_replies().expect("mode messaging error"); + } + + pub fn get_num_mode_requests(&mut self) -> usize { + self.mode_req_mock.mode_messages_received() + } + + pub fn check_mode_requests(&mut self) -> Result<(), GenericTargetedMessagingError> { + while let Some(request) = self.mode_node.try_recv_mode_request()? { + self.handle_mode_request(request).unwrap(); + } + Ok(()) + } + + pub fn check_mode_replies(&mut self) -> Result<(), ModeError> { + while let Some(reply_and_id) = self.mode_node.try_recv_mode_reply()? { + self.mode_reply_mock.handle_mode_reply(&reply_and_id); + match self.commanding_helper.handle_mode_reply(&reply_and_id) { + Ok(result) => { + if let DevManagerHelperResult::ModeCommandingDone(context) = result { + // Complete the mode command. + self.mode_and_submode = context.target_mode; + self.handle_mode_reached(self.mode_requestor_info)?; + } + } + Err(err) => match err { + satrs::dev_mgmt::DevManagerHelperError::ChildNotInStore => todo!(), + }, + } } Ok(()) } } -impl ModeProvider for TestDevice { +impl ModeNode for MgmAssembly { + fn id(&self) -> ComponentId { + Self::id() + } +} +impl ModeParent for MgmAssembly { + type Sender = RequestSenderType; + + fn add_mode_child(&mut self, id: ComponentId, request_sender: RequestSenderType) { + self.mode_node.add_request_target(id, request_sender); + self.commanding_helper.add_mode_child(id, UNKNOWN_MODE); + } +} + +impl ModeChild for MgmAssembly { + type Sender = ReplySenderType; + + fn add_mode_parent(&mut self, id: ComponentId, reply_sender: ReplySenderType) { + self.mode_node.add_reply_target(id, reply_sender); + } +} + +impl ModeProvider for MgmAssembly { fn mode_and_submode(&self) -> ModeAndSubmode { self.mode_and_submode } } -impl ModeRequestHandler for TestDevice { +impl ModeRequestHandler for MgmAssembly { + type Error = ModeError; + fn start_transition( + &mut self, + requestor: MessageMetadata, + mode_and_submode: ModeAndSubmode, + forced: bool, + ) -> Result<(), Self::Error> { + // Always accept forced commands and commands to mode OFF. + if self.commanding_helper.target_mode().is_some() + && !forced + && mode_and_submode.mode() != DefaultMode::OFF as u32 + { + return Err(ModeError::Busy); + } + self.mode_requestor_info = Some(requestor); + self.mode_req_mock + .start_transition(requestor, mode_and_submode, forced) + .unwrap(); + self.commanding_helper.send_mode_cmd_to_all_children( + requestor.request_id(), + mode_and_submode, + forced, + &self.mode_node, + )?; + Ok(()) + } + + fn announce_mode(&self, requestor_info: Option, recursive: bool) { + println!( + "TestAssembly: Announcing mode (recursively: {}): {:?}", + recursive, self.mode_and_submode + ); + let request_id = requestor_info.map_or(0, |info| info.request_id()); + self.commanding_helper + .send_announce_mode_cmd_to_children(request_id, &self.mode_node, recursive) + .expect("sending mode request failed"); + self.mode_req_mock.announce_mode(requestor_info, recursive); + } + + fn handle_mode_reached( + &mut self, + mode_requestor: Option, + ) -> Result<(), Self::Error> { + if let Some(requestor) = mode_requestor { + self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode))?; + } + self.mode_req_mock + .handle_mode_reached(mode_requestor) + .unwrap(); + Ok(()) + } + + fn handle_mode_info( + &mut self, + requestor_info: MessageMetadata, + info: ModeAndSubmode, + ) -> Result<(), Self::Error> { + self.mode_req_mock + .handle_mode_info(requestor_info, info) + .unwrap(); + // TODO: A proper assembly must reach to mode changes of its children.. + Ok(()) + } + + fn send_mode_reply( + &self, + requestor: MessageMetadata, + reply: ModeReply, + ) -> Result<(), Self::Error> { + self.mode_node.send_mode_reply(requestor, reply)?; + self.mode_req_mock + .send_mode_reply(requestor, reply) + .unwrap(); + Ok(()) + } +} + +struct DeviceManager { + name: &'static str, + pub id: ComponentId, + pub commanding_helper: DevManagerCommandingHelper, + pub mode_node: ModeRequestorAndHandlerMpscBounded, + pub mode_requestor_info: Option, + pub mode_and_submode: ModeAndSubmode, + pub mode_req_mock: ModeRequestHandlerMock, + pub mode_reply_mock: ModeReplyHandlerMock, +} + +impl DeviceManager { + pub fn new( + name: &'static str, + id: ComponentId, + mode_node: ModeRequestorAndHandlerMpscBounded, + ) -> Self { + Self { + name, + id, + mode_node, + mode_requestor_info: None, + commanding_helper: DevManagerCommandingHelper::new(TransparentDevManagerHook::default()), + mode_and_submode: UNKNOWN_MODE, + mode_req_mock: ModeRequestHandlerMock::new(id), + mode_reply_mock: ModeReplyHandlerMock::new(id), + } + } + + pub fn get_num_mode_requests(&mut self) -> usize { + self.mode_req_mock.mode_messages_received() + } + + pub fn run(&mut self) { + self.check_mode_requests().expect("mode messaging error"); + self.check_mode_replies().expect("mode reply error"); + } + + pub fn check_mode_requests(&mut self) -> Result<(), ModeError> { + while let Some(request) = self.mode_node.try_recv_mode_request()? { + self.handle_mode_request(request)? + } + Ok(()) + } + + pub fn check_mode_replies(&mut self) -> Result<(), ModeError> { + while let Some(reply) = self.mode_node.try_recv_mode_reply()? { + self.handle_mode_reply(&reply)?; + } + Ok(()) + } + + pub fn handle_mode_reply( + &mut self, + mode_reply: &GenericMessage, + ) -> Result<(), ModeError> { + self.mode_reply_mock.handle_mode_reply(mode_reply); + match self.commanding_helper.handle_mode_reply(mode_reply) { + Ok(result) => { + match result { + DevManagerHelperResult::Idle => todo!(), + DevManagerHelperResult::Busy => todo!(), + DevManagerHelperResult::ModeCommandingDone(context) => { + // Complete the mode command. + self.handle_mode_reached(self.mode_requestor_info)?; + self.mode_and_submode = context.target_mode; + } + } + } + Err(e) => match e { + satrs::dev_mgmt::DevManagerHelperError::ChildNotInStore => todo!(), + }, + } + Ok(()) + } +} + +impl ModeNode for DeviceManager { + fn id(&self) -> ComponentId { + self.id + } +} + +impl ModeChild for DeviceManager { + type Sender = ReplySenderType; + + fn add_mode_parent(&mut self, id: ComponentId, reply_sender: ReplySenderType) { + self.mode_node.add_reply_target(id, reply_sender); + } +} + +impl ModeParent for DeviceManager { + type Sender = RequestSenderType; + + fn add_mode_child(&mut self, id: ComponentId, request_sender: Self::Sender) { + self.commanding_helper + .children_mode_store + .add_component(id, UNKNOWN_MODE); + self.mode_node.add_request_target(id, request_sender); + } +} + +impl ModeProvider for DeviceManager { + fn mode_and_submode(&self) -> ModeAndSubmode { + self.mode_and_submode + } +} + +impl ModeRequestHandler for DeviceManager { type Error = ModeError; fn start_transition( &mut self, requestor: MessageMetadata, mode_and_submode: ModeAndSubmode, + forced: bool, ) -> Result<(), ModeError> { self.mode_and_submode = mode_and_submode; - self.handle_mode_reached(Some(requestor))?; + self.mode_requestor_info = Some(requestor); + self.mode_req_mock + .start_transition(requestor, mode_and_submode, forced) + .unwrap(); + self.commanding_helper.send_mode_cmd_to_all_children( + requestor.request_id(), + mode_and_submode, + forced, + &self.mode_node, + )?; Ok(()) } - fn announce_mode(&self, _requestor_info: Option, _recursive: bool) { + fn announce_mode(&self, requestor_info: Option, recursive: bool) { println!( "{}: announcing mode: {:?}", self.name, self.mode_and_submode ); + let request_id = requestor_info.map_or(0, |info| info.request_id()); + self.commanding_helper + .send_announce_mode_cmd_to_children(request_id, &self.mode_node, recursive) + .expect("sending mode announce request failed"); + self.mode_req_mock.announce_mode(requestor_info, recursive); } fn handle_mode_reached(&mut self, requestor: Option) -> Result<(), ModeError> { if let Some(requestor) = requestor { self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode))?; } - Ok(()) - } - fn send_mode_reply( - &self, - requestor_info: MessageMetadata, - reply: ModeReply, - ) -> Result<(), ModeError> { - self.mode_node.send_mode_reply(requestor_info, reply)?; + self.mode_req_mock.handle_mode_reached(requestor).unwrap(); Ok(()) } @@ -113,246 +836,775 @@ impl ModeRequestHandler for TestDevice { requestor_info.sender_id(), info ); - Ok(()) - } -} - -struct TestAssembly { - pub mode_node: ModeRequestorAndHandlerMpscBounded, - pub mode_requestor_info: Option, - pub mode_and_submode: ModeAndSubmode, - pub target_mode_and_submode: Option, -} - -impl ModeProvider for TestAssembly { - fn mode_and_submode(&self) -> ModeAndSubmode { - self.mode_and_submode - } -} - -impl TestAssembly { - pub fn run(&mut self) { - self.check_mode_requests().expect("mode messaging error"); - self.check_mode_replies().expect("mode messaging error"); - } - - pub fn check_mode_requests(&mut self) -> Result<(), GenericTargetedMessagingError> { - if let Some(request) = self.mode_node.try_recv_mode_request()? { - match request.message { - ModeRequest::SetMode(mode_and_submode) => { - self.start_transition(request.requestor_info, mode_and_submode) - .unwrap(); - } - ModeRequest::ReadMode => self - .mode_node - .send_mode_reply( - request.requestor_info, - ModeReply::ModeReply(self.mode_and_submode), - ) - .unwrap(), - ModeRequest::AnnounceMode => { - self.announce_mode(Some(request.requestor_info), false) - } - ModeRequest::AnnounceModeRecursive => { - self.announce_mode(Some(request.requestor_info), true) - } - ModeRequest::ModeInfo(_) => todo!(), - } - } - Ok(()) - } - - pub fn check_mode_replies(&mut self) -> Result<(), GenericTargetedMessagingError> { - if let Some(reply_and_id) = self.mode_node.try_recv_mode_reply()? { - match reply_and_id.message { - ModeReply::ModeReply(reply) => { - println!( - "TestAssembly: Received mode reply from {:?}, reached: {:?}", - reply_and_id.sender_id(), - reply - ); - } - ModeReply::CantReachMode(_) => todo!(), - ModeReply::WrongMode { expected, reached } => { - println!( - "TestAssembly: Wrong mode reply from {:?}, reached {:?}, expected {:?}", - reply_and_id.sender_id(), - reached, - expected - ); - } - } - } - Ok(()) - } -} - -impl ModeRequestHandler for TestAssembly { - type Error = ModeError; - fn start_transition( - &mut self, - requestor: MessageMetadata, - mode_and_submode: ModeAndSubmode, - ) -> Result<(), Self::Error> { - self.mode_requestor_info = Some(requestor); - self.target_mode_and_submode = Some(mode_and_submode); - Ok(()) - } - - fn announce_mode(&self, requestor_info: Option, recursive: bool) { - println!( - "TestAssembly: Announcing mode (recursively: {}): {:?}", - recursive, self.mode_and_submode - ); - // self.mode_requestor_info = Some((request_id, sender_id)); - let mut mode_request = ModeRequest::AnnounceMode; - if recursive { - mode_request = ModeRequest::AnnounceModeRecursive; - } - let request_id = requestor_info.map_or(0, |info| info.request_id()); - self.mode_node - .request_sender_map - .0 - .iter() - .for_each(|(_, sender)| { - sender - .send(GenericMessage::new( - MessageMetadata::new(request_id, self.mode_node.local_channel_id_generic()), - mode_request, - )) - .expect("sending mode request failed"); - }); - } - - fn handle_mode_reached( - &mut self, - mode_requestor: Option, - ) -> Result<(), Self::Error> { - if let Some(requestor) = mode_requestor { - self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode))?; - } + self.mode_req_mock + .handle_mode_info(requestor_info, info) + .unwrap(); Ok(()) } fn send_mode_reply( &self, - requestor: MessageMetadata, + requestor_info: MessageMetadata, reply: ModeReply, - ) -> Result<(), Self::Error> { - self.mode_node.send_mode_reply(requestor, reply)?; + ) -> Result<(), ModeError> { + self.mode_node.send_mode_reply(requestor_info, reply)?; + self.mode_req_mock + .send_mode_reply(requestor_info, reply) + .unwrap(); + Ok(()) + } +} + +struct CommonDevice { + name: &'static str, + pub id: ComponentId, + pub mode_node: ModeRequestHandlerMpscBounded, + pub mode_and_submode: ModeAndSubmode, + pub mode_req_mock: ModeRequestHandlerMock, +} + +impl CommonDevice { + pub fn new( + name: &'static str, + id: ComponentId, + mode_node: ModeRequestHandlerMpscBounded, + ) -> Self { + Self { + name, + id, + mode_node, + mode_and_submode: UNKNOWN_MODE, + mode_req_mock: ModeRequestHandlerMock::new(id), + } + } + + pub fn run(&mut self) { + self.check_mode_requests().expect("mode messaging error"); + } + + pub fn check_mode_requests(&mut self) -> Result<(), ModeError> { + if let Some(request) = self.mode_node.try_recv_mode_request()? { + self.handle_mode_request(request)? + } + Ok(()) + } + + pub fn get_and_clear_num_mode_msgs(&mut self) -> usize { + self.mode_req_mock.mode_messages_received() + } +} + +impl ModeNode for CommonDevice { + fn id(&self) -> ComponentId { + self.id + } +} + +impl ModeChild for CommonDevice { + type Sender = ReplySenderType; + + fn add_mode_parent(&mut self, id: ComponentId, reply_sender: ReplySenderType) { + self.mode_node.add_message_target(id, reply_sender); + } +} + +impl ModeProvider for CommonDevice { + fn mode_and_submode(&self) -> ModeAndSubmode { + self.mode_and_submode + } +} + +impl ModeRequestHandler for CommonDevice { + type Error = ModeError; + + fn start_transition( + &mut self, + requestor: MessageMetadata, + mode_and_submode: ModeAndSubmode, + forced: bool, + ) -> Result<(), ModeError> { + if self.id() == TestComponentId::MagnetorquerDevice as u64 { + println!("test"); + } + self.mode_and_submode = mode_and_submode; + self.handle_mode_reached(Some(requestor))?; + self.mode_req_mock + .start_transition(requestor, mode_and_submode, forced) + .unwrap(); + Ok(()) + } + + fn announce_mode(&self, requestor_info: Option, recursive: bool) { + println!( + "{}: announcing mode: {:?}", + self.name, self.mode_and_submode + ); + self.mode_req_mock.announce_mode(requestor_info, recursive); + } + + fn handle_mode_reached(&mut self, requestor: Option) -> Result<(), ModeError> { + if let Some(requestor) = requestor { + self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode))?; + } + self.mode_req_mock.handle_mode_reached(requestor).unwrap(); Ok(()) } fn handle_mode_info( &mut self, - _requestor_info: MessageMetadata, - _info: ModeAndSubmode, - ) -> Result<(), Self::Error> { - // TODO: A proper assembly must reach to mode changes of its children.. + requestor_info: MessageMetadata, + info: ModeAndSubmode, + ) -> Result<(), ModeError> { + // A device is a leaf in the tree.. so this really should not happen + println!( + "{}: unexpected mode info from {:?} with mode: {:?}", + self.name, + requestor_info.sender_id(), + info + ); + self.mode_req_mock + .handle_mode_info(requestor_info, info) + .unwrap(); + Ok(()) + } + + fn send_mode_reply( + &self, + requestor_info: MessageMetadata, + reply: ModeReply, + ) -> Result<(), ModeError> { + self.mode_node.send_mode_reply(requestor_info, reply)?; + self.mode_req_mock + .send_mode_reply(requestor_info, reply) + .unwrap(); Ok(()) } } -fn main() { - // All request channel handles. - let (request_sender_to_dev1, request_receiver_dev1) = mpsc::sync_channel(10); - let (request_sender_to_dev2, request_receiver_dev2) = mpsc::sync_channel(10); - let (request_sender_to_assy, request_receiver_assy) = mpsc::sync_channel(10); - - // All reply channel handles. - let (reply_sender_to_assy, reply_receiver_assy) = mpsc::sync_channel(10); - let (reply_sender_to_pus, reply_receiver_pus) = mpsc::sync_channel(10); - - // Mode requestors and handlers. - let mut mode_node_assy = ModeRequestorAndHandlerMpscBounded::new( - TestComponentId::Assembly as ComponentId, - request_receiver_assy, - reply_receiver_assy, - ); - // Mode requestors only. - let mut mode_node_pus = ModeRequestorBoundedMpsc::new( - TestComponentId::PusModeService as ComponentId, - reply_receiver_pus, - ); - - // Request handlers only. - let mut mode_node_dev1 = ModeRequestHandlerMpscBounded::new( - TestComponentId::Device1 as ComponentId, - request_receiver_dev1, - ); - let mut mode_node_dev2 = ModeRequestHandlerMpscBounded::new( - TestComponentId::Device2 as ComponentId, - request_receiver_dev2, - ); - - // Set up mode request senders first. - mode_node_pus.add_message_target( - TestComponentId::Assembly as ComponentId, - request_sender_to_assy, - ); - mode_node_pus.add_message_target( - TestComponentId::Device1 as ComponentId, - request_sender_to_dev1.clone(), - ); - mode_node_pus.add_message_target( - TestComponentId::Device2 as ComponentId, - request_sender_to_dev2.clone(), - ); - mode_node_assy.add_request_target( - TestComponentId::Device1 as ComponentId, - request_sender_to_dev1, - ); - mode_node_assy.add_request_target( - TestComponentId::Device2 as ComponentId, - request_sender_to_dev2, - ); - - // Set up mode reply senders. - mode_node_dev1.add_message_target( - TestComponentId::Assembly as ComponentId, - reply_sender_to_assy.clone(), - ); - mode_node_dev1.add_message_target( - TestComponentId::PusModeService as ComponentId, - reply_sender_to_pus.clone(), - ); - mode_node_dev2.add_message_target( - TestComponentId::Assembly as ComponentId, - reply_sender_to_assy, - ); - mode_node_dev2.add_message_target( - TestComponentId::PusModeService as ComponentId, - reply_sender_to_pus.clone(), - ); - mode_node_assy.add_reply_target( - TestComponentId::PusModeService as ComponentId, - reply_sender_to_pus, - ); - - let mut device1 = TestDevice { - name: "Test Device 1".to_string(), - mode_node: mode_node_dev1, - mode_and_submode: ModeAndSubmode::new(0, 0), - }; - let mut device2 = TestDevice { - name: "Test Device 2".to_string(), - mode_node: mode_node_dev2, - mode_and_submode: ModeAndSubmode::new(0, 0), - }; - let mut assy = TestAssembly { - mode_node: mode_node_assy, - mode_requestor_info: None, - mode_and_submode: ModeAndSubmode::new(0, 0), - target_mode_and_submode: None, - }; - let pus_service = PusModeService { - request_id_counter: Cell::new(0), - mode_node: mode_node_pus, - }; - - pus_service.send_announce_mode_cmd_to_assy(); - assy.run(); - device1.run(); - device2.run(); - assy.run(); +#[derive(Debug, Default)] +pub struct AnnounceModeInfo { + pub requestor: Option, + pub recursive: bool, +} + +pub struct AcsController { + pub mode_node: ModeRequestHandlerMpscBounded, + pub mode_and_submode: ModeAndSubmode, + pub announce_mode_queue: RefCell>, + pub mode_req_mock: ModeRequestHandlerMock, +} + +impl AcsController { + pub fn id() -> ComponentId { + TestComponentId::AcsController as u64 + } + pub fn new(mode_node: ModeRequestHandlerMpscBounded) -> Self { + Self { + mode_node, + mode_and_submode: UNKNOWN_MODE, + announce_mode_queue: Default::default(), + mode_req_mock: ModeRequestHandlerMock::new(Self::id()), + } + } + pub fn run(&mut self) { + self.check_mode_requests().expect("mode messaging error"); + } + + pub fn check_mode_requests(&mut self) -> Result<(), ModeError> { + if let Some(request) = self.mode_node.try_recv_mode_request()? { + self.handle_mode_request(request)? + } + Ok(()) + } +} + +impl ModeNode for AcsController { + fn id(&self) -> ComponentId { + TestComponentId::AcsController as u64 + } +} + +impl ModeChild for AcsController { + type Sender = ReplySenderType; + + fn add_mode_parent(&mut self, id: ComponentId, reply_sender: ReplySenderType) { + self.mode_node.add_message_target(id, reply_sender); + } +} + +impl ModeProvider for AcsController { + fn mode_and_submode(&self) -> ModeAndSubmode { + self.mode_and_submode + } +} + +impl ModeRequestHandler for AcsController { + type Error = ModeError; + + fn start_transition( + &mut self, + requestor: MessageMetadata, + mode_and_submode: ModeAndSubmode, + forced: bool, + ) -> Result<(), Self::Error> { + self.mode_and_submode = mode_and_submode; + self.handle_mode_reached(Some(requestor))?; + self.mode_req_mock + .start_transition(requestor, mode_and_submode, forced) + .unwrap(); + Ok(()) + } + + fn announce_mode(&self, requestor_info: Option, recursive: bool) { + self.announce_mode_queue + .borrow_mut() + .push_back(AnnounceModeInfo { + requestor: requestor_info, + recursive, + }); + self.mode_req_mock.announce_mode(requestor_info, recursive); + } + + fn handle_mode_reached( + &mut self, + requestor_info: Option, + ) -> Result<(), Self::Error> { + if let Some(requestor) = requestor_info { + self.send_mode_reply(requestor, ModeReply::ModeReply(self.mode_and_submode))?; + } + self.mode_req_mock + .handle_mode_reached(requestor_info) + .unwrap(); + Ok(()) + } + + fn handle_mode_info( + &mut self, + requestor_info: MessageMetadata, + info: ModeAndSubmode, + ) -> Result<(), Self::Error> { + // The controller is a leaf in the tree.. so this really should not happen + println!( + "ACS Controller: unexpected mode info from {:?} with mode: {:?}", + requestor_info.sender_id(), + info + ); + self.mode_req_mock + .handle_mode_info(requestor_info, info) + .unwrap(); + Ok(()) + } + + fn send_mode_reply( + &self, + requestor_info: MessageMetadata, + reply: ModeReply, + ) -> Result<(), Self::Error> { + self.mode_node.send_mode_reply(requestor_info, reply)?; + self.mode_req_mock + .send_mode_reply(requestor_info, reply) + .unwrap(); + Ok(()) + } +} + +pub struct TreeTestbench { + pus: PusModeService, + subsystem: AcsSubsystem, + ctrl: AcsController, + mgt_manager: DeviceManager, + mgm_assy: MgmAssembly, + mgm_devs: [CommonDevice; 2], + mgt_dev: CommonDevice, +} + +impl Default for TreeTestbench { + fn default() -> Self { + Self::new() + } +} + +impl TreeTestbench { + pub fn new() -> Self { + // All request channel handles. + let (request_sender_to_mgm_dev_0, request_receiver_mgm_dev_0) = mpsc::sync_channel(10); + let (request_sender_to_mgm_dev_1, request_receiver_mgm_dev_1) = mpsc::sync_channel(10); + let (request_sender_to_mgt_dev, request_receiver_mgt_dev) = mpsc::sync_channel(10); + let (request_sender_to_mgt_man, request_receiver_mgt_man) = mpsc::sync_channel(10); + let (request_sender_to_mgm_assy, request_receiver_mgm_assy) = mpsc::sync_channel(10); + let (request_sender_to_acs_subsystem, request_receiver_acs_subsystem) = + mpsc::sync_channel(10); + let (request_sender_to_acs_ctrl, request_receiver_acs_ctrl) = mpsc::sync_channel(10); + + // All reply channel handles. + let (reply_sender_to_mgm_assy, reply_receiver_mgm_assy) = mpsc::sync_channel(10); + let (reply_sender_to_acs_subsystem, reply_receiver_acs_subsystem) = mpsc::sync_channel(10); + let (reply_sender_to_pus, reply_receiver_pus) = mpsc::sync_channel(10); + let (reply_sender_to_mgt_man, reply_receiver_mgt_man) = mpsc::sync_channel(10); + + // Mode requestors only. + let mode_node_pus = ModeRequestorOneChildBoundedMpsc::new( + TestComponentId::PusModeService as ComponentId, + reply_receiver_pus, + ); + + // Mode requestors and handlers. + let mgm_assy_node = ModeRequestorAndHandlerMpscBounded::new( + TestComponentId::MagnetometerAssembly as ComponentId, + request_receiver_mgm_assy, + reply_receiver_mgm_assy, + ); + let mgt_dev_mgmt_node = ModeRequestorAndHandlerMpscBounded::new( + TestComponentId::MgtDevManager as ComponentId, + request_receiver_mgt_man, + reply_receiver_mgt_man, + ); + let acs_subsystem_node = ModeRequestorAndHandlerMpscBounded::new( + TestComponentId::AcsSubsystem as ComponentId, + request_receiver_acs_subsystem, + reply_receiver_acs_subsystem, + ); + + // Request handlers only. + let mgm_dev_node_0 = ModeRequestHandlerMpscBounded::new( + TestComponentId::MagnetometerDevice0 as ComponentId, + request_receiver_mgm_dev_0, + ); + let mgm_dev_node_1 = ModeRequestHandlerMpscBounded::new( + TestComponentId::MagnetometerDevice1 as ComponentId, + request_receiver_mgm_dev_1, + ); + let mgt_dev_node = ModeRequestHandlerMpscBounded::new( + TestComponentId::MagnetorquerDevice as ComponentId, + request_receiver_mgt_dev, + ); + let acs_ctrl_node = ModeRequestHandlerMpscBounded::new( + TestComponentId::AcsController as ComponentId, + request_receiver_acs_ctrl, + ); + + let mut mgm_dev_0 = CommonDevice::new( + "MGM_0", + TestComponentId::MagnetometerDevice0 as u64, + mgm_dev_node_0, + ); + let mut mgm_dev_1 = CommonDevice::new( + "MGM_1", + TestComponentId::MagnetometerDevice1 as u64, + mgm_dev_node_1, + ); + let mut mgt_dev = CommonDevice::new( + "MGT", + TestComponentId::MagnetorquerDevice as u64, + mgt_dev_node, + ); + let mut mgt_manager = DeviceManager::new( + "MGT_MANAGER", + TestComponentId::MgtDevManager as u64, + mgt_dev_mgmt_node, + ); + let mut mgm_assy = MgmAssembly::new(mgm_assy_node); + let mut acs_subsystem = AcsSubsystem::new(acs_subsystem_node); + let mut acs_ctrl = AcsController::new(acs_ctrl_node); + let mut pus_service = PusModeService::new(1, mode_node_pus); + + // ACS subsystem tables + let mut target_table_safe = TargetTablesMapValue::new("SAFE_TARGET_TBL", None); + target_table_safe.add_entry(TargetTableEntry::new( + "CTRL_SAFE", + TestComponentId::AcsController as u64, + ModeAndSubmode::new(AcsMode::SAFE as u32, 0), + // All submodes allowed. + Some(0xffff), + )); + target_table_safe.add_entry(TargetTableEntry::new_with_precise_submode( + "MGM_A_NML", + TestComponentId::MagnetometerAssembly as u64, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + )); + target_table_safe.add_entry(TargetTableEntry::new_with_precise_submode( + "MGT_MAN_NML", + TestComponentId::MgtDevManager as u64, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + )); + let mut sequence_tbl_safe_0 = SequenceTableMapTable::new("SAFE_SEQ_0_TBL"); + sequence_tbl_safe_0.add_entry(SequenceTableEntry::new( + "SAFE_SEQ_0_MGM_A", + TestComponentId::MagnetometerAssembly as u64, + ModeAndSubmode::new(DefaultMode::NORMAL as Mode, 0), + false, + )); + sequence_tbl_safe_0.add_entry(SequenceTableEntry::new( + "SAFE_SEQ_0_MGT_MAN", + TestComponentId::MgtDevManager as u64, + ModeAndSubmode::new(DefaultMode::NORMAL as Mode, 0), + false, + )); + let mut sequence_tbl_safe_1 = SequenceTableMapTable::new("SAFE_SEQ_1_TBL"); + sequence_tbl_safe_1.add_entry(SequenceTableEntry::new( + "SAFE_SEQ_1_ACS_CTRL", + TestComponentId::AcsController as u64, + ModeAndSubmode::new(AcsMode::SAFE as Mode, 0), + false, + )); + let mut sequence_tbl_safe = SequenceTablesMapValue::new("SAFE_SEQ_TBL"); + sequence_tbl_safe.add_sequence_table(sequence_tbl_safe_0); + sequence_tbl_safe.add_sequence_table(sequence_tbl_safe_1); + acs_subsystem.add_target_and_sequence_table( + AcsMode::SAFE as u32, + target_table_safe, + sequence_tbl_safe, + ); + + // Connect the PUS mode service to all mode objects. + connect_mode_nodes( + &mut pus_service, + request_sender_to_acs_subsystem, + &mut acs_subsystem, + reply_sender_to_pus.clone(), + ); + connect_mode_nodes( + &mut pus_service, + request_sender_to_acs_ctrl.clone(), + &mut acs_ctrl, + reply_sender_to_pus.clone(), + ); + connect_mode_nodes( + &mut pus_service, + request_sender_to_mgm_dev_0.clone(), + &mut mgm_dev_0, + reply_sender_to_pus.clone(), + ); + connect_mode_nodes( + &mut pus_service, + request_sender_to_mgm_dev_1.clone(), + &mut mgm_dev_1, + reply_sender_to_pus.clone(), + ); + connect_mode_nodes( + &mut pus_service, + request_sender_to_mgm_assy.clone(), + &mut mgm_assy, + reply_sender_to_pus.clone(), + ); + connect_mode_nodes( + &mut pus_service, + request_sender_to_mgt_man.clone(), + &mut mgt_manager, + reply_sender_to_pus.clone(), + ); + connect_mode_nodes( + &mut pus_service, + request_sender_to_mgt_dev.clone(), + &mut mgt_dev, + reply_sender_to_pus.clone(), + ); + + // Connect the ACS subsystem to all children. + connect_mode_nodes( + &mut acs_subsystem, + request_sender_to_mgm_assy, + &mut mgm_assy, + reply_sender_to_acs_subsystem.clone(), + ); + connect_mode_nodes( + &mut acs_subsystem, + request_sender_to_acs_ctrl, + &mut acs_ctrl, + reply_sender_to_acs_subsystem.clone(), + ); + connect_mode_nodes( + &mut acs_subsystem, + request_sender_to_mgt_man.clone(), + &mut mgt_manager, + reply_sender_to_acs_subsystem.clone(), + ); + + connect_mode_nodes( + &mut mgm_assy, + request_sender_to_mgm_dev_0, + &mut mgm_dev_0, + reply_sender_to_mgm_assy.clone(), + ); + connect_mode_nodes( + &mut mgm_assy, + request_sender_to_mgm_dev_1, + &mut mgm_dev_1, + reply_sender_to_mgm_assy, + ); + + connect_mode_nodes( + &mut mgt_manager, + request_sender_to_mgt_dev, + &mut mgt_dev, + reply_sender_to_mgt_man, + ); + Self { + pus: pus_service, + subsystem: acs_subsystem, + ctrl: acs_ctrl, + mgm_assy, + mgt_dev, + mgt_manager, + mgm_devs: [mgm_dev_0, mgm_dev_1], + } + } + + pub fn run(&mut self) { + self.pus.run(); + self.subsystem.run(); + self.ctrl.run(); + self.mgt_manager.run(); + self.mgm_assy.run(); + self.mgm_devs[0].run(); + self.mgm_devs[1].run(); + self.mgt_dev.run(); + } +} + +#[test] +fn announce_recursively() { + let mut tb = TreeTestbench::new(); + tb.pus.announce_modes_recursively(); + // Run everything twice so the order does not matter. + tb.run(); + tb.run(); + assert_eq!(tb.subsystem.get_num_mode_requests(), 1); + let mut announces = tb.subsystem.mode_req_mock.announce_mode_calls.borrow_mut(); + assert_eq!(announces.len(), 1); + announces = tb.ctrl.mode_req_mock.announce_mode_calls.borrow_mut(); + assert_eq!(tb.ctrl.mode_req_mock.start_transition_calls.len(), 0); + assert_eq!(tb.ctrl.mode_and_submode(), UNKNOWN_MODE); + assert_eq!(announces.len(), 1); + assert_eq!(tb.mgm_assy.get_num_mode_requests(), 1); + announces = tb.mgm_assy.mode_req_mock.announce_mode_calls.borrow_mut(); + assert_eq!(tb.mgm_assy.mode_req_mock.start_transition_calls.len(), 0); + assert_eq!(tb.mgm_assy.mode_and_submode(), UNKNOWN_MODE); + assert_eq!(announces.len(), 1); + for mgm_dev in &mut tb.mgm_devs { + assert_eq!(mgm_dev.get_and_clear_num_mode_msgs(), 1); + announces = mgm_dev.mode_req_mock.announce_mode_calls.borrow_mut(); + assert_eq!(mgm_dev.mode_req_mock.start_transition_calls.len(), 0); + assert_eq!(mgm_dev.mode_and_submode(), UNKNOWN_MODE); + assert_eq!(announces.len(), 1); + } + assert_eq!(announces.len(), 1); + assert_eq!(tb.mgt_dev.get_and_clear_num_mode_msgs(), 1); + announces = tb.mgt_dev.mode_req_mock.announce_mode_calls.borrow_mut(); + assert_eq!(tb.mgt_dev.mode_req_mock.start_transition_calls.len(), 0); + assert_eq!(tb.mgt_dev.mode_and_submode(), UNKNOWN_MODE); + assert_eq!(announces.len(), 1); + assert_eq!(tb.mgt_manager.get_num_mode_requests(), 1); + announces = tb + .mgt_manager + .mode_req_mock + .announce_mode_calls + .borrow_mut(); + assert_eq!(tb.mgt_manager.mode_req_mock.start_transition_calls.len(), 0); + assert_eq!(tb.mgt_manager.mode_and_submode(), UNKNOWN_MODE); + assert_eq!(announces.len(), 1); +} + +fn generic_mode_reply_checker( + reply_meta: MessageMetadata, + mode_and_submode: ModeAndSubmode, + expected_modes: &mut HashMap, +) { + let id = TestComponentId::try_from(reply_meta.sender_id()).expect("invalid sender id"); + if !expected_modes.contains_key(&reply_meta.sender_id()) { + panic!("received unexpected mode reply from component {:?}", id); + } + let expected_mode = expected_modes.get(&reply_meta.sender_id()).unwrap(); + assert_eq!( + expected_mode, &mode_and_submode, + "mode mismatch for component {:?}, expected {:?}, got {:?}", + id, expected_mode, mode_and_submode + ); + expected_modes.remove(&reply_meta.sender_id()); +} + +#[test] +fn command_safe_mode() { + let mut tb = TreeTestbench::new(); + assert_eq!(tb.ctrl.mode_and_submode(), UNKNOWN_MODE); + assert_eq!(tb.mgm_devs[0].mode_and_submode(), UNKNOWN_MODE); + assert_eq!(tb.mgm_devs[1].mode_and_submode(), UNKNOWN_MODE); + let request_id = tb + .pus + .send_mode_cmd(ModeAndSubmode::new(AcsMode::SAFE as u32, 0)); + tb.run(); + tb.run(); + tb.run(); + let expected_req_id_not_ctrl = request_id << 8; + let expected_req_id_ctrl = (request_id << 8) + 1; + assert_eq!( + tb.ctrl.mode_and_submode(), + ModeAndSubmode::new(AcsMode::SAFE as u32, 0) + ); + assert_eq!( + tb.mgm_devs[0].mode_and_submode(), + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0) + ); + assert_eq!( + tb.mgm_devs[1].mode_and_submode(), + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0) + ); + assert_eq!( + tb.mgm_assy.mode_and_submode(), + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0) + ); + assert_eq!( + tb.mgt_manager.mode_and_submode(), + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0) + ); + assert_eq!( + tb.mgt_dev.mode_and_submode(), + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0) + ); + assert_eq!( + tb.subsystem.mode_and_submode(), + ModeAndSubmode::new(AcsMode::SAFE as u32, 0) + ); + + // Check function calls for subsystem + let generic_mock_check = |ctx: &str, + mock: &mut ModeRequestHandlerMock, + expected_mode_for_transition: ModeAndSubmode, + expected_req_id: RequestId| { + assert_eq!(mock.start_transition_calls.borrow().len(), 1); + let start_transition_call = mock.start_transition_calls.borrow_mut().front().unwrap(); + assert_eq!( + start_transition_call.0.request_id(), + expected_req_id, + "unexpected req ID for component {}", + ctx + ); + assert_eq!(start_transition_call.1, expected_mode_for_transition); + assert_eq!(mock.handle_mode_reached_calls.borrow().len(), 1); + + let handle_mode_reached_ref = mock.handle_mode_reached_calls.borrow(); + let handle_mode_reached_call = handle_mode_reached_ref.front().unwrap(); + assert_eq!( + handle_mode_reached_call.as_ref().unwrap().request_id(), + expected_req_id + ); + drop(handle_mode_reached_ref); + + assert_eq!(mock.send_mode_reply_calls.borrow().len(), 1); + let mode_reply_call = *mock.send_mode_reply_calls.borrow_mut().front().unwrap(); + assert_eq!(mode_reply_call.0.request_id(), expected_req_id); + if let ModeReply::ModeReply(mode_and_submode) = mode_reply_call.1 { + assert_eq!( + mode_and_submode, expected_mode_for_transition, + "unexpected mode for component {}", + mock.id + ); + } else { + panic!("Unexpected mode reply for component {}", mock.id); + } + // TODO: Check all mode replies + assert_eq!(mock.mode_messages_received(), 3); + mock.clear(); + }; + + generic_mock_check( + "subsystem", + &mut tb.subsystem.mode_req_mock, + ModeAndSubmode::new(AcsMode::SAFE as u32, 0), + request_id, + ); + assert_eq!( + tb.subsystem.subsystem_helper.request_id().unwrap(), + request_id + ); + assert_eq!( + tb.subsystem.subsystem_helper.state(), + ModeTreeHelperState::TargetKeeping + ); + assert_eq!(tb.subsystem.subsystem_helper.mode(), AcsMode::SAFE as Mode); + assert_eq!( + tb.subsystem.mode_reply_mock.num_of_received_mode_replies(), + 3 + ); + let mut expected_modes = HashMap::new(); + expected_modes.insert( + TestComponentId::AcsController as u64, + ModeAndSubmode::new(AcsMode::SAFE as u32, 0), + ); + expected_modes.insert( + TestComponentId::MagnetometerAssembly as u64, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + ); + expected_modes.insert( + TestComponentId::MgtDevManager as u64, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + ); + while let Some(reply) = tb.subsystem.mode_reply_mock.mode_reply_messages.pop_front() { + generic_mode_reply_checker(reply.0, reply.1, &mut expected_modes); + } + assert!(expected_modes.is_empty()); + + generic_mock_check( + "ctrl", + &mut tb.ctrl.mode_req_mock, + ModeAndSubmode::new(AcsMode::SAFE as u32, 0), + expected_req_id_ctrl, + ); + generic_mock_check( + "mgm assy", + &mut tb.mgm_assy.mode_req_mock, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + expected_req_id_not_ctrl, + ); + let mut expected_modes = HashMap::new(); + expected_modes.insert( + TestComponentId::MagnetometerDevice0 as u64, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + ); + expected_modes.insert( + TestComponentId::MagnetometerDevice1 as u64, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + ); + while let Some(reply) = tb.mgm_assy.mode_reply_mock.mode_reply_messages.pop_front() { + generic_mode_reply_checker(reply.0, reply.1, &mut expected_modes); + } + assert!(expected_modes.is_empty()); + + generic_mock_check( + "mgt mgmt", + &mut tb.mgt_manager.mode_req_mock, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + expected_req_id_not_ctrl, + ); + let mut expected_modes = HashMap::new(); + expected_modes.insert( + TestComponentId::MagnetorquerDevice as u64, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + ); + let reply = tb + .mgt_manager + .mode_reply_mock + .mode_reply_messages + .pop_front() + .unwrap(); + generic_mode_reply_checker(reply.0, reply.1, &mut expected_modes); + + generic_mock_check( + "mgt dev", + &mut tb.mgt_dev.mode_req_mock, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + expected_req_id_not_ctrl, + ); + generic_mock_check( + "mgm dev 0", + &mut tb.mgm_devs[0].mode_req_mock, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + expected_req_id_not_ctrl, + ); + generic_mock_check( + "mgm dev 1", + &mut tb.mgm_devs[1].mode_req_mock, + ModeAndSubmode::new(DefaultMode::NORMAL as u32, 0), + expected_req_id_not_ctrl, + ); } diff --git a/satrs/tests/pus_events.rs b/satrs/tests/pus_events.rs index d9d572e..81c7b70 100644 --- a/satrs/tests/pus_events.rs +++ b/satrs/tests/pus_events.rs @@ -6,7 +6,6 @@ use satrs::events::{EventU32, EventU32TypedSev, Severity, SeverityInfo}; use satrs::params::U32Pair; use satrs::params::{Params, ParamsHeapless, WritableToBeBytes}; use satrs::pus::event_man::{DefaultPusEventReportingMap, EventReporter, PusEventTmCreatorWithMap}; -use satrs::pus::test_util::TEST_COMPONENT_ID_0; use satrs::request::UniqueApidTargetId; use satrs::tmtc::PacketAsVec; use spacepackets::ecss::tm::PusTmReader; @@ -100,10 +99,7 @@ fn test_threaded_usage() { // Event sender and TM checker thread let jh1 = thread::spawn(move || { event_tx - .send(EventMessage::new( - TEST_COMPONENT_ID_0.id(), - INFO_EVENT.into(), - )) + .send(EventMessage::new(TEST_ID.id(), INFO_EVENT.into())) .expect("Sending info event failed"); loop { match event_packet_rx.try_recv() { @@ -130,7 +126,7 @@ fn test_threaded_usage() { } event_tx .send(EventMessage::new_with_params( - TEST_COMPONENT_ID_0.id(), + TEST_ID.id(), LOW_SEV_EVENT, &Params::Heapless((2_u32, 3_u32).into()), ))