blob: 1222ae04230666f1891b789eb1068694d3d02ca6 [file] [log] [blame]
#include "frc971/control_loops/drivetrain/splinedrivetrain.h"
#include "Eigen/Dense"
#include "aos/realtime.h"
#include "aos/util/math.h"
#include "frc971/control_loops/control_loops_generated.h"
#include "frc971/control_loops/drivetrain/drivetrain_config.h"
#include "frc971/control_loops/drivetrain/drivetrain_goal_generated.h"
#include "frc971/control_loops/drivetrain/drivetrain_output_generated.h"
#include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
namespace frc971 {
namespace control_loops {
namespace drivetrain {
SplineDrivetrain::SplineDrivetrain(const DrivetrainConfig<double> &dt_config)
: dt_config_(dt_config), new_goal_(&mutex_) {
worker_thread_ = std::thread(&SplineDrivetrain::ComputeTrajectory, this);
}
void SplineDrivetrain::ScaleCapU(Eigen::Matrix<double, 2, 1> *U) {
output_was_capped_ =
::std::abs((*U)(0, 0)) > 12.0 || ::std::abs((*U)(1, 0)) > 12.0;
if (output_was_capped_) {
*U *= 12.0 / U->lpNorm<Eigen::Infinity>();
}
}
void SplineDrivetrain::ComputeTrajectory() {
::aos::SetCurrentThreadRealtimePriority(1);
::aos::MutexLocker locker(&mutex_);
while (run_) {
while (goal_.spline_idx == future_spline_idx_) {
AOS_CHECK(!new_goal_.Wait());
if (!run_) {
return;
}
}
past_distance_spline_.reset();
past_trajectory_.reset();
plan_state_ = PlanningState_BUILDING_TRAJECTORY;
future_spline_idx_ = goal_.spline_idx;
planning_spline_idx_ = future_spline_idx_;
const MultiSpline &multispline = goal_.spline;
auto x = multispline.spline_x;
auto y = multispline.spline_y;
::std::vector<Spline> splines = ::std::vector<Spline>();
for (int i = 0; i < multispline.spline_count; ++i) {
::Eigen::Matrix<double, 2, 6> points =
::Eigen::Matrix<double, 2, 6>::Zero();
for (int j = 0; j < 6; ++j) {
points(0, j) = x[i * 5 + j];
points(1, j) = y[i * 5 + j];
}
splines.emplace_back(Spline(points));
}
future_drive_spline_backwards_ = goal_.drive_spline_backwards;
future_distance_spline_ = ::std::unique_ptr<DistanceSpline>(
new DistanceSpline(::std::move(splines)));
future_trajectory_ = ::std::unique_ptr<Trajectory>(
new Trajectory(future_distance_spline_.get(), dt_config_));
for (size_t i = 0; i < multispline.constraints.size(); ++i) {
const ::frc971::ConstraintT &constraint = multispline.constraints[i];
switch (constraint.constraint_type) {
case frc971::ConstraintType_CONSTRAINT_TYPE_UNDEFINED:
break;
case frc971::ConstraintType_LONGITUDINAL_ACCELERATION:
future_trajectory_->set_longitudal_acceleration(constraint.value);
break;
case frc971::ConstraintType_LATERAL_ACCELERATION:
future_trajectory_->set_lateral_acceleration(constraint.value);
break;
case frc971::ConstraintType_VOLTAGE:
future_trajectory_->set_voltage_limit(constraint.value);
break;
case frc971::ConstraintType_VELOCITY:
future_trajectory_->LimitVelocity(constraint.start_distance,
constraint.end_distance,
constraint.value);
break;
}
}
plan_state_ = PlanningState_PLANNING_TRAJECTORY;
future_trajectory_->Plan();
plan_state_ = PlanningState_PLANNED;
}
}
void SplineDrivetrain::SetGoal(
const ::frc971::control_loops::drivetrain::Goal *goal) {
current_spline_handle_ = goal->spline_handle();
// If told to stop, set the executing spline to an invalid index and clear out
// its plan:
if (current_spline_handle_ == 0 &&
(goal->spline() == nullptr ||
current_spline_idx_ != CHECK_NOTNULL(goal->spline())->spline_idx())) {
current_spline_idx_ = -1;
}
::aos::Mutex::State mutex_state = mutex_.TryLock();
if (mutex_state == ::aos::Mutex::State::kLocked) {
if (goal->spline() != nullptr && goal->spline()->spline_idx() &&
future_spline_idx_ != goal->spline()->spline_idx()) {
CHECK_NOTNULL(goal->spline());
goal_.spline_idx = goal->spline()->spline_idx();
goal_.drive_spline_backwards = goal->spline()->drive_spline_backwards();
const frc971::MultiSpline *multispline = goal->spline()->spline();
CHECK_NOTNULL(multispline);
goal_.spline.spline_count = multispline->spline_count();
CHECK_EQ(multispline->spline_x()->size(),
static_cast<size_t>(goal_.spline.spline_count * 5 + 1));
CHECK_EQ(multispline->spline_y()->size(),
static_cast<size_t>(goal_.spline.spline_count * 5 + 1));
std::copy(multispline->spline_x()->begin(),
multispline->spline_x()->end(), goal_.spline.spline_x.begin());
std::copy(multispline->spline_y()->begin(),
multispline->spline_y()->end(), goal_.spline.spline_y.begin());
for (size_t i = 0; i < 6; ++i) {
if (multispline->constraints() != nullptr &&
i < multispline->constraints()->size()) {
multispline->constraints()->Get(i)->UnPackTo(
&goal_.spline.constraints[i]);
} else {
goal_.spline.constraints[i].constraint_type =
ConstraintType_CONSTRAINT_TYPE_UNDEFINED;
}
}
new_goal_.Broadcast();
if (current_spline_handle_ != current_spline_idx_) {
// If we aren't going to actively execute the current spline, evict it's
// plan.
past_trajectory_ = std::move(current_trajectory_);
past_distance_spline_ = std::move(current_distance_spline_);
}
}
// If you never started executing the previous spline, you're screwed.
if (future_trajectory_ &&
(!current_trajectory_ ||
current_trajectory_->is_at_end(current_xva_.block<2, 1>(0, 0)) ||
current_spline_idx_ == -1)) {
// Move current data to other variables to be cleared by worker.
past_trajectory_ = std::move(current_trajectory_);
past_distance_spline_ = std::move(current_distance_spline_);
// Move the computed data to be executed.
current_trajectory_ = std::move(future_trajectory_);
current_distance_spline_ = std::move(future_distance_spline_);
current_drive_spline_backwards_ = future_drive_spline_backwards_;
current_spline_idx_ = future_spline_idx_;
// Reset internal state to a trajectory start position.
current_xva_ = current_trajectory_->FFAcceleration(0);
current_xva_(1) = 0.0;
has_started_execution_ = false;
}
mutex_.Unlock();
}
}
// TODO(alex): Hold position when done following the spline.
// TODO(Austin): Compensate for voltage error.
void SplineDrivetrain::Update(bool enable, const ::Eigen::Matrix<double, 5, 1> &state) {
next_U_ = ::Eigen::Matrix<double, 2, 1>::Zero();
enable_ = enable;
if (enable && current_trajectory_) {
::Eigen::Matrix<double, 2, 1> U_ff = ::Eigen::Matrix<double, 2, 1>::Zero();
if (!IsAtEnd() &&
current_spline_handle_ == current_spline_idx_) {
has_started_execution_ = true;
// TODO(alex): It takes about a cycle for the outputs to propagate to the
// motors. Consider delaying the output by a cycle.
U_ff = current_trajectory_->FFVoltage(current_xva_(0));
}
::Eigen::Matrix<double, 2, 5> K =
current_trajectory_->KForState(state, dt_config_.dt, Q, R);
::Eigen::Matrix<double, 5, 1> goal_state = CurrentGoalState();
if (current_drive_spline_backwards_) {
::Eigen::Matrix<double, 2, 1> swapU(U_ff(1, 0), U_ff(0, 0));
U_ff = -swapU;
goal_state(2, 0) += M_PI;
double left_goal = goal_state(3, 0);
double right_goal = goal_state(4, 0);
goal_state(3, 0) = -right_goal;
goal_state(4, 0) = -left_goal;
}
::Eigen::Matrix<double, 5, 1> state_error = goal_state - state;
state_error(2, 0) = ::aos::math::NormalizeAngle(state_error(2, 0));
::Eigen::Matrix<double, 2, 1> U_fb = K * state_error;
::Eigen::Matrix<double, 2, 1> xv_state = current_xva_.block<2,1>(0,0);
next_xva_ = current_trajectory_->GetNextXVA(dt_config_.dt, &xv_state);
next_U_ = U_ff + U_fb;
uncapped_U_ = next_U_;
ScaleCapU(&next_U_);
}
}
void SplineDrivetrain::SetOutput(
::frc971::control_loops::drivetrain::OutputT *output) {
if (!output) {
return;
}
if (current_spline_handle_ == current_spline_idx_) {
if (!IsAtEnd()) {
output->left_voltage = next_U_(0);
output->right_voltage = next_U_(1);
current_xva_ = next_xva_;
}
}
output->left_voltage = next_U_(0);
output->right_voltage = next_U_(1);
}
void SplineDrivetrain::PopulateStatus(
drivetrain::Status::Builder *builder) const {
if (builder && enable_) {
builder->add_uncapped_left_voltage(uncapped_U_(0));
builder->add_uncapped_right_voltage(uncapped_U_(1));
builder->add_robot_speed(current_xva_(1));
builder->add_output_was_capped(output_was_capped_);
}
}
flatbuffers::Offset<TrajectoryLogging> SplineDrivetrain::MakeTrajectoryLogging(
flatbuffers::FlatBufferBuilder *builder) const {
drivetrain::TrajectoryLogging::Builder trajectory_logging_builder(*builder);
if (current_distance_spline_) {
::Eigen::Matrix<double, 5, 1> goal_state = CurrentGoalState();
trajectory_logging_builder.add_x(goal_state(0));
trajectory_logging_builder.add_y(goal_state(1));
trajectory_logging_builder.add_theta(::aos::math::NormalizeAngle(
goal_state(2) + (current_drive_spline_backwards_ ? M_PI : 0.0)));
trajectory_logging_builder.add_left_velocity(goal_state(3));
trajectory_logging_builder.add_right_velocity(goal_state(4));
}
trajectory_logging_builder.add_planning_state(plan_state_.load());
trajectory_logging_builder.add_is_executing(!IsAtEnd() &&
has_started_execution_);
trajectory_logging_builder.add_is_executed((current_spline_idx_ != -1) &&
IsAtEnd());
trajectory_logging_builder.add_goal_spline_handle(current_spline_handle_);
trajectory_logging_builder.add_current_spline_idx(current_spline_idx_);
trajectory_logging_builder.add_distance_remaining(
current_trajectory_ ? current_trajectory_->length() - current_xva_.x()
: 0.0);
int32_t planning_spline_idx = planning_spline_idx_;
if (current_spline_idx_ == planning_spline_idx) {
trajectory_logging_builder.add_planning_spline_idx(0);
} else {
trajectory_logging_builder.add_planning_spline_idx(planning_spline_idx_);
}
return trajectory_logging_builder.Finish();
}
flatbuffers::Offset<TrajectoryLogging> SplineDrivetrain::MakeTrajectoryLogging(
aos::Sender<drivetrain::Status>::Builder *builder) const {
return MakeTrajectoryLogging(builder->fbb());
}
} // namespace drivetrain
} // namespace control_loops
} // namespace frc971