use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct StateMachine {
pub index: i32,
pub timer_preset: Duration,
pub timeout_preset: Duration,
pub error_code: i32,
pub message: String,
pub error_message: String,
last_index: Option<i32>,
state_entered_at: Option<Instant>,
}
impl StateMachine {
pub fn new() -> Self {
Self {
index: 0,
timer_preset: Duration::MAX,
timeout_preset: Duration::MAX,
error_code: 0,
message: String::new(),
error_message: String::new(),
last_index: None,
state_entered_at: None,
}
}
pub fn call(&mut self) {
if self.last_index != Some(self.index) {
self.state_entered_at = Some(Instant::now());
}
self.last_index = Some(self.index);
}
pub fn timer_done(&self) -> bool {
self.elapsed() >= self.timer_preset
}
pub fn timed_out(&self) -> bool {
self.elapsed() >= self.timeout_preset
}
pub fn elapsed(&self) -> Duration {
self.state_entered_at
.map(|t| t.elapsed())
.unwrap_or(Duration::ZERO)
}
pub fn is_error(&self) -> bool {
self.error_code != 0
}
pub fn set_error(&mut self, code: i32, message: impl Into<String>) {
self.error_code = code;
self.error_message = message.into();
}
pub fn clear_error(&mut self) {
self.error_code = 0;
self.error_message.clear();
}
pub fn state(&self) -> i32 {
self.index
}
}
impl Default for StateMachine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_machine_basic() {
let state = StateMachine::new();
assert_eq!(state.index, 0);
assert_eq!(state.error_code, 0);
assert!(!state.is_error());
assert_eq!(state.timer_preset, Duration::MAX);
assert_eq!(state.timeout_preset, Duration::MAX);
}
#[test]
fn test_state_machine_timer() {
let mut state = StateMachine::new();
state.timer_preset = Duration::from_millis(50);
state.call();
assert!(!state.timer_done());
std::thread::sleep(Duration::from_millis(60));
assert!(state.timer_done());
assert!(state.elapsed() >= Duration::from_millis(50));
}
#[test]
fn test_state_machine_timeout() {
let mut state = StateMachine::new();
state.timeout_preset = Duration::from_millis(50);
state.call();
assert!(!state.timed_out());
std::thread::sleep(Duration::from_millis(60));
assert!(state.timed_out());
}
#[test]
fn test_state_machine_timer_reset_on_state_change() {
let mut state = StateMachine::new();
state.timer_preset = Duration::from_millis(50);
state.call();
std::thread::sleep(Duration::from_millis(30));
let elapsed_before = state.elapsed();
assert!(elapsed_before >= Duration::from_millis(30));
state.index = 10;
state.call();
assert!(state.elapsed() < Duration::from_millis(20));
assert!(!state.timer_done());
}
#[test]
fn test_state_machine_error_handling() {
let mut state = StateMachine::new();
assert!(!state.is_error());
state.set_error(110, "Failed to home axis");
assert!(state.is_error());
assert_eq!(state.error_code, 110);
assert_eq!(state.error_message, "Failed to home axis");
state.clear_error();
assert!(!state.is_error());
assert_eq!(state.error_code, 0);
assert!(state.error_message.is_empty());
}
#[test]
fn test_state_machine_preset_persists() {
let mut state = StateMachine::new();
state.timer_preset = Duration::from_millis(50);
state.index = 10;
state.call();
assert_eq!(state.timer_preset, Duration::from_millis(50));
state.index = 20;
state.call();
assert_eq!(state.timer_preset, Duration::from_millis(50));
}
#[test]
fn test_state_machine_default_presets_never_trigger() {
let mut state = StateMachine::new();
state.call();
std::thread::sleep(Duration::from_millis(10));
assert!(!state.timer_done());
assert!(!state.timed_out());
}
}