mod algorithm;
pub mod control;
const LOOPRATE: Duration = Duration::from_millis(10);
use std::time::Duration;
use control::PursuitControl;
use snafu::Snafu;
use vexide::time::sleep;
use crate::{
motion::{
localization::{Localizer, tracker::devices::TrackingSensorError},
pursuit::algorithm::abs_arc_point,
},
peripherals::drivetrain::{Differential, DrivetrainError},
utils::{
geo::{self, Pose},
units::Length,
},
};
pub trait IsLocalizerError: std::error::Error + 'static {}
impl IsLocalizerError for TrackingSensorError {}
#[derive(Debug, Snafu)]
pub enum PursuitError<E = TrackingSensorError>
where E: IsLocalizerError {
#[snafu(transparent)]
LocalizerError {
source: E,
},
#[snafu(transparent)]
DrivetrainError {
source: DrivetrainError,
},
Unknown {
string: String,
},
}
#[derive(Debug, Clone, Copy)]
pub struct Pursuit {
pub lookahead: Length,
}
impl Pursuit {
pub fn new(lookahead: Length) -> Self { Self { lookahead } }
pub async fn follow<C: PursuitControl, E, D: Differential>(
&self,
odom: &mut impl Localizer<E>,
drivetrain: &D,
ctrl_algorithm: &C,
path: geo::Path,
) -> Result<(), PursuitError<E>>
where
E: IsLocalizerError, {
let mut run = true;
while run {
let odometry_values = odom.get_coords();
let (x, y, t) = (odometry_values.x, odometry_values.y, odometry_values.t);
let cir = geo::Circle {
x: x.as_inches(),
y: y.as_inches(),
r: self.lookahead.as_inches(),
};
let target = algorithm::pursuit_target(path.clone(), cir);
let (tarx, tary) = abs_arc_point(
Pose::new(x, y, t),
Length::as_inches(Length::from_inches(target.x)),
Length::as_inches(Length::from_inches(target.y)),
);
let ((powl, powr), _) = ctrl_algorithm.control(
Length::from_inches(tarx),
Length::from_inches(tary),
self.lookahead,
);
drivetrain.set_left_voltage(powl)?;
drivetrain.set_right_voltage(powr)?;
run = should_continue_to_final_waypoint(
ctrl_algorithm,
Pose::new(x, y, t),
&path,
self.lookahead,
);
odom.tick().await?;
sleep(LOOPRATE).await;
}
Ok(())
}
pub async fn tick<C: PursuitControl, E, D: Differential>(
&self,
odom: &mut impl Localizer<E>,
drivetrain: &D,
ctrl_algorithm: &C,
path: geo::Path,
) -> Result<bool, PursuitError<E>>
where
E: IsLocalizerError, {
let odometry_values = odom.get_coords();
let (x, y, t) = (odometry_values.x, odometry_values.y, odometry_values.t);
let cir = geo::Circle {
x: x.as_inches(),
y: y.as_inches(),
r: self.lookahead.as_inches(),
};
let target = algorithm::pursuit_target(path.clone(), cir);
let (tarx, tary) = abs_arc_point(
Pose::new(x, y, t),
Length::as_inches(Length::from_inches(target.x)),
Length::as_inches(Length::from_inches(target.y)),
);
let ((powl, powr), _) = ctrl_algorithm.control(
Length::from_inches(tarx),
Length::from_inches(tary),
self.lookahead,
);
drivetrain.set_left_voltage(powl)?;
drivetrain.set_right_voltage(powr)?;
let should_continue = should_continue_to_final_waypoint(
ctrl_algorithm,
Pose::new(x, y, t),
&path,
self.lookahead,
);
odom.tick().await?;
Ok(should_continue)
}
}
fn should_continue_to_final_waypoint<C: PursuitControl>(
ctrl_algorithm: &C,
pose: Pose,
path: &geo::Path,
lookahead: Length,
) -> bool {
let final_waypoint = path
.waypoints
.last()
.copied()
.unwrap_or_else(geo::Point::origin);
let (final_x, final_y) = abs_arc_point(pose, final_waypoint.x, final_waypoint.y);
let (_, should_continue) = ctrl_algorithm.control(
Length::from_inches(final_x),
Length::from_inches(final_y),
lookahead,
);
should_continue
}