diff --git a/satrs-example/src/acs/mgm.rs b/satrs-example/src/acs/mgm.rs index 62baf04..9780acd 100644 --- a/satrs-example/src/acs/mgm.rs +++ b/satrs-example/src/acs/mgm.rs @@ -1,5 +1,6 @@ use derive_new::new; use satrs::hk::{HkRequest, HkRequestVariant}; +use satrs::power::{PowerSwitchInfo, PowerSwitcherCommandSender}; use satrs::queue::{GenericSendError, GenericTargetedMessagingError}; use satrs::spacepackets::ecss::hk; use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader}; @@ -9,6 +10,7 @@ use satrs_minisim::acs::lis3mdl::{ MgmLis3MdlReply, MgmLis3RawValues, FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR, }; use satrs_minisim::acs::MgmRequestLis3Mdl; +use satrs_minisim::eps::PcduSwitch; use satrs_minisim::{SerializableSimMsgPayload, SimReply, SimRequest}; use std::fmt::Debug; use std::sync::mpsc::{self}; @@ -127,31 +129,44 @@ pub struct MpscModeLeafInterface { pub reply_to_parent_tx: mpsc::Sender>, } +#[derive(Default)] +pub struct BufWrapper { + tx_buf: [u8; 32], + rx_buf: [u8; 32], + tm_buf: [u8; 32], +} + /// Example MGM device handler strongly based on the LIS3MDL MEMS device. #[derive(new)] #[allow(clippy::too_many_arguments)] -pub struct MgmHandlerLis3Mdl { +pub struct MgmHandlerLis3Mdl< + ComInterface: SpiInterface, + TmSender: EcssTmSender, + SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, +> { id: UniqueApidTargetId, dev_str: &'static str, mode_interface: MpscModeLeafInterface, composite_request_rx: mpsc::Receiver>, hk_reply_tx: mpsc::Sender>, + switch_helper: SwitchHelper, tm_sender: TmSender, pub com_interface: ComInterface, shared_mgm_set: Arc>, #[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")] mode_and_submode: ModeAndSubmode, #[new(default)] - tx_buf: [u8; 32], - #[new(default)] - rx_buf: [u8; 32], - #[new(default)] - tm_buf: [u8; 32], + bufs: BufWrapper, #[new(default)] stamp_helper: TimestampHelper, } -impl MgmHandlerLis3Mdl { +impl< + ComInterface: SpiInterface, + TmSender: EcssTmSender, + SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, + > MgmHandlerLis3Mdl +{ pub fn periodic_operation(&mut self) { self.stamp_helper.update_from_now(); // Handle requests. @@ -210,17 +225,17 @@ impl MgmHandlerLis3Mdl MgmHandlerLis3Mdl MgmHandlerLis3Mdl ModeProvider - for MgmHandlerLis3Mdl +impl< + ComInterface: SpiInterface, + TmSender: EcssTmSender, + SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, + > ModeProvider for MgmHandlerLis3Mdl { fn mode_and_submode(&self) -> ModeAndSubmode { self.mode_and_submode } } -impl ModeRequestHandler - for MgmHandlerLis3Mdl +impl< + ComInterface: SpiInterface, + TmSender: EcssTmSender, + SwitchHelper: PowerSwitchInfo + PowerSwitcherCommandSender, + > ModeRequestHandler for MgmHandlerLis3Mdl { type Error = ModeError; fn start_transition( @@ -388,17 +409,17 @@ mod tests { use satrs_example::config::components::Apid; use satrs_minisim::acs::lis3mdl::MgmLis3RawValues; - use crate::{pus::hk::HkReply, requests::CompositeRequest}; + use crate::{eps::TestSwitchHelper, pus::hk::HkReply, requests::CompositeRequest}; use super::*; #[derive(Default)] - pub struct TestInterface { + pub struct TestSpiInterface { pub call_count: u32, pub next_mgm_data: MgmLis3RawValues, } - impl SpiInterface for TestInterface { + impl SpiInterface for TestSpiInterface { type Error = (); fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> { @@ -420,7 +441,8 @@ mod tests { pub composite_request_tx: mpsc::Sender>, pub hk_reply_rx: mpsc::Receiver>, pub tm_rx: mpsc::Receiver, - pub handler: MgmHandlerLis3Mdl>, + pub handler: + MgmHandlerLis3Mdl, TestSwitchHelper>, } impl MgmTestbench { @@ -450,8 +472,9 @@ mod tests { mode_interface, composite_request_rx, hk_reply_tx, + TestSwitchHelper::default(), tm_tx, - TestInterface::default(), + TestSpiInterface::default(), shared_mgm_set, ), } diff --git a/satrs-example/src/eps/mod.rs b/satrs-example/src/eps/mod.rs index 055b3ab..ee325e4 100644 --- a/satrs-example/src/eps/mod.rs +++ b/satrs-example/src/eps/mod.rs @@ -1 +1,204 @@ +use derive_new::new; +use std::{ + borrow::BorrowMut, + cell::RefCell, + collections::VecDeque, + sync::mpsc, + time::{Duration, Instant}, +}; + +use satrs::{ + power::{ + PowerSwitchInfo, PowerSwitcherCommandSender, SwitchId, SwitchRequest, SwitchState, + SwitchStateBinary, + }, + queue::GenericSendError, + request::{GenericMessage, MessageMetadata}, +}; +use satrs_minisim::eps::{PcduSwitch, SwitchMapWrapper}; +use thiserror::Error; + +use self::pcdu::SharedSwitchSet; + pub mod pcdu; + +#[derive(new, Clone)] +pub struct PowerSwitchHelper { + switcher_tx: mpsc::SyncSender>, + shared_switch_set: SharedSwitchSet, + #[new(default)] + switch_cmd_sent_instant: Option, +} + +#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)] +pub enum SwitchCommandingError { + #[error("invalid switch id")] + InvalidSwitchId(SwitchId), + #[error("send error: {0}")] + Send(#[from] GenericSendError), +} +#[derive(Debug, Error, Copy, Clone, PartialEq, Eq)] +pub enum SwitchInfoError { + /// This is a configuration error which should not occur. + #[error("switch ID not in map")] + SwitchIdNotInMap(PcduSwitch), + #[error("switch set invalid")] + SwitchSetInvalid, +} + +impl PowerSwitchInfo for PowerSwitchHelper { + type Error = SwitchInfoError; + + fn switch_state( + &self, + switch_id: PcduSwitch, + ) -> Result { + let switch_set = self + .shared_switch_set + .lock() + .expect("failed to lock switch set"); + if !switch_set.valid { + return Err(SwitchInfoError::SwitchSetInvalid); + } + + if let Some(state) = switch_set.switch_map.get(&switch_id) { + return Ok(*state); + } + Err(SwitchInfoError::SwitchIdNotInMap(switch_id)) + } + + fn switch_delay_ms(&self) -> Duration { + // Here, we could set device specific switch delays theoretically. Set it to this value + // for now. + Duration::from_millis(1000) + } +} + +impl PowerSwitcherCommandSender for PowerSwitchHelper { + type Error = SwitchCommandingError; + + fn send_switch_on_cmd( + &self, + requestor_info: satrs::request::MessageMetadata, + switch_id: PcduSwitch, + ) -> Result<(), Self::Error> { + self.switcher_tx + .send_switch_on_cmd(requestor_info, switch_id)?; + Ok(()) + } + + fn send_switch_off_cmd( + &self, + requestor_info: satrs::request::MessageMetadata, + switch_id: PcduSwitch, + ) -> Result<(), Self::Error> { + self.switcher_tx + .send_switch_off_cmd(requestor_info, switch_id)?; + Ok(()) + } +} + +#[derive(new)] +pub struct SwitchRequestInfo { + pub requestor_info: MessageMetadata, + pub switch_id: PcduSwitch, + pub target_state: satrs::power::SwitchStateBinary, +} + +// Test switch helper which can be used for unittests. +pub struct TestSwitchHelper { + pub switch_requests: RefCell>, + pub switch_info_requests: RefCell>, + pub switch_delay_request_count: u32, + pub next_switch_delay: Duration, + pub switch_map: RefCell, + pub switch_map_valid: bool, +} + +impl Default for TestSwitchHelper { + fn default() -> Self { + Self { + switch_requests: Default::default(), + switch_info_requests: Default::default(), + switch_delay_request_count: Default::default(), + next_switch_delay: Duration::from_millis(1000), + switch_map: Default::default(), + switch_map_valid: Default::default(), + } + } +} + +impl PowerSwitchInfo for TestSwitchHelper { + type Error = SwitchInfoError; + + fn switch_state( + &self, + switch_id: PcduSwitch, + ) -> Result { + let mut switch_info_requests_mut = self.switch_info_requests.borrow_mut(); + switch_info_requests_mut.push_back(switch_id); + if !self.switch_map_valid { + return Err(SwitchInfoError::SwitchSetInvalid); + } + let switch_map_mut = self.switch_map.borrow_mut(); + if let Some(state) = switch_map_mut.0.get(&switch_id) { + return Ok(*state); + } + Err(SwitchInfoError::SwitchIdNotInMap(switch_id)) + } + + fn switch_delay_ms(&self) -> Duration { + self.next_switch_delay + } +} + +impl PowerSwitcherCommandSender for TestSwitchHelper { + type Error = SwitchCommandingError; + + fn send_switch_on_cmd( + &self, + requestor_info: MessageMetadata, + switch_id: PcduSwitch, + ) -> Result<(), Self::Error> { + let mut switch_requests_mut = self.switch_requests.borrow_mut(); + switch_requests_mut.push_back(SwitchRequestInfo { + requestor_info, + switch_id, + target_state: SwitchStateBinary::On, + }); + // By default, the test helper immediately acknowledges the switch request by setting + // the appropriate switch state in the internal switch map. + let mut switch_map_mut = self.switch_map.borrow_mut(); + if let Some(switch_state) = switch_map_mut.0.get_mut(&switch_id) { + *switch_state = SwitchState::On; + } + Ok(()) + } + + fn send_switch_off_cmd( + &self, + requestor_info: MessageMetadata, + switch_id: PcduSwitch, + ) -> Result<(), Self::Error> { + let mut switch_requests_mut = self.switch_requests.borrow_mut(); + switch_requests_mut.push_back(SwitchRequestInfo { + requestor_info, + switch_id, + target_state: SwitchStateBinary::Off, + }); + // By default, the test helper immediately acknowledges the switch request by setting + // the appropriate switch state in the internal switch map. + let mut switch_map_mut = self.switch_map.borrow_mut(); + if let Some(switch_state) = switch_map_mut.0.get_mut(&switch_id) { + *switch_state = SwitchState::Off; + } + Ok(()) + } +} + +impl TestSwitchHelper { + // Helper function which can be used to force a switch to another state for test purposes. + pub fn set_switch_state(&mut self, switch: PcduSwitch, state: SwitchState) { + self.switch_map.get_mut().0.insert(switch, state); + } +} diff --git a/satrs-example/src/eps/pcdu.rs b/satrs-example/src/eps/pcdu.rs index 17657c6..639b2cd 100644 --- a/satrs-example/src/eps/pcdu.rs +++ b/satrs-example/src/eps/pcdu.rs @@ -8,14 +8,14 @@ use derive_new::new; use satrs::{ hk::{HkRequest, HkRequestVariant}, mode::{ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler}, - power::{SwitchRequest, SwitchStateBinary}, + power::SwitchRequest, pus::EcssTmSender, queue::{GenericSendError, GenericTargetedMessagingError}, request::{GenericMessage, MessageMetadata, UniqueApidTargetId}, }; use satrs_example::{config::components::PUS_MODE_SERVICE, DeviceMode, TimestampHelper}; use satrs_minisim::{ - eps::{PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinary, SwitchMapBinaryWrapper}, + eps::{PcduReply, PcduRequest, PcduSwitch, SwitchMap, SwitchMapBinaryWrapper}, SerializableSimMsgPayload, SimReply, SimRequest, }; diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index 2db7872..00e704c 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -11,6 +11,7 @@ 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; @@ -50,7 +51,7 @@ use satrs::pus::event_man::EventRequestWithToken; use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter}; use satrs_example::config::components::{MGM_HANDLER_0, PCDU_HANDLER, TCP_SERVER, UDP_SERVER}; use std::net::{IpAddr, SocketAddr}; -use std::sync::mpsc; +use std::sync::{mpsc, Mutex}; use std::sync::{Arc, RwLock}; use std::thread; use std::time::Duration; @@ -222,8 +223,9 @@ fn static_tmtc_pool_main() { let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) = mpsc::channel(); - let shared_switch_set = Arc::default(); + 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 { @@ -247,6 +249,7 @@ fn static_tmtc_pool_main() { mgm_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, @@ -507,6 +510,10 @@ fn dyn_tmtc_pool_main() { let mut tm_funnel = TmSinkDynamic::new(sync_tm_tcp_source, tm_funnel_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::channel(); let shared_mgm_set = Arc::default(); @@ -531,6 +538,7 @@ fn dyn_tmtc_pool_main() { mode_leaf_interface, mgm_handler_composite_rx, pus_hk_reply_tx, + switch_helper.clone(), tm_funnel_tx, mgm_spi_interface, shared_mgm_set, diff --git a/satrs/src/power.rs b/satrs/src/power.rs index 48eae0e..72e50b6 100644 --- a/satrs/src/power.rs +++ b/satrs/src/power.rs @@ -1,3 +1,5 @@ +use core::time::Duration; + use derive_new::new; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -57,28 +59,28 @@ impl From for SwitchState { pub type SwitchId = u16; /// Generic trait for a device capable of turning on and off switches. -pub trait PowerSwitcherCommandSender { +pub trait PowerSwitcherCommandSender> { type Error; fn send_switch_on_cmd( &self, requestor_info: MessageMetadata, - switch_id: SwitchId, + switch_id: SwitchType, ) -> Result<(), Self::Error>; fn send_switch_off_cmd( &self, requestor_info: MessageMetadata, - switch_id: SwitchId, + switch_id: SwitchType, ) -> Result<(), Self::Error>; } -pub trait PowerSwitchInfo { +pub trait PowerSwitchInfo { type Error; /// Retrieve the switch state - fn switch_state(&self, switch_id: SwitchId) -> Result; + fn switch_state(&self, switch_id: SwitchType) -> Result; - fn is_switch_on(&self, switch_id: SwitchId) -> Result { + fn is_switch_on(&self, switch_id: SwitchType) -> Result { Ok(self.switch_state(switch_id)? == SwitchState::On) } @@ -86,7 +88,7 @@ pub trait PowerSwitchInfo { /// /// This may take into account the time to send a command, wait for it to be executed, and /// see the switch changed. - fn switch_delay_ms(&self) -> u32; + fn switch_delay_ms(&self) -> Duration; } #[derive(new)] @@ -119,17 +121,17 @@ pub mod std_mod { pub type MpscSwitchCmdSender = mpsc::Sender>; pub type MpscSwitchCmdSenderBounded = mpsc::SyncSender>; - impl PowerSwitcherCommandSender for MpscSwitchCmdSender { + impl> PowerSwitcherCommandSender for MpscSwitchCmdSender { type Error = GenericSendError; fn send_switch_on_cmd( &self, requestor_info: MessageMetadata, - switch_id: SwitchId, + switch_id: SwitchType, ) -> Result<(), Self::Error> { self.send(GenericMessage::new( requestor_info, - SwitchRequest::new(switch_id, SwitchStateBinary::On), + SwitchRequest::new(switch_id.into(), SwitchStateBinary::On), )) .map_err(|_| GenericSendError::RxDisconnected) } @@ -137,27 +139,27 @@ pub mod std_mod { fn send_switch_off_cmd( &self, requestor_info: MessageMetadata, - switch_id: SwitchId, + switch_id: SwitchType, ) -> Result<(), Self::Error> { self.send(GenericMessage::new( requestor_info, - SwitchRequest::new(switch_id, SwitchStateBinary::Off), + SwitchRequest::new(switch_id.into(), SwitchStateBinary::Off), )) .map_err(|_| GenericSendError::RxDisconnected) } } - impl PowerSwitcherCommandSender for MpscSwitchCmdSenderBounded { + impl> PowerSwitcherCommandSender for MpscSwitchCmdSenderBounded { type Error = GenericSendError; fn send_switch_on_cmd( &self, requestor_info: MessageMetadata, - switch_id: SwitchId, + switch_id: SwitchType, ) -> Result<(), Self::Error> { self.try_send(GenericMessage::new( requestor_info, - SwitchRequest::new(switch_id, SwitchStateBinary::On), + SwitchRequest::new(switch_id.into(), SwitchStateBinary::On), )) .map_err(|e| match e { mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None), @@ -168,11 +170,11 @@ pub mod std_mod { fn send_switch_off_cmd( &self, requestor_info: MessageMetadata, - switch_id: SwitchId, + switch_id: SwitchType, ) -> Result<(), Self::Error> { self.try_send(GenericMessage::new( requestor_info, - SwitchRequest::new(switch_id, SwitchStateBinary::Off), + SwitchRequest::new(switch_id.into(), SwitchStateBinary::Off), )) .map_err(|e| match e { mpsc::TrySendError::Full(_) => GenericSendError::QueueFull(None),