use crate::fb::StateMachine;
use super::axis_view::AxisHandle;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WaitStatus {
Pending,
Done,
Error(String),
Timeout,
}
pub struct AxisWait;
impl AxisWait {
pub fn poll<A: AxisHandle + ?Sized>(axis: &A, state: &StateMachine) -> WaitStatus {
if !axis.is_busy() {
if axis.is_error() {
WaitStatus::Error(axis.error_message())
} else {
WaitStatus::Done
}
} else if state.timed_out() {
WaitStatus::Timeout
} else {
WaitStatus::Pending
}
}
pub fn resolve<A: AxisHandle + ?Sized>(
axis: &A,
state: &StateMachine,
follow_up: &mut Option<i32>,
default_next: i32,
error_next: i32,
label: &str,
) -> Option<i32> {
match Self::poll(axis, state) {
WaitStatus::Pending => None,
WaitStatus::Done => {
let next = follow_up.take().unwrap_or(default_next);
log::info!("{label} command complete → state {next}");
Some(next)
}
WaitStatus::Error(msg) => {
log::error!("{label} error: {msg}");
Some(error_next)
}
WaitStatus::Timeout => {
log::error!("Timeout waiting for {label}");
Some(error_next)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::motion::axis_config::AxisConfig;
struct MockAxis {
busy: bool,
error: bool,
message: String,
config: AxisConfig,
}
impl Default for MockAxis {
fn default() -> Self {
Self {
busy: false,
error: false,
message: String::new(),
config: AxisConfig::new(1000),
}
}
}
impl AxisHandle for MockAxis {
fn position(&self) -> f64 { 0.0 }
fn config(&self) -> &AxisConfig { &self.config }
fn move_relative(&mut self, _: f64, _: f64, _: f64, _: f64) {}
fn move_absolute(&mut self, _: f64, _: f64, _: f64, _: f64) {}
fn halt(&mut self) {}
fn is_busy(&self) -> bool { self.busy }
fn is_error(&self) -> bool { self.error }
fn error_message(&self) -> String { self.message.clone() }
fn motor_on(&self) -> bool { true }
}
#[test]
fn poll_pending_when_busy_and_not_timed_out() {
let axis = MockAxis { busy: true, ..Default::default() };
let state = StateMachine::new();
assert_eq!(AxisWait::poll(&axis, &state), WaitStatus::Pending);
}
#[test]
fn poll_done_when_idle_and_no_error() {
let axis = MockAxis { busy: false, error: false, ..Default::default() };
let state = StateMachine::new();
assert_eq!(AxisWait::poll(&axis, &state), WaitStatus::Done);
}
#[test]
fn poll_error_takes_priority_over_done_when_idle() {
let axis = MockAxis {
busy: false, error: true,
message: "fault: overcurrent".into(),
..Default::default()
};
let state = StateMachine::new();
assert_eq!(
AxisWait::poll(&axis, &state),
WaitStatus::Error("fault: overcurrent".to_string()),
);
}
#[test]
fn poll_timeout_only_when_still_busy() {
let mut state = StateMachine::new();
state.timeout_preset = std::time::Duration::from_millis(0);
std::thread::sleep(std::time::Duration::from_millis(2));
let busy_axis = MockAxis { busy: true, ..Default::default() };
assert_eq!(AxisWait::poll(&busy_axis, &state), WaitStatus::Timeout);
let idle_axis = MockAxis { busy: false, ..Default::default() };
assert_eq!(AxisWait::poll(&idle_axis, &state), WaitStatus::Done);
}
#[test]
fn resolve_returns_none_when_pending() {
let axis = MockAxis { busy: true, ..Default::default() };
let state = StateMachine::new();
let mut follow_up: Option<i32> = Some(42);
let next = AxisWait::resolve(&axis, &state, &mut follow_up, 0, 99, "test");
assert_eq!(next, None);
assert_eq!(follow_up, Some(42), "follow_up must not be consumed on Pending");
}
#[test]
fn resolve_consumes_follow_up_on_done() {
let axis = MockAxis { busy: false, ..Default::default() };
let state = StateMachine::new();
let mut follow_up: Option<i32> = Some(42);
let next = AxisWait::resolve(&axis, &state, &mut follow_up, 0, 99, "test");
assert_eq!(next, Some(42));
assert_eq!(follow_up, None);
}
#[test]
fn resolve_uses_default_when_no_follow_up_on_done() {
let axis = MockAxis { busy: false, ..Default::default() };
let state = StateMachine::new();
let mut follow_up: Option<i32> = None;
let next = AxisWait::resolve(&axis, &state, &mut follow_up, 7, 99, "test");
assert_eq!(next, Some(7));
}
#[test]
fn resolve_routes_to_error_next_on_error() {
let axis = MockAxis {
busy: false, error: true,
message: "x".into(), ..Default::default()
};
let state = StateMachine::new();
let mut follow_up: Option<i32> = Some(42);
let next = AxisWait::resolve(&axis, &state, &mut follow_up, 0, 99, "test");
assert_eq!(next, Some(99));
assert_eq!(follow_up, Some(42), "follow_up retained on Error so caller can re-attempt");
}
}