use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct StallDetector<S: ControlScalar> {
pub commanded_steps: i64,
pub stall_threshold: u32,
stall_count: u32,
stalled: bool,
filtered_current: S,
alpha: S,
pub nominal_current: S,
pub stall_current_ratio: S,
pub min_speed: S,
current_speed: S,
}
impl<S: ControlScalar> StallDetector<S> {
pub fn new(
nominal_current: S,
stall_current_ratio: S,
stall_threshold: u32,
min_speed: S,
) -> Self {
Self {
commanded_steps: 0,
stall_threshold,
stall_count: 0,
stalled: false,
filtered_current: S::ZERO,
alpha: S::from_f64(0.1),
nominal_current,
stall_current_ratio,
min_speed,
current_speed: S::ZERO,
}
}
pub fn command_steps(&mut self, steps: i32) {
self.commanded_steps += steps as i64;
}
pub fn update(&mut self, measured_current: S, speed: S) -> bool {
self.current_speed = speed;
self.filtered_current =
self.alpha * measured_current + (S::ONE - self.alpha) * self.filtered_current;
if speed.abs() < self.min_speed {
self.stall_count = 0;
return self.stalled;
}
let ratio = if self.nominal_current > S::ZERO {
self.filtered_current / self.nominal_current
} else {
S::ZERO
};
if ratio > self.stall_current_ratio {
self.stall_count = self.stall_count.saturating_add(1);
} else {
self.stall_count = self.stall_count.saturating_sub(1);
}
if self.stall_count >= self.stall_threshold {
self.stalled = true;
}
self.stalled
}
pub fn is_stalled(&self) -> bool {
self.stalled
}
pub fn clear_stall(&mut self) {
self.stalled = false;
self.stall_count = 0;
}
pub fn reset(&mut self) {
self.commanded_steps = 0;
self.stall_count = 0;
self.stalled = false;
self.filtered_current = S::ZERO;
self.current_speed = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nominal_current_no_stall() {
let mut det = StallDetector::new(2.0_f64, 1.5, 10, 100.0);
for _ in 0..100 {
det.command_steps(1);
det.update(2.0, 500.0); }
assert!(!det.is_stalled());
}
#[test]
fn high_current_triggers_stall() {
let mut det = StallDetector::new(2.0_f64, 1.5, 10, 100.0);
for _ in 0..200 {
det.command_steps(1);
det.update(4.0, 500.0); }
assert!(det.is_stalled());
}
#[test]
fn low_speed_skips_detection() {
let mut det = StallDetector::new(2.0_f64, 1.5, 10, 100.0);
for _ in 0..200 {
det.update(4.0, 50.0); }
assert!(!det.is_stalled());
}
#[test]
fn clear_stall_resets_flag() {
let mut det = StallDetector::new(2.0_f64, 1.5, 5, 100.0);
for _ in 0..100 {
det.update(4.0, 500.0);
}
assert!(det.is_stalled());
det.clear_stall();
assert!(!det.is_stalled());
}
}