#![allow(dead_code)]
use crate::safety::fault::FaultSeverity;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum MachineState {
Normal,
Degraded,
SafeState,
EmergencyStop,
}
impl MachineState {
pub fn is_latching(self) -> bool {
matches!(self, MachineState::SafeState | MachineState::EmergencyStop)
}
pub fn inhibits_actuators(self) -> bool {
matches!(self, MachineState::SafeState | MachineState::EmergencyStop)
}
pub fn is_operational(self) -> bool {
matches!(self, MachineState::Normal | MachineState::Degraded)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SafeStateAction {
Continue,
EnterDegraded,
EnterSafeState,
EmergencyStop,
}
#[derive(Debug, Clone, Copy)]
pub struct SafeStateConfig {
pub degraded_threshold: FaultSeverity,
pub safe_state_threshold: FaultSeverity,
pub emergency_stop_threshold: FaultSeverity,
pub reset_key: u32,
}
impl SafeStateConfig {
pub fn new(
degraded_threshold: FaultSeverity,
safe_state_threshold: FaultSeverity,
emergency_stop_threshold: FaultSeverity,
reset_key: u32,
) -> Self {
Self {
degraded_threshold,
safe_state_threshold,
emergency_stop_threshold,
reset_key,
}
}
pub fn default_sil2() -> Self {
Self::new(
FaultSeverity::Warning,
FaultSeverity::Error,
FaultSeverity::Critical,
0xDEAD_BEEF,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SafeError {
InvalidResetKey,
NotInLatchingState,
EmergencyStopRequiresFullReset,
}
impl core::fmt::Display for SafeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SafeError::InvalidResetKey => write!(f, "safe-state reset rejected: invalid key"),
SafeError::NotInLatchingState => {
write!(f, "safe-state reset not needed: machine is not latching")
}
SafeError::EmergencyStopRequiresFullReset => write!(
f,
"EmergencyStop requires emergency_stop_reset(), not safe_state_reset()"
),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SafeStateMachineExt {
config: SafeStateConfig,
state: MachineState,
state_cycles: u32,
total_faults: u32,
}
impl SafeStateMachineExt {
pub fn new(config: SafeStateConfig) -> Self {
Self {
config,
state: MachineState::Normal,
state_cycles: 0,
total_faults: 0,
}
}
pub fn state(&self) -> MachineState {
self.state
}
pub fn state_cycles(&self) -> u32 {
self.state_cycles
}
pub fn total_faults(&self) -> u32 {
self.total_faults
}
pub fn process_fault(
&mut self,
fault_severity: FaultSeverity,
safety_function_active: bool,
) -> SafeStateAction {
self.total_faults = self.total_faults.saturating_add(1);
let effective_severity = if safety_function_active {
fault_severity
} else {
escalate(fault_severity)
};
let required_action = self.severity_to_action(effective_severity);
let required_state = action_to_state(required_action);
if required_state > self.state {
let prev = self.state;
self.state = required_state;
if prev != self.state {
self.state_cycles = 0;
}
}
self.state_cycles = self.state_cycles.saturating_add(1);
state_to_action(self.state)
}
fn severity_to_action(&self, severity: FaultSeverity) -> SafeStateAction {
if severity >= self.config.emergency_stop_threshold {
SafeStateAction::EmergencyStop
} else if severity >= self.config.safe_state_threshold {
SafeStateAction::EnterSafeState
} else if severity >= self.config.degraded_threshold {
SafeStateAction::EnterDegraded
} else {
SafeStateAction::Continue
}
}
pub fn reset(&mut self, key: u32) -> Result<(), SafeError> {
if !self.state.is_latching() {
return Err(SafeError::NotInLatchingState);
}
if self.state == MachineState::EmergencyStop {
return Err(SafeError::EmergencyStopRequiresFullReset);
}
if key != self.config.reset_key {
return Err(SafeError::InvalidResetKey);
}
self.state = MachineState::Normal;
self.state_cycles = 0;
Ok(())
}
pub fn emergency_stop_reset(&mut self, key: u32) -> Result<(), SafeError> {
if self.state != MachineState::EmergencyStop {
return Err(SafeError::NotInLatchingState);
}
if key != self.config.reset_key {
return Err(SafeError::InvalidResetKey);
}
self.state = MachineState::Normal;
self.state_cycles = 0;
Ok(())
}
#[cfg(test)]
pub fn force_reset_for_test(&mut self) {
self.state = MachineState::Normal;
self.state_cycles = 0;
self.total_faults = 0;
}
}
fn escalate(s: FaultSeverity) -> FaultSeverity {
match s {
FaultSeverity::Info => FaultSeverity::Warning,
FaultSeverity::Warning => FaultSeverity::Error,
FaultSeverity::Error | FaultSeverity::Critical => FaultSeverity::Critical,
}
}
fn action_to_state(a: SafeStateAction) -> MachineState {
match a {
SafeStateAction::Continue => MachineState::Normal,
SafeStateAction::EnterDegraded => MachineState::Degraded,
SafeStateAction::EnterSafeState => MachineState::SafeState,
SafeStateAction::EmergencyStop => MachineState::EmergencyStop,
}
}
fn state_to_action(s: MachineState) -> SafeStateAction {
match s {
MachineState::Normal => SafeStateAction::Continue,
MachineState::Degraded => SafeStateAction::EnterDegraded,
MachineState::SafeState => SafeStateAction::EnterSafeState,
MachineState::EmergencyStop => SafeStateAction::EmergencyStop,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_fsm() -> SafeStateMachineExt {
SafeStateMachineExt::new(SafeStateConfig::default_sil2())
}
#[test]
fn starts_in_normal() {
let fsm = default_fsm();
assert_eq!(fsm.state(), MachineState::Normal);
assert!(fsm.state().is_operational());
}
#[test]
fn info_fault_stays_normal() {
let mut fsm = default_fsm();
let action = fsm.process_fault(FaultSeverity::Info, true);
assert_eq!(action, SafeStateAction::Continue);
assert_eq!(fsm.state(), MachineState::Normal);
}
#[test]
fn warning_fault_enters_degraded() {
let mut fsm = default_fsm();
let action = fsm.process_fault(FaultSeverity::Warning, true);
assert_eq!(action, SafeStateAction::EnterDegraded);
assert_eq!(fsm.state(), MachineState::Degraded);
}
#[test]
fn error_fault_enters_safe_state() {
let mut fsm = default_fsm();
let action = fsm.process_fault(FaultSeverity::Error, true);
assert_eq!(action, SafeStateAction::EnterSafeState);
assert_eq!(fsm.state(), MachineState::SafeState);
assert!(fsm.state().inhibits_actuators());
}
#[test]
fn critical_fault_enters_emergency_stop() {
let mut fsm = default_fsm();
let action = fsm.process_fault(FaultSeverity::Critical, true);
assert_eq!(action, SafeStateAction::EmergencyStop);
assert_eq!(fsm.state(), MachineState::EmergencyStop);
assert!(fsm.state().inhibits_actuators());
}
#[test]
fn safe_state_latches_against_lower_faults() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Error, true); fsm.process_fault(FaultSeverity::Info, true); assert_eq!(fsm.state(), MachineState::SafeState);
}
#[test]
fn emergency_stop_latches_against_all_lower_faults() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Critical, true); fsm.process_fault(FaultSeverity::Warning, true); assert_eq!(fsm.state(), MachineState::EmergencyStop);
}
#[test]
fn safe_state_escalates_to_emergency_on_critical() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Error, true); fsm.process_fault(FaultSeverity::Critical, true); assert_eq!(fsm.state(), MachineState::EmergencyStop);
}
#[test]
fn keyed_reset_from_safe_state_succeeds() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Error, true); let result = fsm.reset(0xDEAD_BEEF);
assert!(result.is_ok());
assert_eq!(fsm.state(), MachineState::Normal);
}
#[test]
fn keyed_reset_from_safe_state_wrong_key() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Error, true); let result = fsm.reset(0x1234_5678);
assert_eq!(result, Err(SafeError::InvalidResetKey));
assert_eq!(fsm.state(), MachineState::SafeState); }
#[test]
fn reset_from_safe_state_blocked_in_estop() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Critical, true); let result = fsm.reset(0xDEAD_BEEF);
assert_eq!(result, Err(SafeError::EmergencyStopRequiresFullReset));
}
#[test]
fn reset_from_normal_is_error() {
let mut fsm = default_fsm();
let result = fsm.reset(0xDEAD_BEEF);
assert_eq!(result, Err(SafeError::NotInLatchingState));
}
#[test]
fn emergency_stop_reset_correct_key() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Critical, true); let result = fsm.emergency_stop_reset(0xDEAD_BEEF);
assert!(result.is_ok());
assert_eq!(fsm.state(), MachineState::Normal);
}
#[test]
fn emergency_stop_reset_wrong_key() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Critical, true);
let result = fsm.emergency_stop_reset(0x0000_0001);
assert_eq!(result, Err(SafeError::InvalidResetKey));
assert_eq!(fsm.state(), MachineState::EmergencyStop);
}
#[test]
fn emergency_stop_reset_when_not_in_estop() {
let mut fsm = default_fsm();
let result = fsm.emergency_stop_reset(0xDEAD_BEEF);
assert_eq!(result, Err(SafeError::NotInLatchingState));
}
#[test]
fn info_fault_escalates_to_warning_when_sf_inactive() {
let mut fsm = default_fsm();
let action = fsm.process_fault(FaultSeverity::Info, false);
assert_eq!(action, SafeStateAction::EnterDegraded);
}
#[test]
fn warning_fault_escalates_to_error_when_sf_inactive() {
let mut fsm = default_fsm();
let action = fsm.process_fault(FaultSeverity::Warning, false);
assert_eq!(action, SafeStateAction::EnterSafeState);
}
#[test]
fn error_fault_escalates_to_critical_when_sf_inactive() {
let mut fsm = default_fsm();
let action = fsm.process_fault(FaultSeverity::Error, false);
assert_eq!(action, SafeStateAction::EmergencyStop);
}
#[test]
fn state_cycles_increment_each_call() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Info, true);
fsm.process_fault(FaultSeverity::Info, true);
assert_eq!(fsm.state_cycles(), 2);
}
#[test]
fn state_cycles_reset_on_state_change() {
let mut fsm = default_fsm();
fsm.process_fault(FaultSeverity::Info, true); fsm.process_fault(FaultSeverity::Warning, true); assert_eq!(fsm.state_cycles(), 1);
}
#[test]
fn total_faults_counts_all_calls() {
let mut fsm = default_fsm();
for _ in 0..5 {
fsm.process_fault(FaultSeverity::Info, true);
}
assert_eq!(fsm.total_faults(), 5);
}
#[test]
fn state_is_latching_flags() {
assert!(!MachineState::Normal.is_latching());
assert!(!MachineState::Degraded.is_latching());
assert!(MachineState::SafeState.is_latching());
assert!(MachineState::EmergencyStop.is_latching());
}
#[test]
fn state_inhibits_actuators_flags() {
assert!(!MachineState::Normal.inhibits_actuators());
assert!(!MachineState::Degraded.inhibits_actuators());
assert!(MachineState::SafeState.inhibits_actuators());
assert!(MachineState::EmergencyStop.inhibits_actuators());
}
}