use pamoja_core::{Actuator, Result, Sensor};
use pamoja_kit::{Odometry, Pose, Twist};
#[derive(Clone, Copy, Debug)]
pub struct SimRobot {
odometry: Odometry,
dt: f32,
}
impl SimRobot {
pub fn new(dt: f32) -> Self {
Self {
odometry: Odometry::at_origin(),
dt: dt.abs(),
}
}
pub fn starting_at(pose: Pose, dt: f32) -> Self {
Self {
odometry: Odometry::new(pose),
dt: dt.abs(),
}
}
pub fn pose(&self) -> Pose {
self.odometry.pose()
}
}
impl Actuator for SimRobot {
type Command = Twist;
async fn apply(&mut self, command: Twist) -> Result<()> {
self.odometry.integrate(command.vx, command.omega, self.dt);
Ok(())
}
}
impl Sensor for SimRobot {
type Reading = Pose;
async fn read(&mut self) -> Result<Pose> {
Ok(self.odometry.pose())
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::f32::consts::FRAC_PI_2;
#[tokio::test]
async fn driving_straight_advances_along_x() {
let mut robot = SimRobot::new(0.1);
for _ in 0..10 {
robot.apply(Twist::planar(1.0, 0.0)).await.unwrap();
}
let pose = robot.read().await.unwrap();
assert!((pose.x - 1.0).abs() < 1e-5);
assert!(pose.y.abs() < 1e-5);
}
#[tokio::test]
async fn turning_in_place_changes_only_the_heading() {
let mut robot = SimRobot::new(0.5);
robot.apply(Twist::planar(0.0, 1.0)).await.unwrap();
robot.apply(Twist::planar(0.0, 1.0)).await.unwrap();
let pose = robot.read().await.unwrap();
assert!(pose.x.abs() < 1e-6 && pose.y.abs() < 1e-6);
assert!((pose.theta - 1.0).abs() < 1e-6);
}
#[tokio::test]
async fn a_quarter_circle_lands_at_the_arc_corner() {
let mut robot = SimRobot::new(FRAC_PI_2);
robot.apply(Twist::planar(1.0, 1.0)).await.unwrap();
let pose = robot.read().await.unwrap();
assert!((pose.x - 1.0).abs() < 1e-5 && (pose.y - 1.0).abs() < 1e-5);
}
}