1#![allow(clippy::print_stdout, clippy::enum_glob_use)]
7
8use ready_active_safe::prelude::*;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11enum Mode {
12 Ready,
13 Active,
14 Safe,
15}
16
17impl core::fmt::Display for Mode {
18 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
19 write!(f, "{self:?}")
20 }
21}
22
23#[derive(Debug)]
24enum Event {
25 Activate,
26 TransientFault,
27 CriticalFault,
28 RecoveryApproved,
29 DiagnosticsComplete,
30 Shutdown,
31}
32
33#[derive(Debug, PartialEq)]
34enum Command {
35 StartProcessing,
36 LogFault,
37 EmergencyStop,
38 NotifyOperator,
39 RunRecoveryDiagnostics,
40 ResetSubsystems,
41 PowerDown,
42}
43
44struct SafetyController;
45
46impl Machine for SafetyController {
47 type Mode = Mode;
48 type Event = Event;
49 type Command = Command;
50
51 fn initial_mode(&self) -> Mode {
52 Mode::Ready
53 }
54
55 fn on_event(
56 &self,
57 mode: &Self::Mode,
58 event: &Self::Event,
59 ) -> Decision<Self::Mode, Self::Command> {
60 use Command::*;
61 use Event::*;
62 use Mode::*;
63
64 match (mode, event) {
65 (Ready, Activate) => transition(Active).emit(StartProcessing),
66 (Active, TransientFault) => stay().emit(LogFault),
67 (Active, CriticalFault) => transition(Safe).emit_all([EmergencyStop, NotifyOperator]),
68 (Safe, RecoveryApproved) => stay().emit(RunRecoveryDiagnostics),
69 (Safe, DiagnosticsComplete) => transition(Ready).emit(ResetSubsystems),
70 (Active | Ready, Shutdown) => transition(Safe).emit(PowerDown),
71 _ => ignore(),
72 }
73 }
74}
75
76struct LifecyclePolicy;
77
78impl Policy<Mode> for LifecyclePolicy {
79 fn is_allowed(&self, from: &Mode, to: &Mode) -> bool {
80 matches!(
81 (from, to),
82 (Mode::Ready, Mode::Active | Mode::Safe)
83 | (Mode::Active, Mode::Safe)
84 | (Mode::Safe, Mode::Ready)
85 )
86 }
87
88 fn check(&self, from: &Mode, to: &Mode) -> Result<(), &'static str> {
89 if self.is_allowed(from, to) {
90 return Ok(());
91 }
92
93 Err("this transition is not part of the allowed lifecycle")
94 }
95}
96
97fn main() -> Result<(), LifecycleError<Mode>> {
98 let controller = SafetyController;
99 let policy = LifecyclePolicy;
100 let mut mode = controller.initial_mode();
101
102 let events = [
103 Event::Activate,
104 Event::TransientFault,
105 Event::CriticalFault,
106 Event::RecoveryApproved,
107 Event::DiagnosticsComplete,
108 Event::Activate,
109 Event::Shutdown,
110 ];
111
112 for event in &events {
113 let decision = controller.decide(&mode, event);
114 println!("{mode:?} + {event:?} => {decision}");
115
116 match decision.apply_checked(mode, &policy) {
117 Ok((next_mode, commands)) => {
118 if !commands.is_empty() {
119 println!(" commands: {commands:?}");
120 }
121 mode = next_mode;
122 }
123 Err(LifecycleError::TransitionDenied { from, to, reason }) => {
124 println!(" denied: {from:?} -> {to:?} ({reason})");
125 mode = from;
126 }
127 Err(err) => return Err(err),
128 }
129 }
130
131 assert!(!policy.is_allowed(&Mode::Active, &Mode::Ready));
132 assert!(!policy.is_allowed(&Mode::Safe, &Mode::Active));
133 assert_eq!(mode, Mode::Safe);
134
135 Ok(())
136}