Factor out parts of drivetrain_lib_test
I want to run some year-specific tests with the drivetrain.
Change-Id: I12989b2ec662f7f6b0f0392d7bcf981c6e176e46
diff --git a/frc971/control_loops/drivetrain/BUILD b/frc971/control_loops/drivetrain/BUILD
index d95e101..ee05fc5 100644
--- a/frc971/control_loops/drivetrain/BUILD
+++ b/frc971/control_loops/drivetrain/BUILD
@@ -198,6 +198,23 @@
],
)
+cc_library(
+ name = "drivetrain_test_lib",
+ testonly = True,
+ srcs = ["drivetrain_test_lib.cc"],
+ hdrs = ["drivetrain_test_lib.h"],
+ deps = [
+ ":drivetrain_config",
+ ":drivetrain_queue",
+ ":trajectory",
+ "//aos/testing:googletest",
+ "//frc971/control_loops:state_feedback_loop",
+ "//frc971/queues:gyro",
+ "//y2016:constants",
+ "//y2016/control_loops/drivetrain:polydrivetrain_plants",
+ ],
+)
+
cc_test(
name = "drivetrain_lib_test",
srcs = [
@@ -207,13 +224,11 @@
":drivetrain_config",
":drivetrain_lib",
":drivetrain_queue",
+ ":drivetrain_test_lib",
"//aos:queues",
"//aos/controls:control_loop_test",
"//aos/testing:googletest",
- "//frc971/control_loops:state_feedback_loop",
"//frc971/queues:gyro",
- "//y2016:constants",
- "//y2016/control_loops/drivetrain:polydrivetrain_plants",
],
)
diff --git a/frc971/control_loops/drivetrain/drivetrain_lib_test.cc b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
index 6c16667..16bd78b 100644
--- a/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
+++ b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
@@ -5,7 +5,6 @@
#include "aos/controls/control_loop_test.h"
#include "aos/controls/polytope.h"
-#include "aos/network/team_number.h"
#include "aos/time/time.h"
#include "gtest/gtest.h"
@@ -13,14 +12,8 @@
#include "frc971/control_loops/drivetrain/drivetrain.h"
#include "frc971/control_loops/drivetrain/drivetrain.q.h"
#include "frc971/control_loops/drivetrain/drivetrain_config.h"
-#include "frc971/control_loops/drivetrain/trajectory.h"
-#include "frc971/control_loops/state_feedback_loop.h"
+#include "frc971/control_loops/drivetrain/drivetrain_test_lib.h"
#include "frc971/queues/gyro.q.h"
-#include "y2016/constants.h"
-#include "y2016/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
-#include "y2016/control_loops/drivetrain/hybrid_velocity_drivetrain.h"
-#include "y2016/control_loops/drivetrain/kalman_drivetrain_motor_plant.h"
-#include "y2016/control_loops/drivetrain/polydrivetrain_dog_motor_plant.h"
namespace frc971 {
namespace control_loops {
@@ -29,215 +22,6 @@
namespace chrono = ::std::chrono;
using ::aos::monotonic_clock;
-using ::y2016::control_loops::drivetrain::MakeDrivetrainPlant;
-
-// TODO(Comran): Make one that doesn't depend on the actual values for a
-// specific robot.
-const constants::ShifterHallEffect kThreeStateDriveShifter{0.0, 0.0, 0.25,
- 0.75};
-
-const DrivetrainConfig<double> &GetDrivetrainConfig() {
- static DrivetrainConfig<double> kDrivetrainConfig{
- ::frc971::control_loops::drivetrain::ShifterType::HALL_EFFECT_SHIFTER,
- ::frc971::control_loops::drivetrain::LoopType::CLOSED_LOOP,
- ::frc971::control_loops::drivetrain::GyroType::SPARTAN_GYRO,
- IMUType::IMU_X,
- ::y2016::control_loops::drivetrain::MakeDrivetrainLoop,
- ::y2016::control_loops::drivetrain::MakeVelocityDrivetrainLoop,
- ::y2016::control_loops::drivetrain::MakeKFDrivetrainLoop,
- ::y2016::control_loops::drivetrain::MakeHybridVelocityDrivetrainLoop,
-
- chrono::duration_cast<chrono::nanoseconds>(
- chrono::duration<double>(::y2016::control_loops::drivetrain::kDt)),
- ::y2016::control_loops::drivetrain::kRobotRadius,
- ::y2016::control_loops::drivetrain::kWheelRadius,
- ::y2016::control_loops::drivetrain::kV,
-
- ::y2016::control_loops::drivetrain::kHighGearRatio,
- ::y2016::control_loops::drivetrain::kLowGearRatio,
- ::y2016::control_loops::drivetrain::kJ,
- ::y2016::control_loops::drivetrain::kMass,
- kThreeStateDriveShifter,
- kThreeStateDriveShifter,
- false,
- 0,
-
- 0.25,
- 1.00,
- 1.00};
-
- return kDrivetrainConfig;
-};
-
-class DrivetrainPlant : public StateFeedbackPlant<4, 2, 2> {
- public:
- explicit DrivetrainPlant(StateFeedbackPlant<4, 2, 2> &&other)
- : StateFeedbackPlant<4, 2, 2>(::std::move(other)) {}
-
- void CheckU(const Eigen::Matrix<double, 2, 1> &U) override {
- EXPECT_LE(U(0, 0), U_max(0, 0) + 0.00001 + left_voltage_offset_);
- EXPECT_GE(U(0, 0), U_min(0, 0) - 0.00001 + left_voltage_offset_);
- EXPECT_LE(U(1, 0), U_max(1, 0) + 0.00001 + right_voltage_offset_);
- EXPECT_GE(U(1, 0), U_min(1, 0) - 0.00001 + right_voltage_offset_);
- }
-
- double left_voltage_offset() const { return left_voltage_offset_; }
- void set_left_voltage_offset(double left_voltage_offset) {
- left_voltage_offset_ = left_voltage_offset;
- }
-
- double right_voltage_offset() const { return right_voltage_offset_; }
- void set_right_voltage_offset(double right_voltage_offset) {
- right_voltage_offset_ = right_voltage_offset;
- }
-
- private:
- double left_voltage_offset_ = 0.0;
- double right_voltage_offset_ = 0.0;
-};
-
-// Class which simulates the drivetrain and sends out queue messages containing
-// the position.
-class DrivetrainSimulation {
- public:
- // Constructs a motor simulation.
- // TODO(aschuh) Do we want to test the clutch one too?
- DrivetrainSimulation()
- : drivetrain_plant_(new DrivetrainPlant(MakeDrivetrainPlant())),
- my_drivetrain_queue_(".frc971.control_loops.drivetrain_queue",
- ".frc971.control_loops.drivetrain_queue.goal",
- ".frc971.control_loops.drivetrain_queue.position",
- ".frc971.control_loops.drivetrain_queue.output",
- ".frc971.control_loops.drivetrain_queue.status"),
- gyro_reading_(::frc971::sensors::gyro_reading.name()),
- velocity_drivetrain_(::std::unique_ptr<StateFeedbackLoop<
- 2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
- HybridKalman<2, 2, 2>>>(
- new StateFeedbackLoop<2, 2, 2, double,
- StateFeedbackHybridPlant<2, 2, 2>,
- HybridKalman<2, 2, 2>>(
- GetDrivetrainConfig()
- .make_hybrid_drivetrain_velocity_loop()))) {
- Reinitialize();
- last_U_.setZero();
- }
-
- // Resets the plant.
- void Reinitialize() {
- drivetrain_plant_->mutable_X(0, 0) = 0.0;
- drivetrain_plant_->mutable_X(1, 0) = 0.0;
- drivetrain_plant_->mutable_Y() =
- drivetrain_plant_->C() * drivetrain_plant_->X();
- last_left_position_ = drivetrain_plant_->Y(0, 0);
- last_right_position_ = drivetrain_plant_->Y(1, 0);
- }
-
- // Returns the position of the drivetrain.
- double GetLeftPosition() const { return drivetrain_plant_->Y(0, 0); }
- double GetRightPosition() const { return drivetrain_plant_->Y(1, 0); }
-
- // Sends out the position queue messages.
- void SendPositionMessage() {
- const double left_encoder = GetLeftPosition();
- const double right_encoder = GetRightPosition();
-
- {
- ::aos::ScopedMessagePtr<
- ::frc971::control_loops::DrivetrainQueue::Position> position =
- my_drivetrain_queue_.position.MakeMessage();
- position->left_encoder = left_encoder;
- position->right_encoder = right_encoder;
- position->left_shifter_position = left_gear_high_ ? 1.0 : 0.0;
- position->right_shifter_position = right_gear_high_ ? 1.0 : 0.0;
- position.Send();
- }
-
- {
- ::aos::ScopedMessagePtr<::frc971::sensors::GyroReading> gyro =
- gyro_reading_.MakeMessage();
- gyro->angle = (right_encoder - left_encoder) /
- (::y2016::control_loops::drivetrain::kRobotRadius * 2.0);
- gyro->velocity =
- (drivetrain_plant_->X(3, 0) - drivetrain_plant_->X(1, 0)) /
- (::y2016::control_loops::drivetrain::kRobotRadius * 2.0);
- gyro.Send();
- }
- }
-
- // Simulates the drivetrain moving for one timestep.
- void Simulate() {
- last_left_position_ = drivetrain_plant_->Y(0, 0);
- last_right_position_ = drivetrain_plant_->Y(1, 0);
- EXPECT_TRUE(my_drivetrain_queue_.output.FetchLatest());
- ::Eigen::Matrix<double, 2, 1> U = last_U_;
- last_U_ << my_drivetrain_queue_.output->left_voltage,
- my_drivetrain_queue_.output->right_voltage;
- {
- ::aos::robot_state.FetchLatest();
- const double scalar = ::aos::robot_state->voltage_battery / 12.0;
- last_U_ *= scalar;
- }
- left_gear_high_ = my_drivetrain_queue_.output->left_high;
- right_gear_high_ = my_drivetrain_queue_.output->right_high;
-
- if (left_gear_high_) {
- if (right_gear_high_) {
- drivetrain_plant_->set_index(3);
- } else {
- drivetrain_plant_->set_index(2);
- }
- } else {
- if (right_gear_high_) {
- drivetrain_plant_->set_index(1);
- } else {
- drivetrain_plant_->set_index(0);
- }
- }
-
- U(0, 0) += drivetrain_plant_->left_voltage_offset();
- U(1, 0) += drivetrain_plant_->right_voltage_offset();
- drivetrain_plant_->Update(U);
- double dt_float =
- ::std::chrono::duration_cast<::std::chrono::duration<double>>(
- GetDrivetrainConfig().dt).count();
- state = RungeKuttaU(
- [this](const ::Eigen::Matrix<double, 5, 1> &X,
- const ::Eigen::Matrix<double, 2, 1> &U) {
- return ContinuousDynamics(velocity_drivetrain_->plant(),
- GetDrivetrainConfig().Tlr_to_la(), X, U);
- },
- state, U, dt_float);
- }
-
- void set_left_voltage_offset(double left_voltage_offset) {
- drivetrain_plant_->set_left_voltage_offset(left_voltage_offset);
- }
- void set_right_voltage_offset(double right_voltage_offset) {
- drivetrain_plant_->set_right_voltage_offset(right_voltage_offset);
- }
-
- ::std::unique_ptr<DrivetrainPlant> drivetrain_plant_;
-
- ::Eigen::Matrix<double, 2, 1> GetPosition() const {
- return state.block<2,1>(0,0);
- }
-
- private:
- ::frc971::control_loops::DrivetrainQueue my_drivetrain_queue_;
- ::aos::Queue<::frc971::sensors::GyroReading> gyro_reading_;
-
- ::Eigen::Matrix<double, 5, 1> state = ::Eigen::Matrix<double, 5, 1>::Zero();
- ::std::unique_ptr<
- StateFeedbackLoop<2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
- HybridKalman<2, 2, 2>>> velocity_drivetrain_;
- double last_left_position_;
- double last_right_position_;
-
- Eigen::Matrix<double, 2, 1> last_U_;
-
- bool left_gear_high_ = false;
- bool right_gear_high_ = false;
-};
class DrivetrainTest : public ::aos::testing::ControlLoopTest {
protected:
@@ -247,6 +31,7 @@
::frc971::control_loops::DrivetrainQueue my_drivetrain_queue_;
::aos::ShmEventLoop event_loop_;
+ const DrivetrainConfig<double> dt_config_;
// Create a loop and simulation plant.
DrivetrainLoop drivetrain_motor_;
DrivetrainSimulation drivetrain_motor_plant_;
@@ -257,8 +42,9 @@
".frc971.control_loops.drivetrain_queue.position",
".frc971.control_loops.drivetrain_queue.output",
".frc971.control_loops.drivetrain_queue.status"),
- drivetrain_motor_(GetDrivetrainConfig(), &event_loop_),
- drivetrain_motor_plant_() {
+ dt_config_(GetTestDrivetrainConfig()),
+ drivetrain_motor_(dt_config_, &event_loop_),
+ drivetrain_motor_plant_(dt_config_) {
::frc971::sensors::gyro_reading.Clear();
set_battery_voltage(12.0);
}
@@ -267,7 +53,7 @@
drivetrain_motor_plant_.SendPositionMessage();
drivetrain_motor_.Iterate();
drivetrain_motor_plant_.Simulate();
- SimulateTimestep(true);
+ SimulateTimestep(true, dt_config_.dt);
}
void RunForTime(monotonic_clock::duration run_for) {
@@ -332,9 +118,9 @@
drivetrain_motor_.Iterate();
drivetrain_motor_plant_.Simulate();
if (i > 20 && i < 200) {
- SimulateTimestep(false);
+ SimulateTimestep(false, dt_config_.dt);
} else {
- SimulateTimestep(true);
+ SimulateTimestep(true, dt_config_.dt);
}
}
VerifyNearGoal();
@@ -394,23 +180,22 @@
// Tests that converting from a left, right position to a distance, angle
// coordinate system and back returns the same answer.
TEST_F(DrivetrainTest, LinearToAngularAndBack) {
- const DrivetrainConfig<double> &dt_config = GetDrivetrainConfig();
- const double width = dt_config.robot_radius * 2.0;
+ const double width = dt_config_.robot_radius * 2.0;
Eigen::Matrix<double, 7, 1> state;
state << 2, 3, 4, 5, 0, 0, 0;
- Eigen::Matrix<double, 2, 1> linear = dt_config.LeftRightToLinear(state);
+ Eigen::Matrix<double, 2, 1> linear = dt_config_.LeftRightToLinear(state);
EXPECT_NEAR(3.0, linear(0, 0), 1e-6);
EXPECT_NEAR(4.0, linear(1, 0), 1e-6);
- Eigen::Matrix<double, 2, 1> angular = dt_config.LeftRightToAngular(state);
+ Eigen::Matrix<double, 2, 1> angular = dt_config_.LeftRightToAngular(state);
EXPECT_NEAR(2.0 / width, angular(0, 0), 1e-6);
EXPECT_NEAR(2.0 / width, angular(1, 0), 1e-6);
Eigen::Matrix<double, 4, 1> back_state =
- dt_config.AngularLinearToLeftRight(linear, angular);
+ dt_config_.AngularLinearToLeftRight(linear, angular);
for (int i = 0; i < 4; ++i) {
EXPECT_NEAR(state(i, 0), back_state(i, 0), 1e-8);
@@ -549,7 +334,7 @@
drivetrain_motor_plant_.SendPositionMessage();
drivetrain_motor_.Iterate();
drivetrain_motor_plant_.Simulate();
- SimulateTimestep(true);
+ SimulateTimestep(true, dt_config_.dt);
ASSERT_TRUE(my_drivetrain_queue_.output.FetchLatest());
EXPECT_GT(my_drivetrain_queue_.output->left_voltage, -6);
EXPECT_GT(my_drivetrain_queue_.output->right_voltage, -6);
diff --git a/frc971/control_loops/drivetrain/drivetrain_test_lib.cc b/frc971/control_loops/drivetrain/drivetrain_test_lib.cc
new file mode 100644
index 0000000..49b0387
--- /dev/null
+++ b/frc971/control_loops/drivetrain/drivetrain_test_lib.cc
@@ -0,0 +1,191 @@
+#include "frc971/control_loops/drivetrain/drivetrain_test_lib.h"
+
+#include <chrono>
+
+#include "gtest/gtest.h"
+
+#include "frc971/control_loops/drivetrain/trajectory.h"
+#include "frc971/control_loops/state_feedback_loop.h"
+#include "y2016/constants.h"
+#include "y2016/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
+#include "y2016/control_loops/drivetrain/hybrid_velocity_drivetrain.h"
+#include "y2016/control_loops/drivetrain/kalman_drivetrain_motor_plant.h"
+#include "y2016/control_loops/drivetrain/polydrivetrain_dog_motor_plant.h"
+
+namespace frc971 {
+namespace control_loops {
+namespace drivetrain {
+namespace testing {
+
+namespace {
+// TODO(Comran): Make one that doesn't depend on the actual values for a
+// specific robot.
+const constants::ShifterHallEffect kThreeStateDriveShifter{0.0, 0.0, 0.25,
+ 0.75};
+
+StateFeedbackPlant<4, 2, 2, double> MakePlantFromConfig(
+ const DrivetrainConfig<double> &dt_config) {
+ ::std::vector<
+ ::std::unique_ptr<StateFeedbackPlantCoefficients<4, 2, 2, double>>>
+ coefs;
+ for (size_t ii = 0;
+ ii < dt_config.make_drivetrain_loop().plant().coefficients_size();
+ ++ii) {
+ coefs.emplace_back(new StateFeedbackPlantCoefficients<4, 2, 2, double>(
+ dt_config.make_drivetrain_loop().plant().coefficients(ii)));
+ }
+ return StateFeedbackPlant<4, 2, 2, double>(&coefs);
+}
+
+} // namespace
+
+namespace chrono = ::std::chrono;
+
+const DrivetrainConfig<double> &GetTestDrivetrainConfig() {
+ static DrivetrainConfig<double> kDrivetrainConfig{
+ ::frc971::control_loops::drivetrain::ShifterType::HALL_EFFECT_SHIFTER,
+ ::frc971::control_loops::drivetrain::LoopType::CLOSED_LOOP,
+ ::frc971::control_loops::drivetrain::GyroType::SPARTAN_GYRO,
+ IMUType::IMU_X,
+ ::y2016::control_loops::drivetrain::MakeDrivetrainLoop,
+ ::y2016::control_loops::drivetrain::MakeVelocityDrivetrainLoop,
+ ::y2016::control_loops::drivetrain::MakeKFDrivetrainLoop,
+ ::y2016::control_loops::drivetrain::MakeHybridVelocityDrivetrainLoop,
+
+ chrono::duration_cast<chrono::nanoseconds>(
+ chrono::duration<double>(::y2016::control_loops::drivetrain::kDt)),
+ ::y2016::control_loops::drivetrain::kRobotRadius,
+ ::y2016::control_loops::drivetrain::kWheelRadius,
+ ::y2016::control_loops::drivetrain::kV,
+
+ ::y2016::control_loops::drivetrain::kHighGearRatio,
+ ::y2016::control_loops::drivetrain::kLowGearRatio,
+ ::y2016::control_loops::drivetrain::kJ,
+ ::y2016::control_loops::drivetrain::kMass,
+ kThreeStateDriveShifter,
+ kThreeStateDriveShifter,
+ false,
+ 0,
+
+ 0.25,
+ 1.00,
+ 1.00};
+
+ return kDrivetrainConfig;
+};
+
+void DrivetrainPlant::CheckU(const Eigen::Matrix<double, 2, 1> &U) {
+ EXPECT_LE(U(0, 0), U_max(0, 0) + 0.00001 + left_voltage_offset_);
+ EXPECT_GE(U(0, 0), U_min(0, 0) - 0.00001 + left_voltage_offset_);
+ EXPECT_LE(U(1, 0), U_max(1, 0) + 0.00001 + right_voltage_offset_);
+ EXPECT_GE(U(1, 0), U_min(1, 0) - 0.00001 + right_voltage_offset_);
+}
+
+DrivetrainSimulation::DrivetrainSimulation(
+ const DrivetrainConfig<double> &dt_config)
+ : dt_config_(dt_config),
+ drivetrain_plant_(MakePlantFromConfig(dt_config_)),
+ my_drivetrain_queue_(".frc971.control_loops.drivetrain_queue",
+ ".frc971.control_loops.drivetrain_queue.goal",
+ ".frc971.control_loops.drivetrain_queue.position",
+ ".frc971.control_loops.drivetrain_queue.output",
+ ".frc971.control_loops.drivetrain_queue.status"),
+ gyro_reading_(::frc971::sensors::gyro_reading.name()),
+ velocity_drivetrain_(
+ ::std::unique_ptr<StateFeedbackLoop<2, 2, 2, double,
+ StateFeedbackHybridPlant<2, 2, 2>,
+ HybridKalman<2, 2, 2>>>(
+ new StateFeedbackLoop<2, 2, 2, double,
+ StateFeedbackHybridPlant<2, 2, 2>,
+ HybridKalman<2, 2, 2>>(
+ dt_config_.make_hybrid_drivetrain_velocity_loop()))) {
+ Reinitialize();
+ last_U_.setZero();
+}
+
+void DrivetrainSimulation::Reinitialize() {
+ drivetrain_plant_.mutable_X(0, 0) = 0.0;
+ drivetrain_plant_.mutable_X(1, 0) = 0.0;
+ drivetrain_plant_.mutable_X(2, 0) = 0.0;
+ drivetrain_plant_.mutable_X(3, 0) = 0.0;
+ drivetrain_plant_.mutable_Y() =
+ drivetrain_plant_.C() * drivetrain_plant_.X();
+ last_left_position_ = drivetrain_plant_.Y(0, 0);
+ last_right_position_ = drivetrain_plant_.Y(1, 0);
+}
+
+void DrivetrainSimulation::SendPositionMessage() {
+ const double left_encoder = GetLeftPosition();
+ const double right_encoder = GetRightPosition();
+
+ {
+ ::aos::ScopedMessagePtr<::frc971::control_loops::DrivetrainQueue::Position>
+ position = my_drivetrain_queue_.position.MakeMessage();
+ position->left_encoder = left_encoder;
+ position->right_encoder = right_encoder;
+ position->left_shifter_position = left_gear_high_ ? 1.0 : 0.0;
+ position->right_shifter_position = right_gear_high_ ? 1.0 : 0.0;
+ position.Send();
+ }
+
+ {
+ ::aos::ScopedMessagePtr<::frc971::sensors::GyroReading> gyro =
+ gyro_reading_.MakeMessage();
+ gyro->angle = (right_encoder - left_encoder) /
+ (dt_config_.robot_radius * 2.0);
+ gyro->velocity = (drivetrain_plant_.X(3, 0) - drivetrain_plant_.X(1, 0)) /
+ (dt_config_.robot_radius * 2.0);
+ gyro.Send();
+ }
+}
+
+// Simulates the drivetrain moving for one timestep.
+void DrivetrainSimulation::Simulate() {
+ last_left_position_ = drivetrain_plant_.Y(0, 0);
+ last_right_position_ = drivetrain_plant_.Y(1, 0);
+ EXPECT_TRUE(my_drivetrain_queue_.output.FetchLatest());
+ ::Eigen::Matrix<double, 2, 1> U = last_U_;
+ last_U_ << my_drivetrain_queue_.output->left_voltage,
+ my_drivetrain_queue_.output->right_voltage;
+ {
+ ::aos::robot_state.FetchLatest();
+ const double scalar = ::aos::robot_state->voltage_battery / 12.0;
+ last_U_ *= scalar;
+ }
+ left_gear_high_ = my_drivetrain_queue_.output->left_high;
+ right_gear_high_ = my_drivetrain_queue_.output->right_high;
+
+ if (left_gear_high_) {
+ if (right_gear_high_) {
+ drivetrain_plant_.set_index(3);
+ } else {
+ drivetrain_plant_.set_index(2);
+ }
+ } else {
+ if (right_gear_high_) {
+ drivetrain_plant_.set_index(1);
+ } else {
+ drivetrain_plant_.set_index(0);
+ }
+ }
+
+ U(0, 0) += drivetrain_plant_.left_voltage_offset();
+ U(1, 0) += drivetrain_plant_.right_voltage_offset();
+ drivetrain_plant_.Update(U);
+ double dt_float =
+ ::std::chrono::duration_cast<::std::chrono::duration<double>>(
+ dt_config_.dt)
+ .count();
+ state_ = RungeKuttaU(
+ [this](const ::Eigen::Matrix<double, 5, 1> &X,
+ const ::Eigen::Matrix<double, 2, 1> &U) {
+ return ContinuousDynamics(velocity_drivetrain_->plant(),
+ dt_config_.Tlr_to_la(), X, U);
+ },
+ state_, U, dt_float);
+}
+
+} // namespace testing
+} // namespace drivetrain
+} // namespace control_loops
+} // namespace frc971
diff --git a/frc971/control_loops/drivetrain/drivetrain_test_lib.h b/frc971/control_loops/drivetrain/drivetrain_test_lib.h
new file mode 100644
index 0000000..a8dfa07
--- /dev/null
+++ b/frc971/control_loops/drivetrain/drivetrain_test_lib.h
@@ -0,0 +1,101 @@
+#ifndef FRC971_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_TEST_LIB_H_
+#define FRC971_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_TEST_LIB_H_
+
+#include "frc971/control_loops/drivetrain/drivetrain.q.h"
+#include "frc971/control_loops/drivetrain/drivetrain_config.h"
+#include "frc971/control_loops/state_feedback_loop.h"
+#include "frc971/queues/gyro.q.h"
+
+namespace frc971 {
+namespace control_loops {
+namespace drivetrain {
+namespace testing {
+
+const DrivetrainConfig<double> &GetTestDrivetrainConfig();
+
+class DrivetrainPlant : public StateFeedbackPlant<4, 2, 2> {
+ public:
+ explicit DrivetrainPlant(StateFeedbackPlant<4, 2, 2> &&other)
+ : StateFeedbackPlant<4, 2, 2>(::std::move(other)) {}
+
+ void CheckU(const Eigen::Matrix<double, 2, 1> &U) override;
+
+ double left_voltage_offset() const { return left_voltage_offset_; }
+ void set_left_voltage_offset(double left_voltage_offset) {
+ left_voltage_offset_ = left_voltage_offset;
+ }
+
+ double right_voltage_offset() const { return right_voltage_offset_; }
+ void set_right_voltage_offset(double right_voltage_offset) {
+ right_voltage_offset_ = right_voltage_offset;
+ }
+
+ private:
+ double left_voltage_offset_ = 0.0;
+ double right_voltage_offset_ = 0.0;
+};
+
+// Class which simulates the drivetrain and sends out queue messages containing
+// the position.
+class DrivetrainSimulation {
+ public:
+ // Constructs a motor simulation.
+ // TODO(aschuh) Do we want to test the clutch one too?
+ DrivetrainSimulation(const DrivetrainConfig<double> &dt_config);
+
+ // Resets the plant.
+ void Reinitialize();
+
+ // Returns the position of the drivetrain.
+ double GetLeftPosition() const { return drivetrain_plant_.Y(0, 0); }
+ double GetRightPosition() const { return drivetrain_plant_.Y(1, 0); }
+
+ // Sends out the position queue messages.
+ void SendPositionMessage();
+
+ // Simulates the drivetrain moving for one timestep.
+ void Simulate();
+
+ void set_left_voltage_offset(double left_voltage_offset) {
+ drivetrain_plant_.set_left_voltage_offset(left_voltage_offset);
+ }
+ void set_right_voltage_offset(double right_voltage_offset) {
+ drivetrain_plant_.set_right_voltage_offset(right_voltage_offset);
+ }
+
+ Eigen::Matrix<double, 5, 1> state() const { return state_; }
+
+ Eigen::Matrix<double, 5, 1> *mutable_state() { return &state_; }
+
+ ::Eigen::Matrix<double, 2, 1> GetPosition() const {
+ return state_.block<2,1>(0,0);
+ }
+
+ private:
+ DrivetrainConfig<double> dt_config_;
+
+ DrivetrainPlant drivetrain_plant_;
+
+ ::frc971::control_loops::DrivetrainQueue my_drivetrain_queue_;
+ ::aos::Queue<::frc971::sensors::GyroReading> gyro_reading_;
+
+ // This state is [x, y, theta, left_velocity, right_velocity].
+ ::Eigen::Matrix<double, 5, 1> state_ = ::Eigen::Matrix<double, 5, 1>::Zero();
+ ::std::unique_ptr<
+ StateFeedbackLoop<2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
+ HybridKalman<2, 2, 2>>> velocity_drivetrain_;
+ double last_left_position_;
+ double last_right_position_;
+
+ Eigen::Matrix<double, 2, 1> last_U_;
+
+ bool left_gear_high_ = false;
+ bool right_gear_high_ = false;
+};
+
+} // namespace testing
+} // namespace drivetrain
+} // namespace control_loops
+} // namespace frc971
+
+#endif // FRC971_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_TEST_LIB_H_
diff --git a/frc971/control_loops/state_feedback_loop.h b/frc971/control_loops/state_feedback_loop.h
index 0da8bd6..d5861b7 100644
--- a/frc971/control_loops/state_feedback_loop.h
+++ b/frc971/control_loops/state_feedback_loop.h
@@ -115,6 +115,8 @@
Eigen::Matrix<Scalar, number_of_outputs, 1> &mutable_Y() { return Y_; }
Scalar &mutable_Y(int i, int j = 0) { return mutable_Y()(i, j); }
+ size_t coefficients_size() const { return coefficients_.size(); }
+
const StateFeedbackPlantCoefficients<number_of_states, number_of_inputs,
number_of_outputs, Scalar>
&coefficients(int index) const {