From d6dbf36c418015005e1ef21dd05e2c44f70c1c07 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 16 Sep 2026 13:55:15 +0200 Subject: [PATCH] extract switch and mode helper --- satrs-example/src/acs/mgm.rs | 115 +++++++++++-------------------- satrs-example/src/device_mode.rs | 112 ++++++++++++++++++++++++++++++ satrs-example/src/main.rs | 1 + 3 files changed, 155 insertions(+), 73 deletions(-) create mode 100644 satrs-example/src/device_mode.rs diff --git a/satrs-example/src/acs/mgm.rs b/satrs-example/src/acs/mgm.rs index 78e2739..bf3d76c 100644 --- a/satrs-example/src/acs/mgm.rs +++ b/satrs-example/src/acs/mgm.rs @@ -1,7 +1,7 @@ use satrs::fdir::FaultCounterStd; use satrs::health::{HealthState, HealthTableMapSync, HealthTableProvider}; use satrs::spacepackets::CcsdsPacketIdAndPsc; -use satrs_example::{HkHelperSingleSet, ModeHelper, TimestampHelper, TmtcQueues}; +use satrs_example::{HkHelperSingleSet, TimestampHelper, TmtcQueues}; use satrs_minisim::acs::MgmRequestLis3Mdl; use satrs_minisim::acs::lis3mdl::{ FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR, MgmLis3MdlReply, MgmLis3RawValues, @@ -17,6 +17,7 @@ use types::pcdu::SwitchId; use types::{ComponentId, DeviceMode, HkRequestType, acs::mgm}; use crate::ccsds::pack_ccsds_tm_packet_for_now; +use crate::device_mode::{ModeTransitionEvent, SwitchAndModeHelper}; use crate::eps::PowerSwitchHelper; pub const NR_OF_DATA_AND_CFG_REGISTERS: usize = 14; @@ -56,14 +57,14 @@ impl MgmId { MgmId::_1 => ComponentId::AcsMgm1, } } -} -#[derive(Default, Debug, PartialEq, Eq)] -pub enum TransitionState { - #[default] - Idle, - PowerSwitching, - Done, + #[inline] + pub const fn switch_id(&self) -> SwitchId { + match self { + MgmId::_0 => SwitchId::Mgm0, + MgmId::_1 => SwitchId::Mgm1, + } + } } #[derive(Default)] @@ -159,14 +160,13 @@ pub struct ModeLeafHelper { /// Example MGM device handler strongly based on the LIS3MDL MEMS device. pub struct MgmHandlerLis3Mdl { id: MgmId, - switch_helper: PowerSwitchHelper, tmtc_queues: TmtcQueues, pub spi_com: SpiCommunication, shared_mgm_set: Arc>, buffers: BufWrapper, stamp_helper: TimestampHelper, hk_helper: HkHelperSingleSet, - mode_helpers: ModeHelper, + switch_and_mode_helper: SwitchAndModeHelper, mode_leaf_helper: ModeLeafHelper, spi_fault_counter: FaultCounterStd, health_table: HealthTableMapSync, @@ -187,10 +187,14 @@ impl MgmHandlerLis3Mdl { Self { id, tmtc_queues, - switch_helper, spi_com, shared_mgm_set, - mode_helpers: ModeHelper::new(DeviceMode::Off, mode_timeout), + switch_and_mode_helper: SwitchAndModeHelper::new( + DeviceMode::Off, + mode_timeout, + switch_helper, + id.switch_id(), + ), buffers: BufWrapper::default(), stamp_helper: TimestampHelper::default(), hk_helper: HkHelperSingleSet::new(false, Duration::from_millis(200)), @@ -202,15 +206,7 @@ impl MgmHandlerLis3Mdl { #[inline] pub fn mode(&self) -> DeviceMode { - self.mode_helpers.current - } - - #[inline] - pub fn switch_id(&self) -> SwitchId { - match self.id { - MgmId::_0 => SwitchId::Mgm0, - MgmId::_1 => SwitchId::Mgm1, - } + self.switch_and_mode_helper.mode() } pub fn periodic_operation(&mut self) { @@ -224,7 +220,16 @@ impl MgmHandlerLis3Mdl { self.handle_mode_leaf_handling(); // Handle mode transitions first. - self.handle_mode_transition(); + if let Some(event) = self.switch_and_mode_helper.handle_mode_transition() { + match event { + ModeTransitionEvent::Reached(tc_commander) => { + self.handle_mode_reached(tc_commander) + } + ModeTransitionEvent::Failed(tc_commander) => { + self.handle_mode_transition_failure(tc_commander) + } + } + } // Poll sensor before checking and generating HK. if self.mode() == DeviceMode::Normal { @@ -259,8 +264,7 @@ impl MgmHandlerLis3Mdl { } mgm::request::Request::Mode(device_mode) => match device_mode { ModeRequest::SetMode(device_mode) => { - self.mode_helpers.tc_commander = Some(tc_id); - self.start_transition(device_mode, false); + self.start_transition(device_mode, Some(tc_id)); } ModeRequest::ReadMode => self.send_telemetry( Some(tc_id), @@ -309,7 +313,7 @@ impl MgmHandlerLis3Mdl { loop { match self.mode_leaf_helper.request_rx.try_recv() { Ok(request) => match request { - ModeRequest::SetMode(device_mode) => self.start_transition(device_mode, false), + ModeRequest::SetMode(device_mode) => self.start_transition(device_mode, None), ModeRequest::ReadMode => self.report_mode_to_parent(), }, Err(e) => match e { @@ -445,59 +449,29 @@ impl MgmHandlerLis3Mdl { // this every cycle the fault persists, and current stays Normal until the // transition completes, so re-triggering here would keep resetting the // transition state machine before it can ever finish. - if self.mode_helpers.target != Some(DeviceMode::Off) { + if self.switch_and_mode_helper.target() != Some(DeviceMode::Off) { log::warn!("{}: commanding device off due to fault", self.id.str()); - self.start_transition(DeviceMode::Off, true); + self.start_transition(DeviceMode::Off, None); } } } } - fn start_transition(&mut self, target_mode: DeviceMode, _forced: bool) { + fn start_transition( + &mut self, + target_mode: DeviceMode, + tc_commander: Option, + ) { log::info!("{}: transitioning to mode {:?}", self.id.str(), target_mode); if target_mode == DeviceMode::Off { self.shared_mgm_set.lock().unwrap().valid = false; } - self.mode_helpers.start(target_mode); - } - - pub fn handle_mode_transition(&mut self) { - if self.mode_helpers.target.is_none() { - return; - } - let target_mode = self.mode_helpers.target.unwrap(); - let switch_target_on = target_mode != DeviceMode::Off; - if self.mode_helpers.transition_state == TransitionState::Idle { - let result = if switch_target_on { - self.switch_helper.send_switch_on_cmd(self.switch_id()) - } else { - self.switch_helper.send_switch_off_cmd(self.switch_id()) - }; - if result.is_err() { - // Could not send switch command.. still continue with transition. - log::error!( - "failed to send switch {} command", - if switch_target_on { "on" } else { "off" } - ); - } - self.mode_helpers.transition_state = TransitionState::PowerSwitching; - } - if self.mode_helpers.transition_state == TransitionState::PowerSwitching { - if self.switch_helper.is_switch_on(self.switch_id()) == switch_target_on { - log::info!("switch is {}", if switch_target_on { "on" } else { "off" }); - self.mode_helpers.transition_state = TransitionState::Done; - } else if self.mode_helpers.timed_out() { - self.handle_mode_transition_failure(); - } - } - if self.mode_helpers.transition_state == TransitionState::Done { - self.handle_mode_reached(); - } + self.switch_and_mode_helper + .start_transition(target_mode, tc_commander); } // Should be called to complete a mode transition which failed. - fn handle_mode_transition_failure(&mut self) { - let tc_commander = self.mode_helpers.finish(false); + fn handle_mode_transition_failure(&mut self, tc_commander: Option) { if tc_commander.is_some() { self.send_telemetry( tc_commander, @@ -511,8 +485,7 @@ impl MgmHandlerLis3Mdl { } // Should be called to complete a mode transition successfully. - fn handle_mode_reached(&mut self) { - let tc_commander = self.mode_helpers.finish(true); + fn handle_mode_reached(&mut self, tc_commander: Option) { self.announce_mode(); if let Some(requestor) = tc_commander { self.send_mode_tm(requestor); @@ -522,18 +495,14 @@ impl MgmHandlerLis3Mdl { } fn announce_mode(&self) { - log::info!( - "{} announcing mode: {:?}", - self.id.str(), - self.mode_helpers.current - ); + log::info!("{} announcing mode: {:?}", self.id.str(), self.mode()); // TODO: Event? } fn report_mode_to_parent(&self) { self.mode_leaf_helper .report_tx - .send(ModeResponse::Mode(self.mode_helpers.current)) + .send(ModeResponse::Mode(self.mode())) .unwrap(); } diff --git a/satrs-example/src/device_mode.rs b/satrs-example/src/device_mode.rs new file mode 100644 index 0000000..2a3349c --- /dev/null +++ b/satrs-example/src/device_mode.rs @@ -0,0 +1,112 @@ +use std::time::Duration; + +use types::pcdu::SwitchId; + +use crate::eps::PowerSwitchHelper; + +/// Modes that distinguish a powered-off state from one or more powered-on states, so +/// [`SwitchAndModeHelper`] knows which way to drive the switch for a given target mode. +pub trait PowerSwitchedMode: Copy + PartialEq { + fn requires_power(&self) -> bool; +} + +impl PowerSwitchedMode for types::DeviceMode { + fn requires_power(&self) -> bool { + *self != types::DeviceMode::Off + } +} + +#[derive(Default, Debug, PartialEq, Eq)] +enum SwitchTransitionState { + #[default] + Idle, + PowerSwitching, + Done, +} + +/// Outcome of a pending mode transition, once [`SwitchAndModeHelper::handle_mode_transition`] +/// has driven it to completion. Carries back whichever TC commanded the transition, if any, so +/// the caller can reply to it -- what that reply looks like is handler-specific, so this stays +/// out of the helper. +pub enum ModeTransitionEvent { + Reached(Option), + Failed(Option), +} + +/// Drives the on/off power-switch commanding state machine (Idle -> PowerSwitching -> Done) +/// shared by every device handler that owns a single power switch of its own. +/// +/// Handler-specific reactions (sending telemetry, invalidating cached sensor data, reporting to +/// a parent) are not this helper's concern: [`Self::handle_mode_transition`] just reports when a +/// transition finishes (or fails) and leaves what to do about it to the caller. +pub struct SwitchAndModeHelper { + mode_helper: satrs_example::ModeHelper, + switch_helper: PowerSwitchHelper, + switch_id: SwitchId, +} + +impl SwitchAndModeHelper { + pub fn new( + init_mode: Mode, + timeout: Duration, + switch_helper: PowerSwitchHelper, + switch_id: SwitchId, + ) -> Self { + Self { + mode_helper: satrs_example::ModeHelper::new(init_mode, timeout), + switch_helper, + switch_id, + } + } + + #[inline] + pub fn mode(&self) -> Mode { + self.mode_helper.current + } + + #[inline] + pub fn target(&self) -> Option { + self.mode_helper.target + } + + pub fn start_transition( + &mut self, + target_mode: Mode, + tc_commander: Option, + ) { + self.mode_helper.tc_commander = tc_commander; + self.mode_helper.start(target_mode); + } + + pub fn handle_mode_transition(&mut self) -> Option { + let target_mode = self.mode_helper.target?; + let switch_target_on = target_mode.requires_power(); + if self.mode_helper.transition_state == SwitchTransitionState::Idle { + let result = if switch_target_on { + self.switch_helper.send_switch_on_cmd(self.switch_id) + } else { + self.switch_helper.send_switch_off_cmd(self.switch_id) + }; + if result.is_err() { + // Could not send switch command.. still continue with transition. + log::error!( + "failed to send switch {} command", + if switch_target_on { "on" } else { "off" } + ); + } + self.mode_helper.transition_state = SwitchTransitionState::PowerSwitching; + } + if self.mode_helper.transition_state == SwitchTransitionState::PowerSwitching { + if self.switch_helper.is_switch_on(self.switch_id) == switch_target_on { + log::info!("switch is {}", if switch_target_on { "on" } else { "off" }); + self.mode_helper.transition_state = SwitchTransitionState::Done; + } else if self.mode_helper.timed_out() { + return Some(ModeTransitionEvent::Failed(self.mode_helper.finish(false))); + } + } + if self.mode_helper.transition_state == SwitchTransitionState::Done { + return Some(ModeTransitionEvent::Reached(self.mode_helper.finish(true))); + } + None + } +} diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index bf4a98a..2d8386c 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -48,6 +48,7 @@ use crate::{ mod acs; mod ccsds; mod controller; +mod device_mode; mod eps; mod event_manager; mod interface;