From cb0a65c4d44053f014061895b8684df9bbf31320 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 18 May 2024 14:08:42 +0200 Subject: [PATCH] continue PCDU --- satrs-example/src/config.rs | 8 +++ satrs-example/src/eps/pcdu.rs | 120 +++++++++++++++++++++++++++++----- satrs-example/src/main.rs | 72 ++++++++++++++++++-- satrs-minisim/Cargo.toml | 1 + satrs-minisim/src/eps.rs | 27 ++++---- satrs-minisim/src/lib.rs | 71 +++++++++++++------- 6 files changed, 240 insertions(+), 59 deletions(-) diff --git a/satrs-example/src/config.rs b/satrs-example/src/config.rs index 9e3ec6f..f3b2037 100644 --- a/satrs-example/src/config.rs +++ b/satrs-example/src/config.rs @@ -132,6 +132,7 @@ pub mod components { Acs = 3, Cfdp = 4, Tmtc = 5, + Eps = 6, } // Component IDs for components with the PUS APID. @@ -150,6 +151,11 @@ pub mod components { Mgm0 = 0, } + #[derive(Copy, Clone, PartialEq, Eq)] + pub enum EpsId { + Pcdu = 0, + } + #[derive(Copy, Clone, PartialEq, Eq)] pub enum TmtcId { UdpServer = 0, @@ -172,6 +178,8 @@ pub mod components { UniqueApidTargetId::new(Apid::Sched as u16, 0); pub const MGM_HANDLER_0: UniqueApidTargetId = UniqueApidTargetId::new(Apid::Acs as u16, AcsId::Mgm0 as u32); + pub const PCDU_HANDLER: UniqueApidTargetId = + UniqueApidTargetId::new(Apid::Eps as u16, EpsId::Pcdu as u32); pub const UDP_SERVER: UniqueApidTargetId = UniqueApidTargetId::new(Apid::Tmtc as u16, TmtcId::UdpServer as u32); pub const TCP_SERVER: UniqueApidTargetId = diff --git a/satrs-example/src/eps/pcdu.rs b/satrs-example/src/eps/pcdu.rs index 2b20520..e39377c 100644 --- a/satrs-example/src/eps/pcdu.rs +++ b/satrs-example/src/eps/pcdu.rs @@ -1,5 +1,6 @@ use std::{ - collections::HashMap, + cell::RefCell, + collections::VecDeque, sync::{mpsc, Arc, Mutex}, }; @@ -7,13 +8,16 @@ use derive_new::new; use satrs::{ hk::{HkRequest, HkRequestVariant}, mode::{ModeAndSubmode, ModeError, ModeProvider, ModeReply, ModeRequestHandler}, - power::SwitchState, + power::{SwitchState, SwitchStateBinary}, pus::EcssTmSender, queue::{GenericSendError, GenericTargetedMessagingError}, request::{GenericMessage, MessageMetadata, UniqueApidTargetId}, }; use satrs_example::{config::components::PUS_MODE_SERVICE, DeviceMode, TimestampHelper}; -use satrs_minisim::{SimReply, SimRequest}; +use satrs_minisim::{ + eps::{PcduReply, PcduRequest, SwitchMap, SwitchMapWrapper}, + SerializableSimMsgPayload, SimReply, SimRequest, +}; use crate::{acs::mgm::MpscModeLeafInterface, pus::hk::HkReply, requests::CompositeRequest}; @@ -29,12 +33,13 @@ pub trait SerialInterface { ) -> Result<(), Self::Error>; } -pub struct SimSerialInterface { +#[derive(new)] +pub struct SerialInterfaceToSim { pub sim_request_tx: mpsc::Sender, pub sim_reply_rx: mpsc::Receiver, } -impl SerialInterface for SimSerialInterface { +impl SerialInterface for SerialInterfaceToSim { type Error = (); fn send(&self, data: &[u8]) -> Result<(), Self::Error> { @@ -68,27 +73,112 @@ impl SerialInterface for SimSerialInterface { } } +#[derive(Default)] +pub struct SerialInterfaceDummy { + // Need interior mutability here for both fields. + pub switch_map: RefCell, + pub reply_deque: RefCell>, +} + +impl SerialInterface for SerialInterfaceDummy { + type Error = (); + + fn send(&self, data: &[u8]) -> Result<(), Self::Error> { + let sim_req: SimRequest = serde_json::from_slice(data).unwrap(); + let pcdu_req = + PcduRequest::from_sim_message(&sim_req).expect("PCDU request creation failed"); + let switch_map_mut = &mut self.switch_map.borrow_mut().0; + match pcdu_req { + PcduRequest::SwitchDevice { switch, state } => { + match switch_map_mut.entry(switch) { + std::collections::hash_map::Entry::Occupied(mut val) => { + match state { + SwitchStateBinary::Off => { + *val.get_mut() = SwitchState::Off; + } + SwitchStateBinary::On => { + *val.get_mut() = SwitchState::On; + } + }; + } + std::collections::hash_map::Entry::Vacant(vacant) => { + match state { + SwitchStateBinary::Off => vacant.insert(SwitchState::Off), + SwitchStateBinary::On => vacant.insert(SwitchState::On), + }; + } + }; + } + PcduRequest::RequestSwitchInfo => { + let mut reply_deque_mut = self.reply_deque.borrow_mut(); + reply_deque_mut.push_back(SimReply::new(&PcduReply::SwitchInfo( + self.switch_map.borrow().0.clone(), + ))); + } + }; + Ok(()) + } + + fn try_recv_replies( + &self, + mut f: ReplyHandler, + ) -> Result<(), Self::Error> { + if self.reply_deque.borrow().is_empty() { + return Ok(()); + } + loop { + let mut reply_deque_mut = self.reply_deque.borrow_mut(); + let next_reply = reply_deque_mut.pop_front().unwrap(); + let reply = serde_json::to_string(&next_reply).unwrap(); + f(reply.as_bytes()); + if reply_deque_mut.is_empty() { + break; + } + } + Ok(()) + } +} + +pub enum SerialSimInterfaceWrapper { + Dummy(SerialInterfaceDummy), + Sim(SerialInterfaceToSim), +} + +impl SerialInterface for SerialSimInterfaceWrapper { + type Error = (); + + fn send(&self, data: &[u8]) -> Result<(), Self::Error> { + match self { + SerialSimInterfaceWrapper::Dummy(dummy) => dummy.send(data), + SerialSimInterfaceWrapper::Sim(sim) => sim.send(data), + } + } + + fn try_recv_replies( + &self, + f: ReplyHandler, + ) -> Result<(), Self::Error> { + match self { + SerialSimInterfaceWrapper::Dummy(dummy) => dummy.try_recv_replies(f), + SerialSimInterfaceWrapper::Sim(sim) => sim.try_recv_replies(f), + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum OpCode { RegularOp = 0, PollAndRecvReplies = 1, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[repr(u32)] -pub enum SwitchId { - Mgm0 = 0, - Mgt = 1, -} - -pub type SwitchMap = HashMap; - -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Default)] pub struct SwitchSet { pub valid: bool, pub switch_map: SwitchMap, } +pub type SharedSwitchSet = Arc>; + /// Example PCDU device handler. #[derive(new)] #[allow(clippy::too_many_arguments)] diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index 712e895..bc8a124 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -8,6 +8,9 @@ mod pus; mod requests; mod tmtc; +use crate::eps::pcdu::{ + PcduHandler, SerialInterfaceDummy, SerialInterfaceToSim, SerialSimInterfaceWrapper, +}; use crate::events::EventHandler; use crate::interface::udp::DynamicUdpTmHandler; use crate::pus::stack::PusStack; @@ -45,7 +48,7 @@ use crate::requests::{CompositeRequest, GenericRequestRouter}; use satrs::mode::ModeRequest; use satrs::pus::event_man::EventRequestWithToken; use satrs::spacepackets::{time::cds::CdsTime, time::TimeWriter}; -use satrs_example::config::components::{MGM_HANDLER_0, TCP_SERVER, UDP_SERVER}; +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::{Arc, RwLock}; @@ -68,11 +71,17 @@ fn static_tmtc_pool_main() { 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::channel::>(); + let (pcdu_handler_composite_tx, pcdu_handler_composite_rx) = + mpsc::channel::>(); + let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::channel::>(); + let (pcdu_handler_mode_tx, pcdu_handler_mode_rx) = + mpsc::channel::>(); // Some request are targetable. This map is used to retrieve sender handles based on a target ID. let mut request_map = GenericRequestRouter::default(); @@ -82,6 +91,12 @@ fn static_tmtc_pool_main() { 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); // This helper structure is used by all telecommand providers which need to send telecommands // to the TC source. @@ -207,10 +222,11 @@ 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_mgm_set = Arc::default(); - let mode_leaf_interface = MpscModeLeafInterface { + let mgm_mode_leaf_interface = MpscModeLeafInterface { request_rx: mgm_handler_mode_rx, - reply_to_pus_tx: pus_mode_reply_tx, + reply_to_pus_tx: pus_mode_reply_tx.clone(), reply_to_parent_tx: mgm_handler_mode_reply_to_parent_tx, }; @@ -226,14 +242,41 @@ fn static_tmtc_pool_main() { let mut mgm_handler = MgmHandlerLis3Mdl::new( MGM_HANDLER_0, "MGM_0", - mode_leaf_interface, + mgm_mode_leaf_interface, mgm_handler_composite_rx, - pus_hk_reply_tx, - tm_sink_tx, + pus_hk_reply_tx.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::channel(); + 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, + tm_sink_tx, + pcdu_serial_interface, + shared_switch_set, + ); + info!("Starting TMTC and UDP task"); let jh_udp_tmtc = thread::Builder::new() .name("SATRS tmtc-udp".to_string()) @@ -290,6 +333,21 @@ fn static_tmtc_pool_main() { }) .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()) @@ -315,6 +373,7 @@ fn static_tmtc_pool_main() { .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"); @@ -328,6 +387,7 @@ fn dyn_tmtc_pool_main() { 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. diff --git a/satrs-minisim/Cargo.toml b/satrs-minisim/Cargo.toml index e1449a9..0e8b18c 100644 --- a/satrs-minisim/Cargo.toml +++ b/satrs-minisim/Cargo.toml @@ -11,6 +11,7 @@ serde_json = "1" log = "0.4" thiserror = "1" fern = "0.5" +strum = { version = "0.26", features = ["derive"] } humantime = "2" [dependencies.asynchronix] diff --git a/satrs-minisim/src/eps.rs b/satrs-minisim/src/eps.rs index af20272..aedc31a 100644 --- a/satrs-minisim/src/eps.rs +++ b/satrs-minisim/src/eps.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::mpsc, time::Duration}; +use std::{sync::mpsc, time::Duration}; use asynchronix::{ model::{Model, Output}, @@ -6,14 +6,14 @@ use asynchronix::{ }; use satrs::power::SwitchStateBinary; use satrs_minisim::{ - eps::{PcduReply, PcduSwitch, SwitchMap}, + eps::{PcduReply, PcduSwitch, SwitchMapBinaryWrapper}, SimReply, }; pub const SWITCH_INFO_DELAY_MS: u64 = 10; pub struct PcduModel { - pub switcher_map: SwitchMap, + pub switcher_map: SwitchMapBinaryWrapper, pub mgm_switch: Output, pub mgt_switch: Output, pub reply_sender: mpsc::Sender, @@ -21,12 +21,8 @@ pub struct PcduModel { impl PcduModel { pub fn new(reply_sender: mpsc::Sender) -> Self { - let mut switcher_map = HashMap::new(); - switcher_map.insert(PcduSwitch::Mgm, SwitchStateBinary::Off); - switcher_map.insert(PcduSwitch::Mgt, SwitchStateBinary::Off); - Self { - switcher_map, + switcher_map: Default::default(), mgm_switch: Output::new(), mgt_switch: Output::new(), reply_sender, @@ -44,7 +40,7 @@ impl PcduModel { } pub fn send_switch_info(&mut self) { - let reply = SimReply::new(&PcduReply::SwitchInfo(self.switcher_map.clone())); + let reply = SimReply::new(&PcduReply::SwitchInfo(self.switcher_map.0.clone())); self.reply_sender.send(reply).unwrap(); } @@ -54,6 +50,7 @@ impl PcduModel { ) { let val = self .switcher_map + .0 .get_mut(&switch_and_target_state.0) .unwrap_or_else(|| panic!("switch {:?} not found", switch_and_target_state.0)); *val = switch_and_target_state.1; @@ -76,7 +73,8 @@ pub(crate) mod tests { use std::time::Duration; use satrs_minisim::{ - eps::PcduRequest, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest, + eps::{PcduRequest, SwitchMapBinary}, + SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest, }; use crate::test_helpers::SimTestbench; @@ -105,14 +103,11 @@ pub(crate) mod tests { switch_device(sim_testbench, switch, SwitchStateBinary::On); } - pub(crate) fn get_all_off_switch_map() -> SwitchMap { - let mut switcher_map = SwitchMap::new(); - switcher_map.insert(super::PcduSwitch::Mgm, super::SwitchStateBinary::Off); - switcher_map.insert(super::PcduSwitch::Mgt, super::SwitchStateBinary::Off); - switcher_map + pub(crate) fn get_all_off_switch_map() -> SwitchMapBinary { + SwitchMapBinary::default() } - fn check_switch_state(sim_testbench: &mut SimTestbench, expected_switch_map: &SwitchMap) { + fn check_switch_state(sim_testbench: &mut SimTestbench, expected_switch_map: &SwitchMapBinary) { let request = SimRequest::new_with_epoch_time(PcduRequest::RequestSwitchInfo); sim_testbench .send_request(request) diff --git a/satrs-minisim/src/lib.rs b/satrs-minisim/src/lib.rs index c4b952c..515e448 100644 --- a/satrs-minisim/src/lib.rs +++ b/satrs-minisim/src/lib.rs @@ -160,18 +160,62 @@ impl From for SimCtrlReply { pub mod eps { use super::*; + use satrs::power::{SwitchState, SwitchStateBinary}; use std::collections::HashMap; + use strum::{EnumIter, IntoEnumIterator}; - use satrs::power::SwitchStateBinary; + pub type SwitchMap = HashMap; + pub type SwitchMapBinary = HashMap; - pub type SwitchMap = HashMap; + pub struct SwitchMapWrapper(pub SwitchMap); + pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary); - #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, EnumIter)] pub enum PcduSwitch { Mgm = 0, Mgt = 1, } + impl Default for SwitchMapBinaryWrapper { + fn default() -> Self { + let mut switch_map = SwitchMapBinary::default(); + for entry in PcduSwitch::iter() { + switch_map.insert(entry, SwitchStateBinary::Off); + } + Self(switch_map) + } + } + + impl Default for SwitchMapWrapper { + fn default() -> Self { + let mut switch_map = SwitchMap::default(); + for entry in PcduSwitch::iter() { + switch_map.insert(entry, SwitchState::Unknown); + } + Self(switch_map) + } + } + + impl SwitchMapWrapper { + pub fn new_with_init_switches_off() -> Self { + let mut switch_map = SwitchMap::default(); + for entry in PcduSwitch::iter() { + switch_map.insert(entry, SwitchState::Off); + } + Self(switch_map) + } + } + + impl From for SwitchMapWrapper { + fn from(value: SwitchMapBinaryWrapper) -> Self { + value + .0 + .iter() + .map(|(key, value)| (*key, SwitchState::from(value.into()))) + .collect() + } + } + #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum PcduRequestId { @@ -188,31 +232,14 @@ pub mod eps { RequestSwitchInfo, } - /* - impl PcduRequest { - /// The sole purpose of this method - pub fn write_to_be_bytes(&self, buf: &mut [u8]) { - match self { - PcduRequest::SwitchDevice { switch, state } => { - buf[0] = PcduRequestId::SwitchDevice as u8; - buf[1..3].copy_from_slice(&(*switch as u16).to_be_bytes()); - buf[4] = *state as u8; - } - PcduRequest::RequestSwitchInfo => { - buf[0] = PcduRequestId::RequestSwitchInfo as u8; - } - } - } - } - */ - impl SerializableSimMsgPayload for PcduRequest { const TARGET: SimComponent = SimComponent::Pcdu; } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum PcduReply { - SwitchInfo(SwitchMap), + // Ack, + SwitchInfo(SwitchMapBinary), } impl SerializableSimMsgPayload for PcduReply {