1use std::collections::HashMap;
13use std::fmt;
14
15pub type CallbackFn = Box<dyn Fn()>;
17pub type GuardFn = Box<dyn Fn() -> bool>;
18pub type ActionFn = Box<dyn Fn()>;
19
20#[derive(Clone, PartialEq)]
22pub struct State {
23 pub name: String,
24 on_enter: Option<String>, on_exit: Option<String>,
26}
27
28impl State {
29 pub fn new(name: &str) -> Self {
31 State {
32 name: name.to_string(),
33 on_enter: None,
34 on_exit: None,
35 }
36 }
37
38 pub fn with_callbacks(name: &str, on_enter: Option<&str>, on_exit: Option<&str>) -> Self {
40 State {
41 name: name.to_string(),
42 on_enter: on_enter.map(|s| s.to_string()),
43 on_exit: on_exit.map(|s| s.to_string()),
44 }
45 }
46
47 pub fn enter(&self) {
49 println!("entering <{}>", self.name);
50 if let Some(ref callback) = self.on_enter {
51 println!(" executing on_enter: {}", callback);
52 }
53 }
54
55 pub fn exit(&self) {
57 println!("exiting <{}>", self.name);
58 if let Some(ref callback) = self.on_exit {
59 println!(" executing on_exit: {}", callback);
60 }
61 }
62}
63
64impl fmt::Display for State {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write!(f, "{}", self.name)
67 }
68}
69
70#[derive(Clone)]
72pub struct Transition {
73 pub src_state: String,
74 pub event: String,
75 pub dst_state: String,
76 pub guard: Option<String>,
77 pub action: Option<String>,
78}
79
80impl Transition {
81 pub fn new(src_state: &str, event: &str, dst_state: &str) -> Self {
82 Transition {
83 src_state: src_state.to_string(),
84 event: event.to_string(),
85 dst_state: dst_state.to_string(),
86 guard: None,
87 action: None,
88 }
89 }
90
91 pub fn with_guard(mut self, guard: &str) -> Self {
92 self.guard = Some(guard.to_string());
93 self
94 }
95
96 pub fn with_action(mut self, action: &str) -> Self {
97 self.action = Some(action.to_string());
98 self
99 }
100}
101
102pub struct StateMachine {
104 name: String,
105 states: HashMap<String, State>,
106 events: HashMap<String, String>,
107 transitions: HashMap<(String, String), Transition>,
108 current_state: Option<State>,
109 transition_history: Vec<(String, String, String)>, }
111
112impl StateMachine {
113 pub fn new(name: &str) -> Self {
115 StateMachine {
116 name: name.to_string(),
117 states: HashMap::new(),
118 events: HashMap::new(),
119 transitions: HashMap::new(),
120 current_state: None,
121 transition_history: Vec::new(),
122 }
123 }
124
125 pub fn register_state(&mut self, state: State) {
127 self.states.insert(state.name.clone(), state);
128 }
129
130 pub fn register_event(&mut self, event: &str) {
132 self.events.insert(event.to_string(), event.to_string());
133 }
134
135 pub fn add_transition(&mut self, transition: Transition) {
137 if !self.states.contains_key(&transition.src_state) {
139 self.register_state(State::new(&transition.src_state));
140 }
141 if !self.states.contains_key(&transition.dst_state) {
142 self.register_state(State::new(&transition.dst_state));
143 }
144 self.register_event(&transition.event);
145
146 let key = (transition.src_state.clone(), transition.event.clone());
147 self.transitions.insert(key, transition);
148 }
149
150 pub fn set_initial_state(&mut self, state_name: &str) {
152 if let Some(state) = self.states.get(state_name) {
153 self.current_state = Some(state.clone());
154 println!("|{}| initial state set to <{}>", self.name, state_name);
155 state.enter();
156 } else {
157 panic!("State '{}' not found", state_name);
158 }
159 }
160
161 pub fn get_current_state(&self) -> Option<&State> {
163 self.current_state.as_ref()
164 }
165
166 pub fn process(&mut self, event: &str) -> Result<(), String> {
168 if let Some(ref current_state) = self.current_state {
169 let key = (current_state.name.clone(), event.to_string());
170
171 if let Some(transition) = self.transitions.get(&key).cloned() {
172 self.execute_transition(&transition, event)
173 } else {
174 Err(format!(
175 "|{}| invalid transition: <{}> : [{}]",
176 self.name, current_state.name, event
177 ))
178 }
179 } else {
180 Err("State machine is not initialized".to_string())
181 }
182 }
183
184 fn execute_transition(&mut self, transition: &Transition, event: &str) -> Result<(), String> {
186 let current_state = self
188 .current_state
189 .as_ref()
190 .expect("execute_transition called without current_state");
191
192 if let Some(ref guard) = transition.guard {
194 println!(" checking guard: {}", guard);
195 if !self.evaluate_guard(guard) {
197 println!(
198 "|{}| skipping transition from <{}> to <{}> because guard [{}] failed",
199 self.name, current_state.name, transition.dst_state, guard
200 );
201 return Ok(());
202 }
203 }
204
205 if let Some(ref action) = transition.action {
207 println!(" executing action: {}", action);
208 self.execute_action(action);
209 }
210
211 if current_state.name != transition.dst_state {
213 println!(
214 "|{}| transitioning from <{}> to <{}> on event [{}]",
215 self.name, current_state.name, transition.dst_state, event
216 );
217
218 self.transition_history.push((
220 current_state.name.clone(),
221 event.to_string(),
222 transition.dst_state.clone(),
223 ));
224
225 current_state.exit();
227
228 if let Some(new_state) = self.states.get(&transition.dst_state) {
230 self.current_state = Some(new_state.clone());
231 new_state.enter();
232 } else {
233 return Err(format!(
234 "Destination state '{}' not found",
235 transition.dst_state
236 ));
237 }
238 } else {
239 println!(
240 "|{}| self-transition on <{}> with event [{}]",
241 self.name, current_state.name, event
242 );
243 }
244
245 Ok(())
246 }
247
248 fn evaluate_guard(&self, guard: &str) -> bool {
250 match guard {
251 "can_start" => true,
252 "has_battery" => true,
253 "obstacle_detected" => false,
254 "goal_reached" => false,
255 "emergency" => false,
256 _ => true, }
258 }
259
260 fn execute_action(&self, action: &str) {
262 match action {
263 "start_motors" => println!(" -> Starting motors"),
264 "stop_motors" => println!(" -> Stopping motors"),
265 "play_sound" => println!(" -> Playing notification sound"),
266 "save_position" => println!(" -> Saving current position"),
267 "send_alert" => println!(" -> Sending emergency alert"),
268 _ => println!(" -> Executing action: {}", action),
269 }
270 }
271
272 pub fn get_transition_history(&self) -> &Vec<(String, String, String)> {
274 &self.transition_history
275 }
276
277 pub fn generate_diagram(&self) -> String {
279 let mut diagram = Vec::new();
280 diagram.push(format!("State Machine: {}", self.name));
281 diagram.push("".to_string());
282
283 if let Some(ref current) = self.current_state {
284 diagram.push(format!("Current State: {}", current.name));
285 diagram.push("".to_string());
286 }
287
288 diagram.push("States:".to_string());
289 for state in self.states.values() {
290 let marker = if Some(state) == self.current_state.as_ref() {
291 " [CURRENT]"
292 } else {
293 ""
294 };
295 diagram.push(format!(" - {}{}", state.name, marker));
296 }
297 diagram.push("".to_string());
298
299 diagram.push("Transitions:".to_string());
300 for transition in self.transitions.values() {
301 let mut trans_str = format!(
302 " {} --[{}]--> {}",
303 transition.src_state, transition.event, transition.dst_state
304 );
305
306 if let Some(ref guard) = transition.guard {
307 trans_str.push_str(&format!(" [guard: {}]", guard));
308 }
309 if let Some(ref action) = transition.action {
310 trans_str.push_str(&format!(" / {}", action));
311 }
312 diagram.push(trans_str);
313 }
314
315 diagram.join("\n")
316 }
317
318 pub fn visualize(&self, filename: &str) {
320 use std::io::Write;
321
322 let mut state_names: Vec<_> = self.states.keys().cloned().collect();
324 state_names.sort();
325 let n_states = state_names.len();
326
327 if n_states == 0 {
328 return;
329 }
330
331 let width = 640.0;
333 let height = 640.0;
334 let cx = width / 2.0;
335 let cy = height / 2.0;
336 let radius = 200.0; let node_radius = 40.0;
338
339 let mut state_positions: HashMap<String, (f64, f64)> = HashMap::new();
341 for (i, state_name) in state_names.iter().enumerate() {
342 let angle = std::f64::consts::PI / 2.0
343 - 2.0 * std::f64::consts::PI * i as f64 / n_states as f64;
344 let x = cx + radius * angle.cos();
345 let y = cy - radius * angle.sin(); state_positions.insert(state_name.clone(), (x, y));
347 }
348
349 let mut svg = String::new();
350
351 svg.push_str(&format!(
353 r##"<?xml version="1.0" encoding="UTF-8"?>
354<svg xmlns="http://www.w3.org/2000/svg" width="{}" height="{}" viewBox="0 0 {} {}">
355 <defs>
356 <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
357 <polygon points="0 0, 10 3.5, 0 7" fill="#666"/>
358 </marker>
359 </defs>
360 <rect width="100%" height="100%" fill="white"/>
361 <text x="{}" y="30" text-anchor="middle" font-family="Arial" font-size="18" font-weight="bold">Robot State Machine</text>
362"##,
363 width, height, width, height, cx
364 ));
365
366 for transition in self.transitions.values() {
368 if let (Some(&(x1, y1)), Some(&(x2, y2))) = (
369 state_positions.get(&transition.src_state),
370 state_positions.get(&transition.dst_state),
371 ) {
372 if transition.src_state != transition.dst_state {
373 let dx = x2 - x1;
374 let dy = y2 - y1;
375 let dist = (dx * dx + dy * dy).sqrt();
376
377 let start_x = x1 + (node_radius + 5.0) * dx / dist;
379 let start_y = y1 + (node_radius + 5.0) * dy / dist;
380 let end_x = x2 - (node_radius + 10.0) * dx / dist;
381 let end_y = y2 - (node_radius + 10.0) * dy / dist;
382
383 svg.push_str(&format!(
385 r##" <line x1="{:.1}" y1="{:.1}" x2="{:.1}" y2="{:.1}" stroke="#666" stroke-width="2" marker-end="url(#arrowhead)"/>
386"##,
387 start_x, start_y, end_x, end_y
388 ));
389
390 let mid_x = (start_x + end_x) / 2.0;
392 let mid_y = (start_y + end_y) / 2.0;
393 let perp_x = -dy / dist * 15.0;
394 let perp_y = dx / dist * 15.0;
395
396 svg.push_str(&format!(
397 r##" <text x="{:.1}" y="{:.1}" text-anchor="middle" font-family="Arial" font-size="11" fill="#0066cc">{}</text>
398"##,
399 mid_x + perp_x, mid_y + perp_y, transition.event
400 ));
401 }
402 }
403 }
404
405 for state_name in &state_names {
407 if let Some(&(x, y)) = state_positions.get(state_name) {
408 svg.push_str(&format!(
410 r##" <circle cx="{:.1}" cy="{:.1}" r="{}" fill="#4682b4" stroke="#2c5574" stroke-width="2"/>
411"##,
412 x, y, node_radius
413 ));
414
415 svg.push_str(&format!(
417 " <text x=\"{:.1}\" y=\"{:.1}\" text-anchor=\"middle\" dominant-baseline=\"middle\" font-family=\"Arial\" font-size=\"12\" fill=\"white\" font-weight=\"bold\">{}</text>\n",
418 x, y, state_name
419 ));
420 }
421 }
422
423 svg.push_str(&format!(
425 r##" <text x="{}" y="{}" text-anchor="middle" font-family="Arial" font-size="10" fill="#666">States: circles | Transitions: arrows with event labels</text>
426"##,
427 cx, height - 15.0
428 ));
429
430 svg.push_str("</svg>\n");
431
432 let output_filename = if filename.ends_with(".png") {
434 filename.replace(".png", ".svg")
435 } else {
436 filename.to_string()
437 };
438
439 if let Ok(mut file) = std::fs::File::create(&output_filename) {
441 let _ = file.write_all(svg.as_bytes());
442 println!("State machine diagram saved to {}", output_filename);
443 }
444 }
445}
446
447pub struct RobotBehavior {
449 pub battery_level: f64,
450 pub has_obstacle: bool,
451 pub goal_reached: bool,
452 pub emergency_stop: bool,
453}
454
455impl RobotBehavior {
456 pub fn new() -> Self {
457 RobotBehavior {
458 battery_level: 100.0,
459 has_obstacle: false,
460 goal_reached: false,
461 emergency_stop: false,
462 }
463 }
464
465 pub fn can_start(&self) -> bool {
466 self.battery_level > 20.0 && !self.emergency_stop
467 }
468
469 pub fn has_battery(&self) -> bool {
470 self.battery_level > 10.0
471 }
472
473 pub fn obstacle_detected(&self) -> bool {
474 self.has_obstacle
475 }
476}
477
478impl Default for RobotBehavior {
479 fn default() -> Self {
480 Self::new()
481 }
482}
483
484pub fn create_robot_state_machine() -> StateMachine {
486 let mut machine = StateMachine::new("RobotController");
487
488 machine.register_state(State::with_callbacks(
490 "idle",
491 Some("on_enter_idle"),
492 Some("on_exit_idle"),
493 ));
494 machine.register_state(State::with_callbacks(
495 "moving",
496 Some("on_enter_moving"),
497 Some("on_exit_moving"),
498 ));
499 machine.register_state(State::with_callbacks(
500 "avoiding",
501 Some("on_enter_avoiding"),
502 None,
503 ));
504 machine.register_state(State::with_callbacks(
505 "charging",
506 Some("on_enter_charging"),
507 None,
508 ));
509 machine.register_state(State::with_callbacks(
510 "emergency",
511 Some("on_enter_emergency"),
512 None,
513 ));
514
515 machine.add_transition(
517 Transition::new("idle", "start", "moving")
518 .with_guard("can_start")
519 .with_action("start_motors"),
520 );
521
522 machine.add_transition(
523 Transition::new("moving", "obstacle", "avoiding").with_action("stop_motors"),
524 );
525
526 machine
527 .add_transition(Transition::new("avoiding", "clear", "moving").with_action("start_motors"));
528
529 machine.add_transition(
530 Transition::new("moving", "low_battery", "charging").with_action("save_position"),
531 );
532
533 machine
534 .add_transition(Transition::new("charging", "charged", "idle").with_action("play_sound"));
535
536 machine.add_transition(Transition::new("moving", "stop", "idle").with_action("stop_motors"));
537
538 machine.add_transition(
539 Transition::new("idle", "emergency", "emergency").with_action("send_alert"),
540 );
541
542 machine.add_transition(
543 Transition::new("moving", "emergency", "emergency").with_action("send_alert"),
544 );
545
546 machine.add_transition(Transition::new("emergency", "reset", "idle"));
547
548 machine
549}
550
551pub fn demo_state_machine() {
553 println!("=== Robot State Machine Demo ===\n");
554
555 std::fs::create_dir_all("img/mission_planning").unwrap_or_default();
557
558 let mut machine = create_robot_state_machine();
559
560 machine.set_initial_state("idle");
562
563 println!("\n{}\n", machine.generate_diagram());
564
565 let events = vec![
567 "start",
568 "obstacle",
569 "clear",
570 "low_battery",
571 "charged",
572 "start",
573 "emergency",
574 "reset",
575 ];
576
577 println!("=== Processing Events ===\n");
578
579 for event in events {
580 println!("Processing event: [{}]", event);
581 match machine.process(event) {
582 Ok(()) => println!(" -> Success\n"),
583 Err(e) => println!(" -> Error: {}\n", e),
584 }
585 }
586
587 if let Some(current) = machine.get_current_state() {
589 println!("Final state: {}", current.name);
590 }
591
592 println!("\n=== Transition History ===");
593 for (i, (from, event, to)) in machine.get_transition_history().iter().enumerate() {
594 println!("{}. {} --[{}]--> {}", i + 1, from, event, to);
595 }
596
597 machine.visualize("img/mission_planning/state_machine_diagram.png");
599 println!("\nState machine diagram saved to img/mission_planning/state_machine_diagram.png");
600
601 println!("\n=== Final State Machine ===");
603 println!("{}", machine.generate_diagram());
604}
605
606#[cfg(test)]
607mod tests {
608 use super::*;
609
610 #[test]
611 fn test_state_creation() {
612 let state = State::new("test_state");
613 assert_eq!(state.name, "test_state");
614 }
615
616 #[test]
617 fn test_state_machine_creation() {
618 let machine = StateMachine::new("test_machine");
619 assert_eq!(machine.name, "test_machine");
620 assert!(machine.current_state.is_none());
621 }
622
623 #[test]
624 fn test_state_registration() {
625 let mut machine = StateMachine::new("test");
626 let state = State::new("idle");
627 machine.register_state(state);
628 assert!(machine.states.contains_key("idle"));
629 }
630
631 #[test]
632 fn test_transition_creation() {
633 let transition = Transition::new("idle", "start", "running")
634 .with_guard("can_start")
635 .with_action("start_motors");
636
637 assert_eq!(transition.src_state, "idle");
638 assert_eq!(transition.event, "start");
639 assert_eq!(transition.dst_state, "running");
640 assert_eq!(transition.guard, Some("can_start".to_string()));
641 assert_eq!(transition.action, Some("start_motors".to_string()));
642 }
643
644 #[test]
645 fn test_simple_transition() {
646 let mut machine = StateMachine::new("test");
647
648 machine.add_transition(Transition::new("idle", "start", "running"));
649 machine.set_initial_state("idle");
650
651 assert!(machine.process("start").is_ok());
652 assert_eq!(machine.get_current_state().unwrap().name, "running");
653 }
654
655 #[test]
656 fn test_robot_state_machine_follows_demo_transitions() {
657 let mut machine = create_robot_state_machine();
658 machine.set_initial_state("idle");
659
660 assert!(machine.process("start").is_ok());
661 assert_eq!(machine.get_current_state().unwrap().name, "moving");
662
663 assert!(machine.process("low_battery").is_ok());
664 assert_eq!(machine.get_current_state().unwrap().name, "charging");
665
666 let history = machine.get_transition_history();
667 assert_eq!(history.len(), 2);
668 assert_eq!(
669 history.last(),
670 Some(&(
671 "moving".to_string(),
672 "low_battery".to_string(),
673 "charging".to_string()
674 ))
675 );
676 }
677}