package com.team254.lib.trajectory;
import edu.wpi.first.wpilibj.Timer;
/**
* PID + Feedforward controller for following a Trajectory.
*
* @author Jared341
*/
public class TrajectoryFollower {
public static class TrajectoryConfig {
public double dt;
public double max_acc;
public double max_vel;
@Override
public String toString() {
return "dt: " + dt + ", max_acc: " + max_acc + ", max_vel: "
+ max_vel;
}
}
public static class TrajectorySetpoint {
public double pos;
public double vel;
public double acc;
@Override
public String toString() {
return "pos: " + pos + ", vel: " + vel + ", acc: " + acc;
}
}
private double kp_;
private double ki_;
private double kd_;
private double kv_;
private double ka_;
private double last_error_;
private double error_sum_;
private boolean reset_ = true;
private double last_timestamp_;
private TrajectorySetpoint next_state_ = new TrajectorySetpoint();
private TrajectoryConfig config_ = new TrajectoryConfig();
private double goal_position_;
private TrajectorySetpoint setpoint_ = new TrajectorySetpoint();
public TrajectoryFollower() {
}
public void configure(double kp, double ki, double kd, double kv,
double ka, TrajectoryConfig config) {
kp_ = kp;
ki_ = ki;
kd_ = kd;
kv_ = kv;
ka_ = ka;
config_ = config;
}
public void setGoal(TrajectorySetpoint current_state, double goal_position) {
goal_position_ = goal_position;
setpoint_ = current_state;
reset_ = true;
error_sum_ = 0.0;
}
public double getGoal() {
return goal_position_;
}
public TrajectoryConfig getConfig() {
return config_;
}
public void setConfig(TrajectoryConfig config) {
config_ = config;
}
public double calculate(double position, double velocity) {
double dt = config_.dt;
if (!reset_) {
double now = Timer.getFPGATimestamp();
dt = now - last_timestamp_;
last_timestamp_ = now;
} else {
last_timestamp_ = Timer.getFPGATimestamp();
}
if (isFinishedTrajectory()) {
setpoint_.pos = goal_position_;
setpoint_.vel = 0;
setpoint_.acc = 0;
} else {
// Compute the new commanded position, velocity, and acceleration.
double distance_to_go = goal_position_ - setpoint_.pos;
double cur_vel = setpoint_.vel;
double cur_vel2 = cur_vel * cur_vel;
boolean inverted = false;
if (distance_to_go < 0) {
inverted = true;
distance_to_go *= -1;
cur_vel *= -1;
}
// Compute discriminants of the minimum and maximum reachable
// velocities over the remaining distance.
double max_reachable_velocity_disc = cur_vel2 / 2.0
+ config_.max_acc * distance_to_go;
double min_reachable_velocity_disc = cur_vel2 / 2.0
- config_.max_acc * distance_to_go;
double cruise_vel = cur_vel;
if (min_reachable_velocity_disc < 0 || cruise_vel < 0) {
cruise_vel = Math.min(config_.max_vel,
Math.sqrt(max_reachable_velocity_disc));
}
double t_start = (cruise_vel - cur_vel) / config_.max_acc; // Accelerate
// to
// cruise_vel
double x_start = cur_vel * t_start + .5 * config_.max_acc * t_start
* t_start;
double t_end = Math.abs(cruise_vel / config_.max_acc); // Decelerate
// to zero
// vel.
double x_end = cruise_vel * t_end - .5 * config_.max_acc * t_end
* t_end;
double x_cruise = Math.max(0, distance_to_go - x_start - x_end);
double t_cruise = Math.abs(x_cruise / cruise_vel);
// Figure out where we should be one dt along this trajectory.
if (t_start >= dt) {
next_state_.pos = cur_vel * dt + .5 * config_.max_acc
* dt * dt;
next_state_.vel = cur_vel + config_.max_acc * dt;
next_state_.acc = config_.max_acc;
} else if (t_start + t_cruise >= dt) {
next_state_.pos = x_start + cruise_vel * (dt - t_start);
next_state_.vel = cruise_vel;
next_state_.acc = 0;
} else if (t_start + t_cruise + t_end >= dt) {
double delta_t = dt - t_start - t_cruise;
next_state_.pos = x_start + x_cruise + cruise_vel * delta_t - .5
* config_.max_acc * delta_t * delta_t;
next_state_.vel = cruise_vel - config_.max_acc * delta_t;
next_state_.acc = -config_.max_acc;
} else {
// Trajectory ends this cycle.
next_state_.pos = distance_to_go;
next_state_.vel = 0;
next_state_.acc = 0;
}
if (inverted) {
next_state_.pos *= -1;
next_state_.vel *= -1;
next_state_.acc *= -1;
}
setpoint_.pos += next_state_.pos;
setpoint_.vel = next_state_.vel;
setpoint_.acc = next_state_.acc;
}
double error = setpoint_.pos - position;
if (reset_) {
// Prevent jump in derivative term when we have been reset.
reset_ = false;
last_error_ = error;
error_sum_ = 0;
}
double output = kp_ * error + kd_
* ((error - last_error_) / dt - setpoint_.vel)
+ (kv_ * setpoint_.vel + ka_ * setpoint_.acc);
if (output < 1.0 && output > -1.0) {
// Only integrate error if the output isn't already saturated.
error_sum_ += error * dt;
}
output += ki_ * error_sum_;
last_error_ = error;
return output;
}
public boolean isFinishedTrajectory() {
return Math.abs(setpoint_.pos - goal_position_) < 1E-3
&& Math.abs(setpoint_.vel) < 1E-2;
}
public TrajectorySetpoint getCurrentSetpoint() {
return setpoint_;
}
}