blob: 0f9418c0c3bf6a2238eb6af6d35761cc2e9c5a62 [file] [log] [blame]
#include "frc971/control_loops/drivetrain/splinedrivetrain.h"
#include "Eigen/Dense"
#include "aos/json_to_flatbuffer.h"
#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),
velocity_drivetrain_(
std::make_shared<StateFeedbackLoop<2, 2, 2, double,
StateFeedbackHybridPlant<2, 2, 2>,
HybridKalman<2, 2, 2>>>(
dt_config_.make_hybrid_drivetrain_velocity_loop())),
current_xva_(Eigen::Vector3d::Zero()),
next_xva_(Eigen::Vector3d::Zero()),
next_U_(Eigen::Vector2d::Zero()) {}
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::SetGoal(
const ::frc971::control_loops::drivetrain::Goal *goal) {
UpdateSplineHandles(goal->has_spline_handle()
? std::make_optional<int>(goal->spline_handle())
: std::nullopt);
}
bool SplineDrivetrain::IsCurrentTrajectory(
const fb::Trajectory *trajectory) const {
const FinishedTrajectory *current = current_trajectory();
return (current != nullptr &&
current->spline_handle() == trajectory->handle());
}
bool SplineDrivetrain::HasTrajectory(const fb::Trajectory *trajectory) const {
if (trajectory == nullptr) {
return false;
}
for (size_t ii = 0; ii < trajectories_.size(); ++ii) {
if (trajectories_[ii].spline_handle() == trajectory->handle()) {
return true;
}
}
return false;
}
void SplineDrivetrain::DeleteTrajectory(const fb::Trajectory *trajectory) {
CHECK(trajectory != nullptr);
for (size_t ii = 0; ii < trajectories_.size(); ++ii) {
if (trajectories_[ii].spline_handle() == trajectory->handle()) {
trajectories_.erase(trajectories_.begin() + ii);
return;
}
}
LOG(FATAL) << "Trying to remove unknown trajectory " << trajectory->handle();
}
void SplineDrivetrain::AddTrajectory(const fb::Trajectory *trajectory) {
CHECK_LT(trajectories_.size(), trajectories_.capacity());
trajectories_.emplace_back(dt_config_, trajectory, velocity_drivetrain_);
UpdateSplineHandles(commanded_spline_);
}
void SplineDrivetrain::DeleteCurrentSpline() {
DeleteTrajectory(&CHECK_NOTNULL(current_trajectory())->trajectory());
executing_spline_ = false;
commanded_spline_.reset();
current_xva_.setZero();
}
void SplineDrivetrain::UpdateSplineHandles(
std::optional<int> commanded_spline) {
// If we are currently executing a spline and have received a change
if (executing_spline_) {
if (!commanded_spline) {
// We've been told to stop executing a spline; remove it from our queue,
// and clean up.
DeleteCurrentSpline();
return;
} else {
if (executing_spline_ &&
CHECK_NOTNULL(current_trajectory())->spline_handle() !=
*commanded_spline) {
// If we are executing a spline, and the handle has changed, garbage
// collect the old spline.
DeleteCurrentSpline();
}
}
}
commanded_spline_ = commanded_spline;
// We've now cleaned up the previous state; handle any new commands.
if (!commanded_spline_) {
return;
}
for (size_t ii = 0; ii < trajectories_.size(); ++ii) {
if (trajectories_[ii].spline_handle() == *commanded_spline_) {
executing_spline_ = true;
}
}
// If we didn't find the commanded spline in the list of available splines,
// that's fine; it just means, it hasn't been fully planned yet.
}
FinishedTrajectory *SplineDrivetrain::current_trajectory() {
for (size_t ii = 0; ii < trajectories_.size(); ++ii) {
if (trajectories_[ii].spline_handle() == *commanded_spline_) {
return &trajectories_[ii];
}
}
return nullptr;
}
const FinishedTrajectory *SplineDrivetrain::current_trajectory() const {
for (size_t ii = 0; ii < trajectories_.size(); ++ii) {
if (trajectories_[ii].spline_handle() == *commanded_spline_) {
return &trajectories_[ii];
}
}
return nullptr;
}
// TODO(alex): Hold position when done following the spline.
void SplineDrivetrain::Update(
bool enable, const ::Eigen::Matrix<double, 5, 1> &state,
const ::Eigen::Matrix<double, 2, 1> &voltage_error) {
next_U_ = ::Eigen::Matrix<double, 2, 1>::Zero();
enable_ = enable;
if (enable && executing_spline_) {
::Eigen::Matrix<double, 2, 1> U_ff = ::Eigen::Matrix<double, 2, 1>::Zero();
const FinishedTrajectory *const trajectory =
CHECK_NOTNULL(current_trajectory());
if (!IsAtEnd() && executing_spline_) {
// TODO(alex): It takes about a cycle for the outputs to propagate to the
// motors. Consider delaying the output by a cycle.
U_ff = trajectory->FFVoltage(current_xva_(0));
}
const double current_distance = current_xva_(0);
::Eigen::Matrix<double, 2, 5> K =
trajectory->GainForDistance(current_distance);
::Eigen::Matrix<double, 5, 1> goal_state = CurrentGoalState();
const bool backwards = trajectory->drive_spline_backwards();
if (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;
}
const Eigen::Matrix<double, 5, 1> relative_goal =
trajectory->StateToPathRelativeState(current_distance, goal_state,
backwards);
const Eigen::Matrix<double, 5, 1> relative_state =
trajectory->StateToPathRelativeState(current_distance, state,
backwards);
Eigen::Matrix<double, 5, 1> state_error = relative_goal - relative_state;
state_error(2, 0) = ::aos::math::NormalizeAngle(state_error(2, 0));
::Eigen::Matrix<double, 2, 1> U_fb = K * state_error;
if (backwards) {
Eigen::Matrix<double, 2, 1> swapU(U_fb(1), U_fb(0));
U_fb = -swapU;
}
::Eigen::Matrix<double, 2, 1> xv_state = current_xva_.block<2, 1>(0, 0);
next_xva_ = trajectory->GetNextXVA(dt_config_.dt, &xv_state);
next_U_ = U_ff + U_fb - voltage_error;
uncapped_U_ = next_U_;
ScaleCapU(&next_U_);
}
}
void SplineDrivetrain::SetOutput(
::frc971::control_loops::drivetrain::OutputT *output) {
if (!output) {
return;
}
if (executing_spline_) {
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 {
int *spline_handles;
const flatbuffers::Offset<flatbuffers::Vector<int>> handles_vector =
builder->CreateUninitializedVector(trajectories_.size(), &spline_handles);
for (size_t ii = 0; ii < trajectories_.size(); ++ii) {
spline_handles[ii] = trajectories_[ii].spline_handle();
}
drivetrain::TrajectoryLogging::Builder trajectory_logging_builder(*builder);
if (executing_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));
if (CHECK_NOTNULL(current_trajectory())->drive_spline_backwards()) {
trajectory_logging_builder.add_left_velocity(-goal_state(4));
trajectory_logging_builder.add_right_velocity(-goal_state(3));
trajectory_logging_builder.add_theta(
::aos::math::NormalizeAngle(goal_state(2) + M_PI));
} else {
trajectory_logging_builder.add_theta(
::aos::math::NormalizeAngle(goal_state(2)));
trajectory_logging_builder.add_left_velocity(goal_state(3));
trajectory_logging_builder.add_right_velocity(goal_state(4));
}
}
trajectory_logging_builder.add_is_executing(!IsAtEnd() && executing_spline_);
trajectory_logging_builder.add_is_executed(executing_spline_ && IsAtEnd());
if (commanded_spline_) {
trajectory_logging_builder.add_goal_spline_handle(*commanded_spline_);
if (executing_spline_) {
trajectory_logging_builder.add_current_spline_idx(*commanded_spline_);
}
}
trajectory_logging_builder.add_distance_remaining(
executing_spline_
? CHECK_NOTNULL(current_trajectory())->length() - current_xva_.x()
: 0.0);
trajectory_logging_builder.add_available_splines(handles_vector);
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