diff --git a/satrs-example/src/acs/mgm.rs b/satrs-example/src/acs/mgm.rs index 0c33689..662549b 100644 --- a/satrs-example/src/acs/mgm.rs +++ b/satrs-example/src/acs/mgm.rs @@ -38,10 +38,10 @@ pub const SPI_FAULT_DECREMENT_AFTER: Duration = Duration::from_secs(30); // FDIR configuration for power cycle recoveries. A second recovery before the counter was // decremented again marks the component faulty. -pub const RECOVERY_THRESHOLD: u32 = 1; -pub const RECOVERY_DECREMENT_AFTER: Duration = Duration::from_secs(30 * 60); +pub const RECOVERY_THRESHOLD: u32 = 2; +pub const RECOVERY_DECREMENT_AFTER: Duration = Duration::from_secs(60); /// Time the device stays unpowered during a power cycle, so it can fully discharge. -pub const RECOVERY_OFF_DURATION: Duration = Duration::from_secs(1); +pub const RECOVERY_OFF_DURATION: Duration = Duration::from_millis(500); #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum MgmId { diff --git a/satrs-example/src/device_mode.rs b/satrs-example/src/device_mode.rs index e9fc455..0c069b2 100644 --- a/satrs-example/src/device_mode.rs +++ b/satrs-example/src/device_mode.rs @@ -29,6 +29,12 @@ enum SwitchTransitionState { Done, } +/// Outcome of a single power switch transition. +enum SwitchOutcome { + Reached(Option), + Failed(Option), +} + /// Dedicated states for power cycling a device. #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum PowerCycleState { @@ -64,7 +70,7 @@ pub struct SwitchAndModeHelper { mode_helper: satrs_example::ModeHelper, switch_helper: PowerSwitchHelper, switch_id: SwitchId, - power_cycle: PowerCycleState, + power_cycle_state: PowerCycleState, power_cycle_off_duration: Duration, } @@ -79,7 +85,7 @@ impl SwitchAndModeHelper { mode_helper: satrs_example::ModeHelper::new(init_mode, timeout), switch_helper, switch_id, - power_cycle: PowerCycleState::Idle, + power_cycle_state: PowerCycleState::Idle, power_cycle_off_duration: Duration::ZERO, } } @@ -97,7 +103,7 @@ impl SwitchAndModeHelper { /// Mode which should be reported to other components. A power cycle is hidden from them, /// so this is the mode which is restored after the power cycle while one is active. pub fn reported_mode(&self) -> Mode { - match self.power_cycle { + match self.power_cycle_state { PowerCycleState::SwitchingOff { restore_mode } | PowerCycleState::WaitingOff { restore_mode, .. } | PowerCycleState::SwitchingOn { restore_mode } => restore_mode, @@ -107,7 +113,7 @@ impl SwitchAndModeHelper { #[inline] pub fn power_cycle_active(&self) -> bool { - self.power_cycle != PowerCycleState::Idle + self.power_cycle_state != PowerCycleState::Idle } /// Starts a new transition, aborting a running power cycle. @@ -116,14 +122,14 @@ impl SwitchAndModeHelper { target_mode: Mode, tc_commander: Option, ) { - self.power_cycle = PowerCycleState::Idle; + self.power_cycle_state = PowerCycleState::Idle; self.start_transition_internal(target_mode, tc_commander); } /// Switches the device off, keeps it off for `off_duration` and then restores the current /// mode. Reaching the intermediate off mode does not generate an event. pub fn start_power_cycle(&mut self, off_duration: Duration) { - self.power_cycle = PowerCycleState::SwitchingOff { + self.power_cycle_state = PowerCycleState::SwitchingOff { restore_mode: self.mode(), }; self.power_cycle_off_duration = off_duration; @@ -140,43 +146,64 @@ impl SwitchAndModeHelper { } /// This is the main API that the periodic handler of a device handler should call. + /// + /// It handles the switch commanding and returns relevant events. pub fn handle_mode_transition(&mut self) -> Option { - if let PowerCycleState::WaitingOff { - restore_mode, - since, - } = self.power_cycle - && since.elapsed() >= self.power_cycle_off_duration - { - self.power_cycle = PowerCycleState::SwitchingOn { restore_mode }; - self.start_transition_internal(restore_mode, None); + // The most probable case: Nothing to do. + if self.target().is_none() && !self.power_cycle_active() { + return None; } - let event = self.handle_switch_transition()?; - if self.power_cycle == PowerCycleState::Idle { - return Some(event); + // Handle this as an extra step so the switch transition after this can proceed. + self.handle_waiting_for_off_when_power_cycling(); + // Core logic: Command the switches, check whether target switch state was reached. + // Note the ?: if a switch transition is on-going, we might do an early return. + let outcome = self.handle_switch_transition()?; + // Regular mode transition without power cycling. + if self.power_cycle_state == PowerCycleState::Idle { + return Some(match outcome { + SwitchOutcome::Reached(tc_commander) => ModeTransitionEvent::Reached(tc_commander), + SwitchOutcome::Failed(tc_commander) => ModeTransitionEvent::Failed(tc_commander), + }); } - match event { - ModeTransitionEvent::Reached(_) => match self.power_cycle { - PowerCycleState::SwitchingOff { restore_mode } => { - self.power_cycle = PowerCycleState::WaitingOff { - restore_mode, - since: Instant::now(), - }; - None - } - _ => { - self.power_cycle = PowerCycleState::Idle; - Some(ModeTransitionEvent::PowerCycleDone) - } - }, - ModeTransitionEvent::Failed(_) => { - self.power_cycle = PowerCycleState::Idle; - Some(ModeTransitionEvent::PowerCycleFailed) + // Power cycling, where a bit more logic is required. + // Handle the error case first. + if let SwitchOutcome::Failed(_) = outcome { + self.power_cycle_state = PowerCycleState::Idle; + return Some(ModeTransitionEvent::PowerCycleFailed); + } + // At this point: The switching was succesfull, so we only match on the + // power cycle state. + match self.power_cycle_state { + // No switching going on for thse cases. + PowerCycleState::Idle | PowerCycleState::WaitingOff { .. } => None, + PowerCycleState::SwitchingOff { restore_mode } => { + self.power_cycle_state = PowerCycleState::WaitingOff { + restore_mode, + since: Instant::now(), + }; + None + } + PowerCycleState::SwitchingOn { .. } => { + // Power is back and we are done. + self.power_cycle_state = PowerCycleState::Idle; + Some(ModeTransitionEvent::PowerCycleDone) } - event => Some(event), } } - fn handle_switch_transition(&mut self) -> Option { + fn handle_waiting_for_off_when_power_cycling(&mut self) { + if let PowerCycleState::WaitingOff { + restore_mode, + since, + } = self.power_cycle_state + && since.elapsed() >= self.power_cycle_off_duration + { + self.power_cycle_state = PowerCycleState::SwitchingOn { restore_mode }; + self.start_transition_internal(restore_mode, None); + } + } + + fn handle_switch_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 { @@ -199,11 +226,11 @@ impl SwitchAndModeHelper { 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))); + return Some(SwitchOutcome::Failed(self.mode_helper.finish(false))); } } if self.mode_helper.transition_state == SwitchTransitionState::Done { - return Some(ModeTransitionEvent::Reached(self.mode_helper.finish(true))); + return Some(SwitchOutcome::Reached(self.mode_helper.finish(true))); } None }