use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct AbsoluteEncoder<S: ControlScalar> {
pub resolution: u32,
position: u32,
turns: i32,
omega: S,
theta_prev: S,
alpha: S,
initialized: bool,
}
impl<S: ControlScalar> AbsoluteEncoder<S> {
pub fn new(resolution: u32, alpha: S) -> Self {
Self {
resolution,
position: 0,
turns: 0,
omega: S::ZERO,
theta_prev: S::ZERO,
alpha,
initialized: false,
}
}
pub fn update(&mut self, raw: u32, dt: S) {
let raw = raw % self.resolution;
let half = self.resolution / 2;
if !self.initialized {
self.position = raw;
self.theta_prev = self.theta();
self.initialized = true;
return;
}
let prev = self.position;
if raw > prev + half {
self.turns -= 1;
} else if prev > raw + half {
self.turns += 1;
}
self.position = raw;
if dt > S::ZERO {
let theta_new = self.theta();
let dtheta = theta_new - self.theta_prev;
let pi = S::PI;
let two_pi = S::TWO * pi;
let dtheta = if dtheta > pi {
dtheta - two_pi
} else if dtheta < -pi {
dtheta + two_pi
} else {
dtheta
};
let omega_raw = dtheta / dt;
self.omega += self.alpha * (omega_raw - self.omega);
self.theta_prev = theta_new;
}
}
pub fn theta(&self) -> S {
let two_pi = S::TWO * S::PI;
let frac = S::from_f64(self.position as f64 / self.resolution as f64);
S::from_f64(self.turns as f64) * two_pi + frac * two_pi
}
pub fn angle_within_turn(&self) -> S {
S::from_f64(self.position as f64 / self.resolution as f64) * S::TWO * S::PI
}
pub fn omega(&self) -> S {
self.omega
}
pub fn turns(&self) -> i32 {
self.turns
}
pub fn raw_position(&self) -> u32 {
self.position
}
pub fn reset(&mut self) {
self.position = 0;
self.turns = 0;
self.omega = S::ZERO;
self.theta_prev = S::ZERO;
self.initialized = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts::PI;
#[test]
fn angle_within_turn_correct() {
let mut enc = AbsoluteEncoder::new(4096_u32, 1.0_f64);
enc.update(1024, 0.001); let expected = 2.0 * PI / 4.0;
assert!(
(enc.angle_within_turn() - expected).abs() < 0.01,
"angle={:.4}",
enc.angle_within_turn()
);
}
#[test]
fn multi_turn_tracking_forward() {
let mut enc = AbsoluteEncoder::new(4096_u32, 1.0_f64);
enc.update(3000, 0.001); enc.update(100, 0.001); assert_eq!(enc.turns(), 1, "Should count one forward turn");
}
#[test]
fn multi_turn_tracking_backward() {
let mut enc = AbsoluteEncoder::new(4096_u32, 1.0_f64);
enc.update(100, 0.001); enc.update(3900, 0.001); assert_eq!(enc.turns(), -1, "Should count one backward turn");
}
#[test]
fn velocity_estimation() {
let mut enc = AbsoluteEncoder::new(4096_u32, 1.0_f64);
let dt = 0.001_f64;
let omega_true = 2.0 * PI * 10.0; let counts_per_step = (omega_true * dt / (2.0 * PI) * 4096.0) as u32;
let mut pos = 0u32;
for _ in 0..200 {
pos = (pos + counts_per_step) % 4096;
enc.update(pos, dt);
}
assert!(
(enc.omega() - omega_true).abs() < 5.0,
"omega={:.2}, true={:.2}",
enc.omega(),
omega_true
);
}
#[test]
fn reset_clears_state() {
let mut enc = AbsoluteEncoder::new(4096_u32, 1.0_f64);
enc.update(2000, 0.001);
enc.update(100, 0.001);
enc.reset();
assert_eq!(enc.turns(), 0);
assert!((enc.omega()).abs() < 1e-10);
}
}