Claw avoids windup.
diff --git a/frc971/constants.cc b/frc971/constants.cc
index 672d5ef..39f92d2 100755
--- a/frc971/constants.cc
+++ b/frc971/constants.cc
@@ -108,8 +108,9 @@
0.1,
0.0,
1.57,
- {0.0, 2.05, 0.02, 2.02, {-0.1, 0.05}, {1.0, 1.1}, {2.0, 2.1}},
- {0.0, 2.05, 0.02, 2.02, {-0.1, 0.05}, {1.0, 1.1}, {2.0, 2.1}},
+ // TODO(austin): Radians...
+ {-196.70, 121.42, 0.02, 2.02, {-196.92, -180.99}, {-15.17, -3.30}, {106.26, 129.55}},
+ {-142.96, 179.45, 0.02, 2.02, {-147.01, -127.93}, {-17.84, -5.39}, {167.75, 192.25}},
0.01, // claw_unimportant_epsilon
0.9, // start_fine_tune_pos
4.0,
diff --git a/frc971/constants.h b/frc971/constants.h
index a5b7fb1..2a9c642 100755
--- a/frc971/constants.h
+++ b/frc971/constants.h
@@ -11,8 +11,8 @@
// Has all of the numbers that change for both robots and makes it easy to
// retrieve the values for the current one.
-const uint16_t kCompTeamNumber = 8971;
-const uint16_t kPracticeTeamNumber = 971;
+const uint16_t kCompTeamNumber = 971;
+const uint16_t kPracticeTeamNumber = 8971;
// Contains the voltages for an analog hall effect sensor on a shifter.
struct ShifterHallEffect {
diff --git a/frc971/control_loops/claw/claw.cc b/frc971/control_loops/claw/claw.cc
index cfe61a0..b6d3b2f 100755
--- a/frc971/control_loops/claw/claw.cc
+++ b/frc971/control_loops/claw/claw.cc
@@ -53,12 +53,10 @@
const double difference =
uncapped_average_voltage_ - values.claw.max_zeroing_voltage;
U(0, 0) -= difference;
- U(1, 0) -= difference;
} else if (uncapped_average_voltage_ < -values.claw.max_zeroing_voltage) {
const double difference =
-uncapped_average_voltage_ - values.claw.max_zeroing_voltage;
U(0, 0) += difference;
- U(1, 0) += difference;
}
}
@@ -83,7 +81,8 @@
claw_(MakeClawLoop()),
was_enabled_(false),
doing_calibration_fine_tune_(false),
- capped_goal_(false) {}
+ capped_goal_(false),
+ mode_(UNKNOWN_LOCATION) {}
const int ZeroedStateFeedbackLoop::kZeroingMaxVoltage;
@@ -211,6 +210,21 @@
LOG(INFO, "Changing bottom offset by %f\n", doffset);
}
+bool ClawMotor::is_ready() const {
+ return (
+ (top_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED &&
+ bottom_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED) ||
+ (::aos::robot_state->autonomous &&
+ ((top_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED ||
+ top_claw_.zeroing_state() ==
+ ZeroedStateFeedbackLoop::DISABLED_CALIBRATION) &&
+ (bottom_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED ||
+ bottom_claw_.zeroing_state() ==
+ ZeroedStateFeedbackLoop::DISABLED_CALIBRATION))));
+}
+
+bool ClawMotor::is_zeroing() const { return !is_ready(); }
+
// Positive angle is up, and positive power is up.
void ClawMotor::RunIteration(const control_loops::ClawGroup::Goal *goal,
const control_loops::ClawGroup::Position *position,
@@ -249,13 +263,13 @@
if (!has_top_claw_goal_) {
has_top_claw_goal_ = true;
top_claw_goal_ = top_claw_.absolute_position();
- initial_seperation_ =
+ initial_separation_ =
top_claw_.absolute_position() - bottom_claw_.absolute_position();
}
if (!has_bottom_claw_goal_) {
has_bottom_claw_goal_ = true;
bottom_claw_goal_ = bottom_claw_.absolute_position();
- initial_seperation_ =
+ initial_separation_ =
top_claw_.absolute_position() - bottom_claw_.absolute_position();
}
LOG(DEBUG, "Claw position is (top: %f bottom: %f\n",
@@ -265,14 +279,6 @@
bool autonomous = ::aos::robot_state->autonomous;
bool enabled = ::aos::robot_state->enabled;
- enum CalibrationMode {
- READY,
- FINE_TUNE,
- UNKNOWN_LOCATION
- };
-
- CalibrationMode mode;
-
if ((top_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED &&
bottom_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED) ||
(autonomous &&
@@ -290,13 +296,14 @@
has_top_claw_goal_ = true;
doing_calibration_fine_tune_ = false;
- mode = READY;
+ mode_ = READY;
} else if (top_claw_.zeroing_state() !=
ZeroedStateFeedbackLoop::UNKNOWN_POSITION &&
bottom_claw_.zeroing_state() !=
ZeroedStateFeedbackLoop::UNKNOWN_POSITION) {
// Time to fine tune the zero.
// Limit the goals here.
+ // TODO(austin): Handle disabled state here.
if (bottom_claw_.zeroing_state() != ZeroedStateFeedbackLoop::CALIBRATED) {
// always get the bottom claw to calibrated first
LOG(DEBUG, "Calibrating the bottom of the claw\n");
@@ -307,12 +314,15 @@
doing_calibration_fine_tune_ = true;
bottom_claw_goal_ += values.claw.claw_zeroing_speed * dt;
LOG(DEBUG, "Ready to fine tune the bottom\n");
+ mode_ = FINE_TUNE_BOTTOM;
} else {
// send bottom to zeroing start
bottom_claw_goal_ = values.claw.start_fine_tune_pos;
LOG(DEBUG, "Going to the start position for the bottom\n");
+ mode_ = PREP_FINE_TUNE_BOTTOM;
}
} else {
+ mode_ = FINE_TUNE_BOTTOM;
bottom_claw_goal_ += values.claw.claw_zeroing_speed * dt;
if (top_claw_.front_hall_effect() || top_claw_.back_hall_effect() ||
bottom_claw_.front_hall_effect() ||
@@ -322,6 +332,7 @@
doing_calibration_fine_tune_ = false;
bottom_claw_goal_ = values.claw.start_fine_tune_pos;
LOG(DEBUG, "Found a limit, starting over.\n");
+ mode_ = PREP_FINE_TUNE_BOTTOM;
}
if (bottom_claw_.calibration_hall_effect()) {
@@ -338,6 +349,7 @@
} else {
doing_calibration_fine_tune_ = false;
bottom_claw_goal_ = values.claw.start_fine_tune_pos;
+ mode_ = PREP_FINE_TUNE_BOTTOM;
}
} else {
LOG(DEBUG, "Fine tuning\n");
@@ -355,12 +367,15 @@
doing_calibration_fine_tune_ = true;
top_claw_goal_ += values.claw.claw_zeroing_speed * dt;
LOG(DEBUG, "Ready to fine tune the top\n");
+ mode_ = FINE_TUNE_TOP;
} else {
// send top to zeroing start
top_claw_goal_ = values.claw.start_fine_tune_pos;
LOG(DEBUG, "Going to the start position for the top\n");
+ mode_ = PREP_FINE_TUNE_TOP;
}
} else {
+ mode_ = FINE_TUNE_TOP;
top_claw_goal_ += values.claw.claw_zeroing_speed * dt;
if (top_claw_.front_hall_effect() || top_claw_.back_hall_effect() ||
bottom_claw_.front_hall_effect() ||
@@ -369,6 +384,7 @@
doing_calibration_fine_tune_ = false;
top_claw_goal_ = values.claw.start_fine_tune_pos;
LOG(DEBUG, "Found a limit, starting over.\n");
+ mode_ = PREP_FINE_TUNE_TOP;
}
if (top_claw_.calibration_hall_effect()) {
if (top_claw_.calibration_hall_effect_posedge_count_changed() &&
@@ -378,26 +394,26 @@
position->top.posedge_value,
values.claw.upper_claw.calibration.lower_angle);
top_claw_.set_zeroing_state(ZeroedStateFeedbackLoop::CALIBRATED);
- // calinrated so we are done fine tuning top
+ // calibrated so we are done fine tuning top
doing_calibration_fine_tune_ = false;
LOG(DEBUG, "Calibrated the top correctly!\n");
} else {
doing_calibration_fine_tune_ = false;
top_claw_goal_ = values.claw.start_fine_tune_pos;
+ mode_ = PREP_FINE_TUNE_TOP;
}
}
}
// now set the bottom claw to track
bottom_claw_goal_ = top_claw_goal_ - values.claw.claw_zeroing_separation;
}
- mode = FINE_TUNE;
} else {
doing_calibration_fine_tune_ = false;
if (!was_enabled_ && enabled) {
if (position) {
top_claw_goal_ = position->top.position;
bottom_claw_goal_ = position->bottom.position;
- initial_seperation_ =
+ initial_separation_ =
position->top.position - position->bottom.position;
} else {
has_top_claw_goal_ = false;
@@ -441,12 +457,9 @@
bottom_claw_.SetCalibrationOnEdge(
values.claw.lower_claw, ZeroedStateFeedbackLoop::DISABLED_CALIBRATION);
}
- mode = UNKNOWN_LOCATION;
+ mode_ = UNKNOWN_LOCATION;
}
- // TODO(austin): Handle disabled properly everwhere... Restart and all that
- // jazz.
-
if (has_top_claw_goal_ && has_bottom_claw_goal_) {
claw_.R << bottom_claw_goal_, top_claw_goal_ - bottom_claw_goal_, 0, 0;
double separation = -971;
@@ -457,18 +470,21 @@
claw_.R(1, 0), separation);
// Only cap power when one of the halves of the claw is unknown.
- claw_.set_is_zeroing(mode == UNKNOWN_LOCATION);
+ claw_.set_is_zeroing(mode_ == UNKNOWN_LOCATION || mode_ == FINE_TUNE_TOP ||
+ mode_ == FINE_TUNE_BOTTOM);
claw_.Update(output == nullptr);
} else {
claw_.Update(true);
}
capped_goal_ = false;
- switch (mode) {
+ switch (mode_) {
case READY:
+ case PREP_FINE_TUNE_TOP:
+ case PREP_FINE_TUNE_BOTTOM:
break;
- case FINE_TUNE:
- break;
+ case FINE_TUNE_BOTTOM:
+ case FINE_TUNE_TOP:
case UNKNOWN_LOCATION: {
if (claw_.uncapped_average_voltage() > values.claw.max_zeroing_voltage) {
double dx = (claw_.uncapped_average_voltage() -
@@ -478,6 +494,10 @@
top_claw_goal_ -= dx;
capped_goal_ = true;
LOG(DEBUG, "Moving the goal by %f to prevent windup\n", dx);
+ LOG(DEBUG, "Uncapped is %f, max is %f, difference is %f\n",
+ claw_.uncapped_average_voltage(), values.claw.max_zeroing_voltage,
+ (claw_.uncapped_average_voltage() -
+ values.claw.max_zeroing_voltage));
} else if (claw_.uncapped_average_voltage() <
-values.claw.max_zeroing_voltage) {
double dx = (claw_.uncapped_average_voltage() +
diff --git a/frc971/control_loops/claw/claw.h b/frc971/control_loops/claw/claw.h
index 20b30b6..34786b8 100755
--- a/frc971/control_loops/claw/claw.h
+++ b/frc971/control_loops/claw/claw.h
@@ -12,8 +12,7 @@
namespace frc971 {
namespace control_loops {
namespace testing {
-class ClawTest_NoWindupPositive_Test;
-class ClawTest_NoWindupNegative_Test;
+class WindupClawTest;
};
// Note: Everything in this file assumes that there is a 1 cycle delay between
@@ -241,12 +240,30 @@
// True if the state machine is ready.
bool capped_goal() const { return capped_goal_; }
+ double uncapped_average_voltage() const {
+ return claw_.uncapped_average_voltage();
+ }
+
+ // True if the claw is zeroing.
+ bool is_zeroing() const;
+
// True if the state machine is ready.
- bool is_ready() const { return false; }
+ bool is_ready() const;
void ChangeTopOffset(double doffset);
void ChangeBottomOffset(double doffset);
+ enum CalibrationMode {
+ READY,
+ PREP_FINE_TUNE_TOP,
+ FINE_TUNE_TOP,
+ PREP_FINE_TUNE_BOTTOM,
+ FINE_TUNE_BOTTOM,
+ UNKNOWN_LOCATION
+ };
+
+ CalibrationMode mode() const { return mode_; }
+
protected:
virtual void RunIteration(const control_loops::ClawGroup::Goal *goal,
const control_loops::ClawGroup::Position *position,
@@ -260,8 +277,7 @@
private:
// Friend the test classes for acces to the internal state.
- friend class testing::ClawTest_NoWindupPositive_Test;
- friend class testing::ClawTest_NoWindupNegative_Test;
+ friend class testing::WindupClawTest;
// The zeroed joint to use.
bool has_top_claw_goal_;
@@ -280,9 +296,10 @@
// The initial seperation when disabled. Used as the safe seperation
// distance.
- double initial_seperation_;
+ double initial_separation_;
bool capped_goal_;
+ CalibrationMode mode_;
DISALLOW_COPY_AND_ASSIGN(ClawMotor);
};
diff --git a/frc971/control_loops/claw/claw_lib_test.cc b/frc971/control_loops/claw/claw_lib_test.cc
index 0d4a7be..774a4c1 100644
--- a/frc971/control_loops/claw/claw_lib_test.cc
+++ b/frc971/control_loops/claw/claw_lib_test.cc
@@ -430,6 +430,7 @@
EXPECT_TRUE(min_seperation_ <= seperation);
}
+
virtual ~ClawTest() {
::aos::robot_state.Clear();
}
@@ -441,28 +442,10 @@
.bottom_angle(0.1)
.seperation_angle(0.2)
.Send();
- for (int i = 0; i < 400; ++i) {
- claw_motor_plant_.SendPositionMessage();
- claw_motor_.Iterate();
- claw_motor_plant_.Simulate();
- SendDSPacket(true);
- }
- VerifyNearGoal();
-}
-
-// Tests that the wrist zeros correctly starting on the hall effect sensor.
-TEST_F(ClawTest, ZerosInTheMiddle) {
- claw_motor_plant_.Reinitialize(0.5, 0.4);
-
- claw_queue_group.goal.MakeWithBuilder()
- .bottom_angle(0.1)
- .seperation_angle(0.2)
- .Send();
for (int i = 0; i < 500; ++i) {
claw_motor_plant_.SendPositionMessage();
claw_motor_.Iterate();
claw_motor_plant_.Simulate();
-
SendDSPacket(true);
}
VerifyNearGoal();
@@ -480,7 +463,7 @@
.bottom_angle(0.1)
.seperation_angle(0.2)
.Send();
- for (int i = 0; i < 600; ++i) {
+ for (int i = 0; i < 700; ++i) {
claw_motor_plant_.SendPositionMessage();
claw_motor_.Iterate();
claw_motor_plant_.Simulate();
@@ -498,7 +481,7 @@
.bottom_angle(0.1)
.seperation_angle(0.2)
.Send();
- for (int i = 0; i < 600; ++i) {
+ for (int i = 0; i < 700; ++i) {
if (i % 23) {
claw_motor_plant_.SendPositionMessage();
}
@@ -607,127 +590,100 @@
}
VerifyNearGoal();
}
-
-// Tests that the wrist can't get too far away from the zeroing position if the
-// zeroing position is saturating the goal.
-TEST_F(ClawTest, NoWindupNegative) {
- int capped_count[2] = {0, 0};
- double saved_zeroing_position[2] = {0, 0};
- claw_queue_group.goal.MakeWithBuilder()
- .bottom_angle(0.1)
- .seperation_angle(0.2)
- .Send();
- for (int i = 0; i < 500; ++i) {
- claw_motor_plant_.SendPositionMessage();
- if (i == 50) {
- EXPECT_TRUE(claw_motor_.is_zeroing());
- // Move the zeroing position far away and verify that it gets moved back.
- saved_zeroing_position[TOP_CLAW] =
- top_claw_motor_.zeroed_joint_.zeroing_position_;
- top_claw_motor_.zeroed_joint_.zeroing_position_ = -100.0;
- saved_zeroing_position[BOTTOM_CLAW] =
- bottom_claw_motor_.zeroed_joint_.zeroing_position_;
- bottom_claw_motor_.zeroed_joint_.zeroing_position_ = -100.0;
- } else if (i == 51) {
- EXPECT_TRUE(claw_motor_.is_zeroing());
-
- EXPECT_NEAR(saved_zeroing_position[TOP_CLAW],
- top_claw_motor_.zeroed_joint_.zeroing_position_, 0.4);
- EXPECT_NEAR(saved_zeroing_position[BOTTOM_CLAW],
- bottom_claw_motor_.zeroed_joint_.zeroing_position_, 0.4);
- }
- if (!claw_motor_.top().is_ready()) {
- if (claw_motor_.top().capped_goal()) {
- ++capped_count[TOP_CLAW];
- // The cycle after we kick the zero position is the only cycle during
- // which we should expect to see a high uncapped power during zeroing.
- ASSERT_LT(5, ::std::abs(top_claw_motor_.zeroed_joint_.U_uncapped()));
- } else {
- ASSERT_GT(5, ::std::abs(top_claw_motor_.zeroed_joint_.U_uncapped()));
- }
- }
- if (!claw_motor_.bottom().is_ready()) {
- if (claw_motor_.bottom().capped_goal()) {
- ++capped_count[BOTTOM_CLAW];
- // The cycle after we kick the zero position is the only cycle during
- // which we should expect to see a high uncapped power during zeroing.
- ASSERT_LT(5, ::std::abs(bottom_claw_motor_.zeroed_joint_.U_uncapped()));
- } else {
- ASSERT_GT(5, ::std::abs(bottom_claw_motor_.zeroed_joint_.U_uncapped()));
- }
- }
-
- claw_motor_.Iterate();
- claw_motor_plant_.Simulate();
- SendDSPacket(true);
- }
- VerifyNearGoal();
- EXPECT_GT(3, capped_count[TOP_CLAW]);
- EXPECT_GT(3, capped_count[BOTTOM_CLAW]);
-}
-
-// Tests that the wrist can't get too far away from the zeroing position if the
-// zeroing position is saturating the goal.
-TEST_F(ClawTest, NoWindupPositive) {
- int capped_count[2] = {0, 0};
- double saved_zeroing_position[2] = {0, 0};
- claw_queue_group.goal.MakeWithBuilder()
- .bottom_angle(0.1)
- .seperation_angle(0.2)
- .Send();
- for (int i = 0; i < 500; ++i) {
- claw_motor_plant_.SendPositionMessage();
- if (i == 50) {
- EXPECT_TRUE(top_claw_motor_.is_zeroing());
- EXPECT_TRUE(top_claw_motor_.is_zeroing());
- // Move the zeroing position far away and verify that it gets moved back.
- saved_zeroing_position[TOP_CLAW] =
- top_claw_motor_.zeroed_joint_.zeroing_position_;
- top_claw_motor_.zeroed_joint_.zeroing_position_ = 100.0;
- saved_zeroing_position[BOTTOM_CLAW] =
- bottom_claw_motor_.zeroed_joint_.zeroing_position_;
- top_claw_motor_.zeroed_joint_.zeroing_position_ = 100.0;
- } else {
- if (i == 51) {
- EXPECT_TRUE(claw_motor_.top().is_zeroing());
- EXPECT_TRUE(claw_motor_.bottom().is_zeroing());
- EXPECT_NEAR(saved_zeroing_position[TOP_CLAW],
- claw_motor_.top().zeroing_position_, 0.4);
- EXPECT_NEAR(saved_zeroing_position[BOTTOM_CLAW],
- claw_motor_.bottom().zeroing_position_, 0.4);
- }
- }
- if (!top_claw_motor_.is_ready()) {
- if (top_claw_motor_.capped_goal()) {
- ++capped_count[TOP_CLAW];
- // The cycle after we kick the zero position is the only cycle during
- // which we should expect to see a high uncapped power during zeroing.
- ASSERT_LT(5, ::std::abs(claw_motor_.top().zeroed_joint_.U_uncapped()));
- } else {
- ASSERT_GT(5, ::std::abs(claw_motor_.top().zeroed_joint_.U_uncapped()));
- }
- }
- if (!bottom_claw_motor_.is_ready()) {
- if (bottom_claw_motor_.capped_goal()) {
- ++capped_count[BOTTOM_CLAW];
- // The cycle after we kick the zero position is the only cycle during
- // which we should expect to see a high uncapped power during zeroing.
- ASSERT_LT(5, ::std::abs(claw_motor_.bottom().zeroed_joint_.U_uncapped()));
- } else {
- ASSERT_GT(5, ::std::abs(claw_motor_.bottom().zeroed_joint_.U_uncapped()));
- }
- }
-
- claw_motor_.Iterate();
- claw_motor_plant_.Simulate();
- SendDSPacket(true);
- }
- VerifyNearGoal();
- EXPECT_GT(3, capped_count[TOP_CLAW]);
- EXPECT_GT(3, capped_count[BOTTOM_CLAW]);
-}
*/
+class WindupClawTest : public ClawTest {
+ protected:
+ void TestWindup(ClawMotor::CalibrationMode mode, int start_time, double offset) {
+ int capped_count = 0;
+ double saved_zeroing_position[2] = {0, 0};
+ const frc971::constants::Values& values = constants::GetValues();
+ bool kicked = false;
+ bool measured = false;
+ for (int i = 0; i < 600; ++i) {
+ claw_motor_plant_.SendPositionMessage();
+ if (i >= start_time && mode == claw_motor_.mode() && !kicked) {
+ EXPECT_EQ(mode, claw_motor_.mode());
+ // Move the zeroing position far away and verify that it gets moved
+ // back.
+ saved_zeroing_position[TOP_CLAW] = claw_motor_.top_claw_goal_;
+ saved_zeroing_position[BOTTOM_CLAW] = claw_motor_.bottom_claw_goal_;
+ claw_motor_.top_claw_goal_ += offset;
+ claw_motor_.bottom_claw_goal_ += offset;
+ kicked = true;
+ } else {
+ if (kicked && !measured) {
+ measured = true;
+ EXPECT_EQ(mode, claw_motor_.mode());
+
+ EXPECT_NEAR(saved_zeroing_position[TOP_CLAW],
+ claw_motor_.top_claw_goal_, 0.1);
+ EXPECT_NEAR(saved_zeroing_position[BOTTOM_CLAW],
+ claw_motor_.bottom_claw_goal_, 0.1);
+ }
+ }
+ if (claw_motor_.mode() == mode) {
+ if (claw_motor_.capped_goal()) {
+ ++capped_count;
+ // The cycle after we kick the zero position is the only cycle during
+ // which we should expect to see a high uncapped power during zeroing.
+ ASSERT_LT(values.claw.max_zeroing_voltage,
+ ::std::abs(claw_motor_.uncapped_average_voltage()));
+ } else {
+ ASSERT_GT(values.claw.max_zeroing_voltage,
+ ::std::abs(claw_motor_.uncapped_average_voltage()));
+ }
+ }
+
+ claw_motor_.Iterate();
+ claw_motor_plant_.Simulate();
+ SendDSPacket(true);
+ }
+ EXPECT_TRUE(kicked);
+ EXPECT_TRUE(measured);
+ EXPECT_EQ(1, capped_count);
+ }
+};
+
+// Tests that the wrist can't get too far away from the zeroing position if the
+// zeroing position is saturating the goal.
+TEST_F(WindupClawTest, NoWindupPositive) {
+ claw_queue_group.goal.MakeWithBuilder()
+ .bottom_angle(0.1)
+ .seperation_angle(0.2)
+ .Send();
+
+ TestWindup(ClawMotor::UNKNOWN_LOCATION, 100, 971.0);
+
+ VerifyNearGoal();
+}
+
+// Tests that the wrist can't get too far away from the zeroing position if the
+// zeroing position is saturating the goal.
+TEST_F(WindupClawTest, NoWindupNegative) {
+ claw_queue_group.goal.MakeWithBuilder()
+ .bottom_angle(0.1)
+ .seperation_angle(0.2)
+ .Send();
+
+ TestWindup(ClawMotor::UNKNOWN_LOCATION, 100, -971.0);
+
+ VerifyNearGoal();
+}
+
+// Tests that the wrist can't get too far away from the zeroing position if the
+// zeroing position is saturating the goal.
+TEST_F(WindupClawTest, NoWindupNegativeFineTune) {
+ claw_queue_group.goal.MakeWithBuilder()
+ .bottom_angle(0.1)
+ .seperation_angle(0.2)
+ .Send();
+
+ TestWindup(ClawMotor::FINE_TUNE_BOTTOM, 200, -971.0);
+
+ VerifyNearGoal();
+}
+
} // namespace testing
} // namespace control_loops
} // namespace frc971