use arci::{BaseVelocity, MoveBase, Navigation};
use k::nalgebra as na;
use na::Isometry2;
use serde::{Deserialize, Serialize};
use crate::Error;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct LocalMoveConfig {
pub reach_distance_threshold: f64,
pub reach_angle_threshold: f64,
pub control_frequency: f64,
pub linear_gain: f64,
pub angular_gain: f64,
pub max_linear_vel: f64,
pub max_angular_vel: f64,
}
impl LocalMoveConfig {
pub fn new<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
let config: LocalMoveConfig = toml::from_str(
&std::fs::read_to_string(&path)
.map_err(|e| Error::NoFile(path.as_ref().to_owned(), e))?,
)
.map_err(|e| Error::TomlParseFailure(path.as_ref().to_owned(), e))?;
Ok(config)
}
}
#[derive(Debug)]
pub struct LocalMove<N, M>
where
N: Navigation,
M: MoveBase,
{
pub nav_client: N,
pub vel_client: M,
pub config: LocalMoveConfig,
}
impl<N, M> LocalMove<N, M>
where
N: Navigation,
M: MoveBase,
{
pub fn new(nav_client: N, vel_client: M, config: LocalMoveConfig) -> Self {
Self {
nav_client,
vel_client,
config,
}
}
pub fn is_reached(&self, pose_error: Isometry2<f64>) -> bool {
pose_error.translation.vector.norm() < self.config.reach_distance_threshold
&& pose_error.rotation.angle().abs() < self.config.reach_angle_threshold
}
pub fn send_zero_velocity(&self) -> Result<(), arci::Error> {
self.vel_client
.send_velocity(&BaseVelocity::new(0.0, 0.0, 0.0))
}
pub fn send_control_velocity_from_pose_error(
&self,
pose_error: Isometry2<f64>,
) -> Result<(), arci::Error> {
let vel_x = na::clamp(
self.config.linear_gain * pose_error.translation.vector[0],
-self.config.max_linear_vel,
self.config.max_linear_vel,
);
let vel_y = na::clamp(
self.config.linear_gain * pose_error.translation.vector[1],
-self.config.max_linear_vel,
self.config.max_linear_vel,
);
let vel_theta = na::clamp(
self.config.angular_gain * pose_error.rotation.angle(),
-self.config.max_angular_vel,
self.config.max_angular_vel,
);
self.vel_client
.send_velocity(&BaseVelocity::new(vel_x, vel_y, vel_theta))
}
}
#[cfg(test)]
mod tests {
use arci::{DummyMoveBase, DummyNavigation};
use assert_approx_eq::assert_approx_eq;
use na::Vector2;
use super::*;
#[test]
fn test_config() {
let path = std::path::Path::new("tests/local_move_sample.toml");
let config = LocalMoveConfig::new(path).unwrap();
assert_approx_eq!(config.reach_distance_threshold, 0.1);
assert_approx_eq!(config.control_frequency, 10.0);
assert_approx_eq!(config.linear_gain, 1.0);
assert_approx_eq!(config.max_linear_vel, 1.0);
}
#[test]
fn test() {
let path = std::path::Path::new("tests/local_move_sample.toml");
let config = LocalMoveConfig::new(path).unwrap();
assert_approx_eq!(config.linear_gain, 1.0);
let move_base = DummyMoveBase::new();
let nav = DummyNavigation::new();
let local_move = LocalMove::new(Box::new(nav), Box::new(move_base), config);
assert!(local_move.is_reached(Isometry2::new(Vector2::new(0.0, 0.0), 0.0)));
assert!(local_move.is_reached(Isometry2::new(Vector2::new(0.05, 0.05), 0.05)));
assert!(!local_move.is_reached(Isometry2::new(Vector2::new(0.11, 0.0), 0.0))); assert!(!local_move.is_reached(Isometry2::new(Vector2::new(0.0, 0.0), 0.11))); local_move
.send_control_velocity_from_pose_error(Isometry2::new(Vector2::new(1.0, -1.0), 1.0))
.unwrap();
let vel0 = local_move.vel_client.current_velocity().unwrap();
assert_approx_eq!(vel0.x, 1.0); assert_approx_eq!(vel0.y, -1.0); assert_approx_eq!(vel0.theta, 1.0); local_move
.send_control_velocity_from_pose_error(Isometry2::new(Vector2::new(2.0, -2.0), 2.0))
.unwrap(); let vel1 = local_move.vel_client.current_velocity().unwrap();
assert_approx_eq!(vel1.x, 1.0); assert_approx_eq!(vel1.y, -1.0); assert_approx_eq!(vel1.theta, 1.0); local_move.send_zero_velocity().unwrap();
let vel2 = local_move.vel_client.current_velocity().unwrap();
assert_approx_eq!(vel2.x, 0.0);
assert_approx_eq!(vel2.y, 0.0);
assert_approx_eq!(vel2.theta, 0.0);
}
}