use std::{ sync::mpsc::{self, Receiver, SyncSender}, time::Duration, }; use models::{ ComponentId, acs::subsystem::{Mode, response}, }; use satrs::spacepackets::CcsdsPacketIdAndPsc; use satrs_example::{ModeHelper, TmtcQueues}; #[derive(Debug)] pub struct TransitionInfo { check_mode_reached: bool, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum TransitionState { #[default] Idle, AwaitingReplies, } #[derive(Debug)] pub struct ChildModes { mgm_assembly_mode: models::acs::mgm_assembly::Mode, mgt_mode: models::acs::mgt::Mode, controller_mode: models::acs::ctrl::Mode, } #[derive(Debug)] pub struct TransitionCommands { mgm_assembly_mode: Option<(models::acs::mgm_assembly::Mode, TransitionInfo)>, mgt_mode: Option<(models::acs::mgt::Mode, TransitionInfo)>, controller_mode: Option<(models::acs::ctrl::Mode, TransitionInfo)>, } const OFF_SEQUENCE: [TransitionCommands; 2] = [ TransitionCommands { mgm_assembly_mode: None, mgt_mode: None, controller_mode: Some(( models::acs::ctrl::Mode::Passive, TransitionInfo { check_mode_reached: true, }, )), }, TransitionCommands { mgm_assembly_mode: Some(( models::acs::mgm_assembly::Mode::Device(models::DeviceMode::Off), TransitionInfo { check_mode_reached: false, }, )), mgt_mode: Some(( models::acs::mgt::Mode::Off, TransitionInfo { check_mode_reached: false, }, )), controller_mode: None, }, ]; const SAFE_SEQUENCE: [TransitionCommands; 2] = [ TransitionCommands { mgm_assembly_mode: Some(( models::acs::mgm_assembly::Mode::Device(models::DeviceMode::Normal), TransitionInfo { check_mode_reached: false, }, )), mgt_mode: Some(( models::acs::mgt::Mode::Normal, TransitionInfo { check_mode_reached: false, }, )), controller_mode: None, }, TransitionCommands { mgm_assembly_mode: None, mgt_mode: None, controller_mode: Some(( models::acs::ctrl::Mode::Safe, TransitionInfo { check_mode_reached: false, }, )), }, ]; #[derive(Debug)] pub struct ModeRequestSenders { pub mode_request_ctrl: SyncSender, pub mode_request_assy: SyncSender, pub mode_request_mgt: SyncSender, } #[derive(Debug)] pub struct ModeReportReceivers { pub mode_response_ctrl: Receiver, pub mode_response_assy: Receiver, pub mode_response_mgt: Receiver, } #[derive(Debug)] pub struct Subsystem { mode_helper: ModeHelper, transition_step: usize, current_child_modes: Option, mode_request_senders: ModeRequestSenders, mode_report_receivers: ModeReportReceivers, tmtc_queues: TmtcQueues, } impl Subsystem { pub const ID: ComponentId = ComponentId::AcsSubsystem; pub fn new( mode_request_senders: ModeRequestSenders, mode_report_receivers: ModeReportReceivers, tmtc_queues: TmtcQueues, ) -> Self { Self { mode_helper: ModeHelper::new( models::acs::subsystem::Mode::Off, Duration::from_millis(2000), ), current_child_modes: None, mode_request_senders, mode_report_receivers, tmtc_queues, transition_step: 0, } } pub fn periodic_operation(&mut self) { self.handle_telecommands(); } pub fn handle_telecommands(&mut self) { loop { match self.tmtc_queues.tc_rx.try_recv() { Ok(packet) => { let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header); match postcard::from_bytes::( &packet.payload, ) { Ok(request) => match request { models::acs::subsystem::request::Request::Ping => { self.send_telemetry(Some(tc_id), response::Response::Ok) } models::acs::subsystem::request::Request::Mode(mode_request) => { self.handle_mode_request(mode_request); } }, Err(e) => { log::warn!("failed to deserialize request: {}", e); } } } Err(e) => match e { mpsc::TryRecvError::Empty => break, mpsc::TryRecvError::Disconnected => log::warn!("packet sender disconnected"), }, } } } pub fn transition_sequence_for_mode(mode: Mode) -> &'static [TransitionCommands] { match mode { Mode::Off => &OFF_SEQUENCE, Mode::Safe => &SAFE_SEQUENCE, } } pub fn handle_mode_request( &mut self, mode_request: models::acs::subsystem::request::ModeRequest, ) { match mode_request { models::acs::subsystem::request::ModeRequest::SetMode(target_mode) => { self.mode_helper.start(target_mode); match target_mode { models::acs::subsystem::Mode::Off => { // TODO: We need to start executing the transition table here.. /* self.mode_request_senders .mode_request_assy .send(models::acs::mgm_assembly::request::ModeRequest::SetMode( models::acs::mgm_assembly::Mode::Device(models::DeviceMode::Off), )) .expect("failed to send mode request to MGM assembly"); self.mode_request_senders .mode_request_mgt .send(models::acs::mgt::request::ModeRequest::SetMode( models::acs::mgt::Mode::Normal, )) .expect("failed to send mode request to MGM assembly"); self.mode_request_senders .mode_request_ctrl .send(models::acs::ctrl::request::ModeRequest::SetMode( models::acs::ctrl::Mode::Passive, )) .expect("failed to send mode request to MGM assembly"); */ } models::acs::subsystem::Mode::Safe => { // TODO: Perform SAFE sequence. } } } models::acs::subsystem::request::ModeRequest::ReadMode => { self.send_telemetry( None, response::Response::Mode(response::ModeResponse::Mode( self.mode_helper.current, )), ); } } } pub fn send_telemetry( &self, tc_id: Option, response: models::acs::subsystem::response::Response, ) { match crate::ccsds::pack_ccsds_tm_packet_for_now(Self::ID, tc_id, &response) { Ok(packet) => { if let Err(e) = self.tmtc_queues.tm_tx.send(packet) { log::warn!("failed to send TM packet: {}", e); } } Err(e) => { log::warn!("failed to pack TM packet: {}", e); } } } }