Indexer shoots and passes the tests. Doing the last little bit of cleanup.
diff --git a/frc971/control_loops/index.cc b/frc971/control_loops/index.cc
index 845f1c6..6d72fe2 100644
--- a/frc971/control_loops/index.cc
+++ b/frc971/control_loops/index.cc
@@ -23,37 +23,48 @@
wrist_loop_(new StateFeedbackLoop<2, 1, 1>(MakeIndexLoop())),
hopper_disc_count_(0),
total_disc_count_(0),
+ safe_goal_(Goal::HOLD),
+ loader_goal_(LoaderGoal::READY),
+ loader_state_(LoaderState::READY),
loader_up_(false),
disc_clamped_(false),
disc_ejected_(false),
last_bottom_disc_detect_(false) {
}
+/*static*/ const double IndexMotor::kTransferStartPosition = 0.0;
+/*static*/ const double IndexMotor::kIndexStartPosition = 0.2159;
+/*static*/ const double IndexMotor::kIndexFreeLength =
+ IndexMotor::ConvertDiscAngleToDiscPosition((360 * 2 + 14) * M_PI / 180);
+/*static*/ const double IndexMotor::kLoaderFreeStopPosition =
+ kIndexStartPosition + kIndexFreeLength;
+/*static*/ const double IndexMotor::kReadyToLiftPosition =
+ kLoaderFreeStopPosition + 0.2921;
+/*static*/ const double IndexMotor::kGrabberLength = 0.03175;
+/*static*/ const double IndexMotor::kGrabberStartPosition =
+ kReadyToLiftPosition - kGrabberLength;
+/*static*/ const double IndexMotor::kGrabberMovementVelocity = 0.5;
+/*static*/ const double IndexMotor::kLifterStopPosition =
+ kReadyToLiftPosition + 0.161925;
+/*static*/ const double IndexMotor::kLifterMovementVelocity = 1.0;
+/*static*/ const double IndexMotor::kEjectorStopPosition =
+ kLifterStopPosition + 0.01;
+/*static*/ const double IndexMotor::kEjectorMovementVelocity = 1.0;
+/*static*/ const double IndexMotor::kBottomDiscDetectStart = -0.08;
+/*static*/ const double IndexMotor::kBottomDiscDetectStop = 0.200025;
+
+// TODO(aschuh): Figure these out.
+/*static*/ const double IndexMotor::kTopDiscDetectStart = 18.0;
+/*static*/ const double IndexMotor::kTopDiscDetectStop = 19.0;
+
const /*static*/ double IndexMotor::kDiscRadius = 10.875 * 0.0254 / 2;
const /*static*/ double IndexMotor::kRollerRadius = 2.0 * 0.0254 / 2;
+const /*static*/ double IndexMotor::kTransferRollerRadius = 1.25 * 0.0254 / 2;
-bool IndexMotor::FetchConstants() {
- if (!constants::horizontal_lower_limit(&horizontal_lower_limit_)) {
- LOG(ERROR, "Failed to fetch the horizontal lower limit constant.\n");
- return false;
- }
- if (!constants::horizontal_upper_limit(&horizontal_upper_limit_)) {
- LOG(ERROR, "Failed to fetch the horizontal upper limit constant.\n");
- return false;
- }
- if (!constants::horizontal_hall_effect_start_angle(
- &horizontal_hall_effect_start_angle_)) {
- LOG(ERROR, "Failed to fetch the horizontal start angle constant.\n");
- return false;
- }
- if (!constants::horizontal_zeroing_speed(
- &horizontal_zeroing_speed_)) {
- LOG(ERROR, "Failed to fetch the horizontal zeroing speed constant.\n");
- return false;
- }
-
- return true;
-}
+/*static*/ const int IndexMotor::kGrabbingDelay = 5;
+/*static*/ const int IndexMotor::kLiftingDelay = 20;
+/*static*/ const int IndexMotor::kShootingDelay = 5;
+/*static*/ const int IndexMotor::kLoweringDelay = 20;
// Distance to move the indexer when grabbing a disc.
const double kNextPosition = 10.0;
@@ -62,10 +73,16 @@
return (angle * (1 + (kDiscRadius * 2 + kRollerRadius) / kRollerRadius));
}
-/*static*/ double IndexMotor::ConvertDiscAngleToDiscPosition(const double angle) {
+/*static*/ double IndexMotor::ConvertDiscAngleToDiscPosition(
+ const double angle) {
return angle * (kDiscRadius + kRollerRadius);
}
+/*static*/ double IndexMotor::ConvertDiscPositionToDiscAngle(
+ const double position) {
+ return position / (kDiscRadius + kRollerRadius);
+}
+
/*static*/ double IndexMotor::ConvertIndexToDiscAngle(const double angle) {
return (angle / (1 + (kDiscRadius * 2 + kRollerRadius) / kRollerRadius));
}
@@ -75,6 +92,53 @@
ConvertIndexToDiscAngle(angle));
}
+/*static*/ double IndexMotor::ConvertTransferToDiscPosition(
+ const double angle) {
+ const double gear_ratio = (1 + (kDiscRadius * 2 + kTransferRollerRadius) /
+ kTransferRollerRadius);
+ return angle / gear_ratio * (kDiscRadius + kTransferRollerRadius);
+}
+
+/*static*/ double IndexMotor::ConvertDiscPositionToIndex(
+ const double position) {
+ return IndexMotor::ConvertDiscAngleToIndex(
+ ConvertDiscPositionToDiscAngle(position));
+}
+
+bool IndexMotor::MinDiscPosition(double *disc_position) {
+ bool found_start = false;
+ for (unsigned int i = 0; i < frisbees_.size(); ++i) {
+ const Frisbee &frisbee = frisbees_[i];
+ if (!found_start) {
+ if (frisbee.has_position()) {
+ *disc_position = frisbee.position();
+ found_start = true;
+ }
+ } else {
+ *disc_position = ::std::min(frisbee.position(),
+ *disc_position);
+ }
+ }
+ return found_start;
+}
+
+bool IndexMotor::MaxDiscPosition(double *disc_position) {
+ bool found_start = false;
+ for (unsigned int i = 0; i < frisbees_.size(); ++i) {
+ const Frisbee &frisbee = frisbees_[i];
+ if (!found_start) {
+ if (frisbee.has_position()) {
+ *disc_position = frisbee.position();
+ found_start = true;
+ }
+ } else {
+ *disc_position = ::std::max(frisbee.position(),
+ *disc_position);
+ }
+ }
+ return found_start;
+}
+
// Positive angle is towards the shooter, and positive power is towards the
// shooter.
void IndexMotor::RunIteration(
@@ -87,6 +151,7 @@
// Disable the motors now so that all early returns will return with the
// motors disabled.
+ double transfer_voltage = 0.0;
if (output) {
output->transfer_voltage = 0.0;
output->index_voltage = 0.0;
@@ -94,32 +159,28 @@
status->ready_to_intake = false;
- // Cache the constants to avoid error handling down below.
- if (!FetchConstants()) {
- return;
- }
-
+ // Compute a safe index position that we can use.
if (position) {
wrist_loop_->Y << position->index_position;
}
const double index_position = wrist_loop_->X_hat(0, 0);
+ // TODO(aschuh): Watch for top disc detect and update the frisbee
+ // position.
+
+ // TODO(aschuh): Horizontal and centering should be here as well...
+
+ // Bool to track if it is safe for the goal to change yet.
bool safe_to_change_state_ = true;
switch (safe_goal_) {
- case HOLD:
+ case Goal::HOLD:
// The goal should already be good, so sit tight with everything the same
// as it was.
- printf("HOLD Not implemented\n");
break;
- case READY_LOWER:
- printf("READY_LOWER Not implemented\n");
- break;
- case INTAKE:
+ case Goal::READY_LOWER:
+ case Goal::INTAKE:
{
Time now = Time::Now();
- if (hopper_disc_count_ < 4) {
- output->transfer_voltage = 12.0;
- }
// Posedge of the disc entering the beam break.
if (position) {
if (position->bottom_disc_detect && !last_bottom_disc_detect_) {
@@ -128,6 +189,7 @@
printf("Posedge of bottom disc %f\n",
transfer_frisbee_.bottom_posedge_time_.ToSeconds());
++hopper_disc_count_;
+ ++total_disc_count_;
}
// Disc exited the beam break now.
@@ -139,12 +201,12 @@
}
if (position->bottom_disc_detect) {
- output->transfer_voltage = 12.0;
+ transfer_voltage = 12.0;
// Must wait until the disc gets out before we can change state.
safe_to_change_state_ = false;
- // TODO(aschuh): A disc on the way through needs to start moving the
- // indexer if it isn't already moving. Maybe?
+ // TODO(aschuh): A disc on the way through needs to start moving
+ // the indexer if it isn't already moving. Maybe?
Time elapsed_posedge_time = now -
transfer_frisbee_.bottom_posedge_time_;
@@ -155,12 +217,13 @@
}
}
+ // Check all non-indexed discs and see if they should be indexed.
for (Frisbee &frisbee : frisbees_) {
if (!frisbee.has_been_indexed_) {
- output->transfer_voltage = 12.0;
- Time elapsed_posedge_time = now -
- frisbee.bottom_posedge_time_;
- if (elapsed_posedge_time >= Time::InSeconds(0.07)) {
+ transfer_voltage = 12.0;
+ Time elapsed_negedge_time = now -
+ frisbee.bottom_negedge_time_;
+ if (elapsed_negedge_time >= Time::InSeconds(0.005)) {
// Should have just engaged.
// Save the indexer position, and the time.
@@ -170,44 +233,236 @@
printf("Grabbed on the index now at %f\n", index_position);
frisbee.has_been_indexed_ = true;
frisbee.index_start_position_ = index_position;
- frisbee.index_start_time_ = now;
}
}
if (!frisbee.has_been_indexed_) {
- // Discs must all be indexed before it is safe to stop indexing.
+ // All discs must be indexed before it is safe to stop indexing.
safe_to_change_state_ = false;
}
}
- double new_index_position = wrist_loop_->R(0, 0);
+ // Figure out where the indexer should be to move the discs down to
+ // the right position.
+ double max_disc_position;
+ if (MaxDiscPosition(&max_disc_position)) {
+ printf("There is a disc down here!\n");
+ // TODO(aschuh): Figure out what to do if grabbing the next one
+ // would cause things to jam into the loader.
+ // Say we aren't ready any more. Undefined behavior will result if
+ // that isn't observed.
+ double bottom_disc_position =
+ max_disc_position + ConvertDiscAngleToIndex(M_PI);
+ wrist_loop_->R << bottom_disc_position, 0.0;
- // TODO(aschuh): As we loop through, assess the state of the indexer
- // and figure if the bottom disc is in a place such that we can
- // intake without filling the hopper early.
- // status->ready_to_intake = false;
-
- for (Frisbee &frisbee : frisbees_) {
- if (frisbee.has_been_indexed_) {
- // We want to store it pi from where the disc was grabbed
- // (for now).
- new_index_position = ::std::max(
- new_index_position,
- (frisbee.index_start_position_ +
- ConvertDiscAngleToIndex(M_PI)));
- // TODO(aschuh): We should be able to pick the M_PI knowing if
- // the next disc is coming in hot or not.
+ // Verify that we are close enough to the goal so that we should be
+ // fine accepting the next disc.
+ double disc_error_meters = ConvertIndexToDiscPosition(
+ wrist_loop_->X_hat(0, 0) - bottom_disc_position);
+ // We are ready for the next disc if the first one is in the first
+ // half circle of the indexer. It will take time for the disc to
+ // come into the indexer, so we will be able to move it out of the
+ // way in time.
+ // This choice also makes sure that we don't claim that we aren't
+ // ready between full speed intaking.
+ if (-ConvertDiscAngleToIndex(M_PI) < disc_error_meters &&
+ disc_error_meters < 0.04) {
+ // We are only ready if we aren't being asked to change state or
+ // are full.
+ status->ready_to_intake =
+ (safe_goal_ == goal_enum) && hopper_disc_count_ < 4;
+ } else {
+ status->ready_to_intake = false;
}
+ } else {
+ // No discs! We are always ready for more if we aren't being
+ // asked to change state.
+ status->ready_to_intake = (safe_goal_ == goal_enum);
}
- wrist_loop_->R << new_index_position, 0.0;
+
+ // Turn on the transfer roller if we are ready.
+ if (status->ready_to_intake && hopper_disc_count_ < 4 &&
+ safe_goal_ == Goal::INTAKE) {
+ transfer_voltage = 12.0;
+ }
}
- printf("INTAKE Not implemented\n");
+ printf("INTAKE\n");
}
break;
- case READY_SHOOTER:
- printf("READY_SHOOTER Not implemented\n");
+ case Goal::READY_SHOOTER:
+ case Goal::SHOOT:
+ // Check if we have any discs to shoot or load and handle them.
+ double min_disc_position;
+ if (MinDiscPosition(&min_disc_position)) {
+ const double ready_disc_position =
+ min_disc_position + ConvertDiscPositionToIndex(kIndexFreeLength) -
+ ConvertDiscAngleToIndex(M_PI / 6.0);
+
+ const double grabbed_disc_position =
+ min_disc_position +
+ ConvertDiscPositionToIndex(kReadyToLiftPosition -
+ kIndexStartPosition + 0.03);
+
+ // Check the state of the loader FSM.
+ // If it is ready to load discs, position the disc so that it is ready
+ // to be grabbed.
+ // If it isn't ready, there is a disc in there. It needs to finish it's
+ // cycle first.
+ if (loader_state_ != LoaderState::READY) {
+ // We already have a disc in the loader.
+ // Stage the discs back a bit.
+ wrist_loop_->R << ready_disc_position, 0.0;
+
+ // Must wait until it has been grabbed to continue.
+ if (loader_state_ == LoaderState::GRABBING) {
+ safe_to_change_state_ = false;
+ }
+ } else {
+ // No disc up top right now.
+ wrist_loop_->R << grabbed_disc_position, 0.0;
+
+ // See if the disc has gotten pretty far up yet.
+ if (wrist_loop_->X_hat(0, 0) > ready_disc_position) {
+ // Point of no return. We are committing to grabbing it now.
+ safe_to_change_state_ = false;
+ const double robust_grabbed_disc_position =
+ (grabbed_disc_position -
+ ConvertDiscPositionToIndex(kGrabberLength));
+
+ // If close, start grabbing and/or shooting.
+ if (wrist_loop_->X_hat(0, 0) > robust_grabbed_disc_position) {
+ // Start the state machine.
+ if (safe_goal_ == Goal::SHOOT) {
+ loader_goal_ = LoaderGoal::SHOOT_AND_RESET;
+ } else {
+ loader_goal_ = LoaderGoal::GRAB;
+ }
+ // This frisbee is now gone. Take it out of the queue.
+ frisbees_.pop_back();
+ --hopper_disc_count_;
+ }
+ }
+ }
+ }
+
+ printf("READY_SHOOTER or SHOOT\n");
break;
- case SHOOT:
- printf("SHOOT Not implemented\n");
+ }
+
+ // The only way out of the loader is to shoot the disc. The FSM can only go
+ // forwards.
+ switch (loader_state_) {
+ case LoaderState::READY:
+ printf("Loader READY\n");
+ // Open and down, ready to accept a disc.
+ loader_up_ = false;
+ disc_clamped_ = false;
+ disc_ejected_ = false;
+ if (loader_goal_ == LoaderGoal::GRAB ||
+ loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
+ if (loader_goal_ == LoaderGoal::GRAB) {
+ printf("Told to GRAB, moving on\n");
+ } else {
+ printf("Told to SHOOT_AND_RESET, moving on\n");
+ }
+ loader_state_ = LoaderState::GRABBING;
+ loader_countdown_ = kGrabbingDelay;
+ } else {
+ break;
+ }
+ case LoaderState::GRABBING:
+ printf("Loader GRABBING %d\n", loader_countdown_);
+ // Closing the grabber.
+ loader_up_ = false;
+ disc_clamped_ = true;
+ disc_ejected_ = false;
+ if (loader_countdown_ > 0) {
+ --loader_countdown_;
+ break;
+ } else {
+ loader_state_ = LoaderState::GRABBED;
+ }
+ case LoaderState::GRABBED:
+ printf("Loader GRABBED\n");
+ // Grabber closed.
+ loader_up_ = false;
+ disc_clamped_ = true;
+ disc_ejected_ = false;
+ if (loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
+ // TODO(aschuh): Only shoot if the shooter is up to speed.
+ // Seems like that would have us shooting a bit later than we could be,
+ // but it also probably spins back up real fast.
+ loader_state_ = LoaderState::LIFTING;
+ loader_countdown_ = kLiftingDelay;
+ printf("Told to SHOOT_AND_RESET, moving on\n");
+ } else if (loader_goal_ == LoaderGoal::READY) {
+ LOG(ERROR, "Can't go to ready when we have something grabbed.\n");
+ printf("Can't go to ready when we have something grabbed.\n");
+ break;
+ } else {
+ break;
+ }
+ case LoaderState::LIFTING:
+ printf("Loader LIFTING %d\n", loader_countdown_);
+ // Lifting the disc.
+ loader_up_ = true;
+ disc_clamped_ = true;
+ disc_ejected_ = false;
+ if (loader_countdown_ > 0) {
+ --loader_countdown_;
+ break;
+ } else {
+ loader_state_ = LoaderState::LIFTED;
+ }
+ case LoaderState::LIFTED:
+ printf("Loader LIFTED\n");
+ // Disc lifted. Time to eject it out.
+ loader_up_ = true;
+ disc_clamped_ = true;
+ disc_ejected_ = false;
+ loader_state_ = LoaderState::SHOOTING;
+ loader_countdown_ = kShootingDelay;
+ case LoaderState::SHOOTING:
+ printf("Loader SHOOTING %d\n", loader_countdown_);
+ // Ejecting the disc into the shooter.
+ loader_up_ = true;
+ disc_clamped_ = false;
+ disc_ejected_ = true;
+ if (loader_countdown_ > 0) {
+ --loader_countdown_;
+ break;
+ } else {
+ loader_state_ = LoaderState::SHOOT;
+ }
+ case LoaderState::SHOOT:
+ printf("Loader SHOOT\n");
+ // The disc has been shot.
+ loader_up_ = true;
+ disc_clamped_ = false;
+ disc_ejected_ = true;
+ loader_state_ = LoaderState::LOWERING;
+ loader_countdown_ = kLoweringDelay;
+ case LoaderState::LOWERING:
+ printf("Loader LOWERING %d\n", loader_countdown_);
+ // Lowering the loader back down.
+ loader_up_ = false;
+ disc_clamped_ = false;
+ disc_ejected_ = true;
+ if (loader_countdown_ > 0) {
+ --loader_countdown_;
+ break;
+ } else {
+ loader_state_ = LoaderState::LOWERED;
+ }
+ case LoaderState::LOWERED:
+ printf("Loader LOWERED\n");
+ // The indexer is lowered.
+ loader_up_ = false;
+ disc_clamped_ = false;
+ disc_ejected_ = false;
+ loader_state_ = LoaderState::READY;
+ // Once we have shot, we need to hang out in READY until otherwise
+ // notified.
+ loader_goal_ = LoaderGoal::READY;
break;
}
@@ -215,17 +470,20 @@
wrist_loop_->Update(position != NULL, output == NULL);
if (position) {
- LOG(DEBUG, "pos=%f currently %f\n",
- position->index_position, index_position);
+ LOG(DEBUG, "pos=%f\n", position->index_position);
last_bottom_disc_detect_ = position->bottom_disc_detect;
}
status->hopper_disc_count = hopper_disc_count_;
status->total_disc_count = total_disc_count_;
-
+ status->preloaded = (loader_state_ != LoaderState::READY);
if (output) {
+ output->transfer_voltage = transfer_voltage;
output->index_voltage = wrist_loop_->U(0, 0);
+ output->loader_up = loader_up_;
+ output->disc_clamped = disc_clamped_;
+ output->disc_ejected = disc_ejected_;
}
if (safe_to_change_state_) {
diff --git a/frc971/control_loops/index.h b/frc971/control_loops/index.h
index c62c639..9b64282 100644
--- a/frc971/control_loops/index.h
+++ b/frc971/control_loops/index.h
@@ -17,7 +17,42 @@
: public aos::control_loops::ControlLoop<control_loops::IndexLoop> {
public:
explicit IndexMotor(
- control_loops::IndexLoop *my_index = &control_loops::index);
+ control_loops::IndexLoop *my_index = &control_loops::index_loop);
+
+ static const double kTransferStartPosition;
+ static const double kIndexStartPosition;
+ // The distance from where the disc first grabs on the indexer to where it
+ // just bairly clears the loader.
+ static const double kIndexFreeLength;
+ // The distance to where the disc just starts to enter the loader.
+ static const double kLoaderFreeStopPosition;
+
+ // Distance that the grabber pulls the disc in by.
+ static const double kGrabberLength;
+ // Distance to where the grabber takes over.
+ static const double kGrabberStartPosition;
+
+ // The distance to where the disc hits the back of the loader and is ready to
+ // lift.
+ static const double kReadyToLiftPosition;
+
+ static const double kGrabberMovementVelocity;
+ // TODO(aschuh): This depends on the shooter angle...
+ // Distance to where the shooter is up and ready to shoot.
+ static const double kLifterStopPosition;
+ static const double kLifterMovementVelocity;
+
+ // Distance to where the disc has been launched.
+ // TODO(aschuh): This depends on the shooter angle...
+ static const double kEjectorStopPosition;
+ static const double kEjectorMovementVelocity;
+
+ // Start and stop position of the bottom disc detect sensor in meters.
+ static const double kBottomDiscDetectStart;
+ static const double kBottomDiscDetectStop;
+
+ static const double kTopDiscDetectStart;
+ static const double kTopDiscDetectStop;
// Converts the angle of the indexer to the angle of the disc.
static double ConvertIndexToDiscAngle(const double angle);
@@ -25,38 +60,73 @@
// disc has traveled.
static double ConvertIndexToDiscPosition(const double angle);
+ // Converts the angle of the transfer roller to the position that the center
+ // of the disc has traveled.
+ static double ConvertTransferToDiscPosition(const double angle);
+
+ // Converts the distance around the indexer to the position of
+ // the index roller.
+ static double ConvertDiscPositionToIndex(const double position);
// Converts the angle around the indexer to the position of the index roller.
static double ConvertDiscAngleToIndex(const double angle);
// Converts the angle around the indexer to the position of the disc in the
// indexer.
static double ConvertDiscAngleToDiscPosition(const double angle);
+ // Converts the distance around the indexer to the angle of the disc around
+ // the indexer.
+ static double ConvertDiscPositionToDiscAngle(const double position);
// Disc radius in meters.
- const static double kDiscRadius;
+ static const double kDiscRadius;
// Roller radius in meters.
- const static double kRollerRadius;
+ static const double kRollerRadius;
+ // Transfer roller radius in meters.
+ static const double kTransferRollerRadius;
+ // Time that it takes to grab the disc in cycles.
+ static const int kGrabbingDelay;
+ // Time that it takes to lift the loader in cycles.
+ static const int kLiftingDelay;
+ // Time that it takes to shoot the disc in cycles.
+ static const int kShootingDelay;
+ // Time that it takes to lower the loader in cycles.
+ static const int kLoweringDelay;
+
+ // Object representing a Frisbee tracked by the indexer.
class Frisbee {
public:
Frisbee()
: bottom_posedge_time_(0, 0),
- bottom_negedge_time_(0, 0),
- index_start_time_(0, 0) {
+ bottom_negedge_time_(0, 0) {
Reset();
}
+ // Resets a Frisbee so it can be reused.
void Reset() {
bottom_posedge_time_ = ::aos::time::Time(0, 0);
bottom_negedge_time_ = ::aos::time::Time(0, 0);
- index_start_time_ = ::aos::time::Time(0, 0);
has_been_indexed_ = false;
index_start_position_ = 0.0;
}
+ // Returns true if the position is valid.
+ bool has_position() const {
+ return has_been_indexed_;
+ }
+
+ // Returns the most up to date and accurate position that we have for the
+ // disc. This is the indexer position that the disc grabbed at.
+ double position() const {
+ return index_start_position_;
+ }
+
+ // Posedge and negedge disc times.
::aos::time::Time bottom_posedge_time_;
::aos::time::Time bottom_negedge_time_;
- ::aos::time::Time index_start_time_;
+
+ // True if the disc has a valid index position.
bool has_been_indexed_;
+ // Location of the index when the disc first contacted it.
double index_start_position_;
};
@@ -68,23 +138,20 @@
control_loops::IndexLoop::Status *status);
private:
- // Fetches and locally caches the latest set of constants.
- bool FetchConstants();
+ // Sets disc_position to the minimum or maximum disc position.
+ // Returns true if there were discs, and false if there weren't.
+ // On false, disc_position is left unmodified.
+ bool MinDiscPosition(double *disc_position);
+ bool MaxDiscPosition(double *disc_position);
// The state feedback control loop to talk to for the index.
::std::unique_ptr<StateFeedbackLoop<2, 1, 1>> wrist_loop_;
- // Local cache of the index geometry constants.
- double horizontal_lower_limit_;
- double horizontal_upper_limit_;
- double horizontal_hall_effect_start_angle_;
- double horizontal_zeroing_speed_;
-
// Count of the number of discs that we have collected.
uint32_t hopper_disc_count_;
uint32_t total_disc_count_;
- enum Goal {
+ enum class Goal {
// Hold position, in a low power state.
HOLD = 0,
// Get ready to load discs by shifting the discs down.
@@ -97,19 +164,59 @@
SHOOT = 4
};
+ // These two enums command and track the loader loading discs into the
+ // shooter.
+ enum class LoaderState {
+ // Open and down, ready to accept a disc.
+ READY,
+ // Closing the grabber.
+ GRABBING,
+ // Grabber closed.
+ GRABBED,
+ // Lifting the disc.
+ LIFTING,
+ // Disc lifted.
+ LIFTED,
+ // Ejecting the disc into the shooter.
+ SHOOTING,
+ // The disc has been shot.
+ SHOOT,
+ // Lowering the loader back down.
+ LOWERING,
+ // The indexer is lowered.
+ LOWERED
+ };
+
+ // TODO(aschuh): If we are grabbed and asked to be ready, now what?
+ // LOG ?
+ enum class LoaderGoal {
+ // Get the loader ready to accept another disc.
+ READY,
+ // Grab a disc now.
+ GRAB,
+ // Lift it up, shoot, and reset.
+ // Waits to shoot until the shooter is stable.
+ // Resets the goal to READY once one disc has been shot.
+ SHOOT_AND_RESET
+ };
+
// The current goal
Goal safe_goal_;
+ // Loader goal, state, and counter.
+ LoaderGoal loader_goal_;
+ LoaderState loader_state_;
+ int loader_countdown_;
+
// Current state of the pistons.
bool loader_up_;
bool disc_clamped_;
bool disc_ejected_;
- //::aos::time::Time disc_bottom_posedge_time_;
- //::aos::time::Time disc_bottom_negedge_time_;
// The frisbee that is flying through the transfer rollers.
Frisbee transfer_frisbee_;
+ // Bottom disc detect from the last valid packet for detecting edges.
bool last_bottom_disc_detect_;
// Frisbees are in order such that the newest frisbee is on the front.
diff --git a/frc971/control_loops/index_lib_test.cc b/frc971/control_loops/index_lib_test.cc
index 418a9b6..17b0e0f 100644
--- a/frc971/control_loops/index_lib_test.cc
+++ b/frc971/control_loops/index_lib_test.cc
@@ -18,120 +18,150 @@
namespace control_loops {
namespace testing {
-// TODO(aschuh): Figure out these constants.
-const double kTransferStartPosition = 0.0;
-const double kIndexStartPosition = 0.5;
-const double kIndexStopPosition = 2.5;
-const double kGrabberStopPosition = 2.625;
-const double kGrabberMovementVelocity = 0.4;
-
-// Start and stop position of the bottom disc detect sensor in meters.
-const double kBottomDiscDetectStart = -0.08;
-const double kBottomDiscDetectStop = 0.200025;
-
-const double kTopDiscDetectStart = 18.0;
-const double kTopDiscDetectStop = 19.0;
-
-// Disc radius in meters.
-const double kDiscRadius = 11.875 * 0.0254 / 2;
-// Roller radius in meters.
-const double kRollerRadius = 2.0 * 0.0254 / 2;
-
class Frisbee {
public:
// Creates a frisbee starting at the specified position in the frisbee path,
// and with the transfer and index rollers at the specified positions.
Frisbee(double transfer_roller_position,
double index_roller_position,
- double position = 0.0)
+ double position = IndexMotor::kBottomDiscDetectStart)
: transfer_roller_position_(transfer_roller_position),
index_roller_position_(index_roller_position),
- clamped_(false),
- position_(position) {
+ position_(position),
+ has_been_shot_(false) {
}
// Returns true if the frisbee is controlled by the transfer roller.
bool IsTouchingTransfer() const {
- return (position_ >= kTransferStartPosition &&
- position_ <= kIndexStartPosition);
+ return (position_ >= IndexMotor::kBottomDiscDetectStart &&
+ position_ <= IndexMotor::kIndexStartPosition);
+ }
+
+ // Returns true if the frisbee is in a place where it is unsafe to grab.
+ bool IsUnsafeToGrab() const {
+ return (position_ > (IndexMotor::kLoaderFreeStopPosition) &&
+ position_ < IndexMotor::kGrabberStartPosition);
}
// Returns true if the frisbee is controlled by the indexing roller.
bool IsTouchingIndex() const {
- return (position_ >= kIndexStartPosition &&
- position_ <= kIndexStopPosition);
+ return (position_ >= IndexMotor::kIndexStartPosition &&
+ position_ < IndexMotor::kGrabberStartPosition);
+ }
+
+ // Returns true if the frisbee is in a position such that the disc can be
+ // lifted.
+ bool IsUnsafeToLift() const {
+ return (position_ >= IndexMotor::kLoaderFreeStopPosition &&
+ position_ <= IndexMotor::kReadyToLiftPosition);
}
// Returns true if the frisbee is in a position such that the grabber will
// pull it into the loader.
bool IsTouchingGrabber() const {
- return (position_ >= kIndexStopPosition &&
- position_ <= kGrabberStopPosition);
+ return (position_ >= IndexMotor::kGrabberStartPosition &&
+ position_ < IndexMotor::kReadyToLiftPosition);
+ }
+
+ // Returns true if the frisbee is in a position such that the disc can be
+ // lifted.
+ bool IsTouchingLoader() const {
+ return (position_ >= IndexMotor::kReadyToLiftPosition &&
+ position_ < IndexMotor::kLifterStopPosition);
+ }
+
+ // Returns true if the frisbee is touching the ejector.
+ bool IsTouchingEjector() const {
+ return (position_ >= IndexMotor::kLifterStopPosition &&
+ position_ < IndexMotor::kEjectorStopPosition);
}
// Returns true if the disc is triggering the bottom disc detect sensor.
bool bottom_disc_detect() const {
- return (position_ >= kBottomDiscDetectStart &&
- position_ <= kBottomDiscDetectStop);
+ return (position_ >= IndexMotor::kBottomDiscDetectStart &&
+ position_ <= IndexMotor::kBottomDiscDetectStop);
}
// Returns true if the disc is triggering the top disc detect sensor.
bool top_disc_detect() const {
- return (position_ >= kTopDiscDetectStart &&
- position_ <= kTopDiscDetectStop);
- }
-
- // Converts the angle of the indexer to the distance traveled by the center of
- // the disc.
- double ConvertIndexToDiscPosition(const double angle) const {
- return (angle * (kDiscRadius + kRollerRadius) /
- (1 + (kDiscRadius * 2 + kRollerRadius) / kRollerRadius));
- }
-
- // Converts the angle of the transfer to the distance traveled by the center
- // of the disc.
- double ConvertTransferToDiscPosition(const double angle) const {
- return ConvertIndexToDiscPosition(angle);
+ return (position_ >= IndexMotor::kTopDiscDetectStart &&
+ position_ <= IndexMotor::kTopDiscDetectStop);
}
// Updates the position of the frisbee in the frisbee path.
void UpdatePosition(double transfer_roller_position,
double index_roller_position,
- bool clamped) {
- // TODO(aschuh): Assert that you can't slide the frisbee through the
- // clamp.
- if (IsTouchingTransfer()) {
- position_ += ConvertTransferToDiscPosition(transfer_roller_position -
- transfer_roller_position_);
+ bool clamped,
+ bool lifted,
+ bool ejected) {
+ if (IsTouchingTransfer() || position() < 0.0) {
+ position_ += IndexMotor::ConvertTransferToDiscPosition(
+ transfer_roller_position - transfer_roller_position_);
+ printf("Transfer Roller: ");
} else if (IsTouchingIndex()) {
- position_ += ConvertIndexToDiscPosition(index_roller_position -
- index_roller_position_);
+ position_ += ::std::min(
+ IndexMotor::ConvertIndexToDiscPosition(
+ index_roller_position - index_roller_position_),
+ IndexMotor::kGrabberStartPosition);
+ // Verify that we aren't trying to grab or lift when it isn't safe.
+ EXPECT_FALSE(clamped && IsUnsafeToGrab());
+ EXPECT_FALSE(lifted && IsUnsafeToLift());
+ printf("Index: ");
} else if (IsTouchingGrabber()) {
if (clamped) {
- position_ = ::std::min(position_ + kGrabberMovementVelocity / 100.0,
- kGrabberStopPosition);
+ const double grabber_dx = IndexMotor::kGrabberMovementVelocity / 100.0;
+ position_ = ::std::min(position_ + grabber_dx,
+ IndexMotor::kReadyToLiftPosition);
}
- } else {
- // TODO(aschuh): Deal with lifting.
- // TODO(aschuh): Deal with shooting.
- // We must wait long enough for the disc to leave the loader before
- // lowering.
+ EXPECT_FALSE(lifted);
+ EXPECT_FALSE(ejected);
+ printf("Grabber: ");
+ } else if (IsTouchingLoader()) {
+ if (lifted) {
+ const double lifter_dx = IndexMotor::kLifterMovementVelocity / 100.0;
+ position_ = ::std::min(position_ + lifter_dx,
+ IndexMotor::kLifterStopPosition);
+ }
+ EXPECT_TRUE(clamped);
+ EXPECT_FALSE(ejected);
+ printf("Loader: ");
+ } else if (IsTouchingEjector()) {
+ EXPECT_TRUE(lifted);
+ if (ejected) {
+ const double ejector_dx = IndexMotor::kEjectorMovementVelocity / 100.0;
+ position_ = ::std::min(position_ + ejector_dx,
+ IndexMotor::kEjectorStopPosition);
+ EXPECT_FALSE(clamped);
+ }
+ printf("Ejector: ");
+ } else if (position_ == IndexMotor::kEjectorStopPosition) {
+ printf("Shot: ");
+ has_been_shot_ = true;
}
transfer_roller_position_ = transfer_roller_position;
index_roller_position_ = index_roller_position;
- clamped_ = clamped;
printf("Disc is at %f\n", position_);
}
+ // Returns if the disc has been shot and can be removed from the robot.
+ bool has_been_shot() const {
+ return has_been_shot_;
+ }
+
+ // Returns the position of the disc in the system.
double position() const {
return position_;
}
private:
+ // Previous transfer roller position for computing deltas.
double transfer_roller_position_;
+ // Previous index roller position for computing deltas.
double index_roller_position_;
- bool clamped_;
+ // Position in the robot.
double position_;
+ // True if the disc has been shot.
+ bool has_been_shot_;
};
@@ -175,12 +205,42 @@
return top_disc_detect;
}
- void UpdateDiscs(bool clamped) {
+ // Updates all discs, and verifies that the state of the system is sane.
+ void UpdateDiscs(bool clamped, bool lifted, bool ejected) {
for (Frisbee &frisbee : frisbees) {
- // TODO(aschuh): Simulate clamping
frisbee.UpdatePosition(transfer_roller_position(),
index_roller_position(),
- clamped);
+ clamped,
+ lifted,
+ ejected);
+ }
+
+ // Make sure nobody is too close to anybody else.
+ Frisbee *last_frisbee = NULL;
+ for (Frisbee &frisbee : frisbees) {
+ if (last_frisbee) {
+ const double distance = frisbee.position() - last_frisbee->position();
+ double min_distance;
+ if (frisbee.IsTouchingTransfer() ||
+ last_frisbee->IsTouchingTransfer()) {
+ min_distance = 0.3;
+ } else {
+ min_distance =
+ IndexMotor::ConvertDiscAngleToDiscPosition(M_PI * 2.0 / 3.0);
+ }
+
+ EXPECT_LT(min_distance, ::std::abs(distance)) << "Discs too close";
+ }
+ last_frisbee = &frisbee;
+ }
+
+ // Remove any shot frisbees.
+ for (int i = 0; i < static_cast<int>(frisbees.size()); ++i) {
+ if (frisbees[i].has_been_shot()) {
+ shot_frisbees.push_back(frisbees[i]);
+ frisbees.erase(frisbees.begin() + i);
+ --i;
+ }
}
}
@@ -210,9 +270,12 @@
my_index_loop_.output->index_voltage,
transfer_roller_position(), index_roller_position());
- UpdateDiscs(my_index_loop_.output->disc_clamped);
+ UpdateDiscs(my_index_loop_.output->disc_clamped,
+ my_index_loop_.output->loader_up,
+ my_index_loop_.output->disc_ejected);
}
+ // Plants for the index and transfer rollers.
::std::unique_ptr<StateFeedbackPlant<2, 1, 1>> index_plant_;
::std::unique_ptr<StateFeedbackPlant<2, 1, 1>> transfer_plant_;
@@ -226,9 +289,13 @@
return transfer_plant_->Y(0, 0);
}
+ // Frisbees being tracked in the robot.
::std::vector<Frisbee> frisbees;
+ // Frisbees that have been shot.
+ ::std::vector<Frisbee> shot_frisbees;
private:
+ // Control loop for the indexer.
IndexLoop my_index_loop_;
};
@@ -269,6 +336,54 @@
Time::SetMockTime(Time::InMS(10 * loop_count_));
}
+ // Loads n discs into the indexer at the bottom.
+ void LoadNDiscs(int n) {
+ my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
+ // Spin it up.
+ for (int i = 0; i < 100; ++i) {
+ index_motor_plant_.SendPositionMessage();
+ index_motor_.Iterate();
+ index_motor_plant_.Simulate();
+ SendDSPacket(true);
+ UpdateTime();
+ }
+
+ EXPECT_EQ(0, index_motor_plant_.index_roller_position());
+ my_index_loop_.status.FetchLatest();
+ EXPECT_TRUE(my_index_loop_.status->ready_to_intake);
+
+ // Stuff N discs in, waiting between each one for a tiny bit of time so they
+ // don't get too close.
+ int num_grabbed = 0;
+ int wait_counter = 0;
+ while (num_grabbed < n) {
+ index_motor_plant_.SendPositionMessage();
+ index_motor_.Iterate();
+ if (!index_motor_plant_.BottomDiscDetect()) {
+ if (wait_counter > 0) {
+ --wait_counter;
+ } else {
+ index_motor_plant_.InsertDisc();
+ ++num_grabbed;
+ wait_counter = 3;
+ }
+ }
+ index_motor_plant_.Simulate();
+ SendDSPacket(true);
+ UpdateTime();
+ }
+
+ // Settle.
+ for (int i = 0; i < 100; ++i) {
+ index_motor_plant_.SendPositionMessage();
+ index_motor_.Iterate();
+ index_motor_plant_.Simulate();
+ SendDSPacket(true);
+ UpdateTime();
+ }
+ }
+
+ // Copy of core that works in this process only.
::aos::common::testing::GlobalCoreInstance my_core;
// Create a new instance of the test queue so that it invalidates the queue
@@ -280,6 +395,8 @@
IndexMotor index_motor_;
IndexMotorSimulation index_motor_plant_;
+ // Number of loop cycles that have been executed for tracking the current
+ // time.
int loop_count_;
};
@@ -293,22 +410,222 @@
EXPECT_EQ(0, index_motor_plant_.index_roller_position());
index_motor_plant_.InsertDisc();
}
+ if (i > 0) {
+ EXPECT_TRUE(my_index_loop_.status.FetchLatest());
+ EXPECT_TRUE(my_index_loop_.status->ready_to_intake);
+ }
index_motor_plant_.Simulate();
SendDSPacket(true);
UpdateTime();
}
- EXPECT_TRUE(my_index_loop_.status.FetchLatest());
+ my_index_loop_.status.FetchLatest();
EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
EXPECT_EQ(static_cast<size_t>(1), index_motor_plant_.frisbees.size());
EXPECT_NEAR(
- kIndexStartPosition + IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
- index_motor_plant_.frisbees[0].position(), 0.01);
+ IndexMotor::kIndexStartPosition + IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
+ index_motor_plant_.frisbees[0].position(), 0.05);
}
-// Test that pulling in a second disc works correctly.
-// Test that HOLD still finishes the disc correctly.
-// Test that pulling a disc down works correctly and ready_to_intake waits.
+// Tests that the index grabs 1 disc and places it at the correct position when
+// told to hold immediately after the disc starts into the bot.
+TEST_F(IndexTest, GrabAndHold) {
+ my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
+ for (int i = 0; i < 200; ++i) {
+ index_motor_plant_.SendPositionMessage();
+ index_motor_.Iterate();
+ if (i == 100) {
+ EXPECT_EQ(0, index_motor_plant_.index_roller_position());
+ index_motor_plant_.InsertDisc();
+ } else if (i == 102) {
+ // The disc has been seen. Tell the indexer to now hold.
+ my_index_loop_.goal.MakeWithBuilder().goal_state(0).Send();
+ } else if (i > 102) {
+ my_index_loop_.status.FetchLatest();
+ EXPECT_FALSE(my_index_loop_.status->ready_to_intake);
+ }
+ index_motor_plant_.Simulate();
+ SendDSPacket(true);
+ UpdateTime();
+ }
+
+ my_index_loop_.status.FetchLatest();
+ EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
+ EXPECT_EQ(static_cast<size_t>(1), index_motor_plant_.frisbees.size());
+ EXPECT_NEAR(
+ (IndexMotor::kIndexStartPosition +
+ IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
+ index_motor_plant_.frisbees[0].position(), 0.04);
+}
+
+// Tests that the index grabs two discs and places them at the correct
+// positions.
+TEST_F(IndexTest, GrabTwoDiscs) {
+ LoadNDiscs(2);
+
+ EXPECT_TRUE(my_index_loop_.status.FetchLatest());
+ EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 2);
+ EXPECT_EQ(static_cast<size_t>(2), index_motor_plant_.frisbees.size());
+ EXPECT_NEAR(
+ (IndexMotor::kIndexStartPosition +
+ IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
+ index_motor_plant_.frisbees[1].position(), 0.10);
+ EXPECT_NEAR(
+ IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
+ (index_motor_plant_.frisbees[0].position() -
+ index_motor_plant_.frisbees[1].position()), 0.10);
+}
+
+// Tests that the index grabs 2 discs, and loads one up into the loader to get
+// ready to shoot. It then pulls the second disc back down to be ready to
+// intake more.
+TEST_F(IndexTest, ReadyGrabsOneDisc) {
+ LoadNDiscs(2);
+
+ // Lift the discs up to the top. Wait a while to let the system settle and
+ // verify that they don't collide.
+ my_index_loop_.goal.MakeWithBuilder().goal_state(3).Send();
+ for (int i = 0; i < 300; ++i) {
+ index_motor_plant_.SendPositionMessage();
+ index_motor_.Iterate();
+ index_motor_plant_.Simulate();
+ SendDSPacket(true);
+ UpdateTime();
+ }
+
+ // Verify that the disc has been grabbed.
+ my_index_loop_.output.FetchLatest();
+ EXPECT_TRUE(my_index_loop_.output->disc_clamped);
+ // And that we are preloaded.
+ my_index_loop_.status.FetchLatest();
+ EXPECT_TRUE(my_index_loop_.status->preloaded);
+
+ // Pull the disc back down and verify that the transfer roller doesn't turn on
+ // until we are ready.
+ my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
+ for (int i = 0; i < 100; ++i) {
+ index_motor_plant_.SendPositionMessage();
+ index_motor_.Iterate();
+
+ EXPECT_TRUE(my_index_loop_.status.FetchLatest());
+ EXPECT_TRUE(my_index_loop_.output.FetchLatest());
+ if (!my_index_loop_.status->ready_to_intake) {
+ EXPECT_EQ(my_index_loop_.output->transfer_voltage, 0)
+ << "Transfer should be off until indexer is ready";
+ }
+
+ index_motor_plant_.Simulate();
+ SendDSPacket(true);
+ UpdateTime();
+ }
+
+ my_index_loop_.status.FetchLatest();
+ EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
+ EXPECT_EQ(my_index_loop_.status->total_disc_count, 2);
+ my_index_loop_.output.FetchLatest();
+ EXPECT_TRUE(my_index_loop_.output->disc_clamped);
+
+ EXPECT_EQ(static_cast<size_t>(2), index_motor_plant_.frisbees.size());
+ EXPECT_NEAR(IndexMotor::kReadyToLiftPosition,
+ index_motor_plant_.frisbees[0].position(), 0.01);
+ EXPECT_NEAR(
+ (IndexMotor::kIndexStartPosition +
+ IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
+ index_motor_plant_.frisbees[1].position(), 0.10);
+}
+
+// Tests that the index grabs 1 disc and continues to pull it in correctly when
+// in the READY_LOWER state. The transfer roller should be disabled then.
+TEST_F(IndexTest, GrabAndReady) {
+ my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
+ for (int i = 0; i < 200; ++i) {
+ index_motor_plant_.SendPositionMessage();
+ index_motor_.Iterate();
+ if (i == 100) {
+ EXPECT_EQ(0, index_motor_plant_.index_roller_position());
+ index_motor_plant_.InsertDisc();
+ } else if (i == 102) {
+ my_index_loop_.goal.MakeWithBuilder().goal_state(1).Send();
+ } else if (i > 150) {
+ my_index_loop_.status.FetchLatest();
+ EXPECT_TRUE(my_index_loop_.status->ready_to_intake);
+ my_index_loop_.output.FetchLatest();
+ EXPECT_EQ(my_index_loop_.output->transfer_voltage, 0.0);
+ }
+ index_motor_plant_.Simulate();
+ SendDSPacket(true);
+ UpdateTime();
+ }
+
+ my_index_loop_.status.FetchLatest();
+ EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
+ EXPECT_EQ(static_cast<size_t>(1), index_motor_plant_.frisbees.size());
+ EXPECT_NEAR(
+ (IndexMotor::kIndexStartPosition +
+ IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
+ index_motor_plant_.frisbees[0].position(), 0.04);
+}
+
+// Tests that grabbing 4 discs ends up with 4 discs in the bot and us no longer
+// ready.
+TEST_F(IndexTest, GrabFourDiscs) {
+ LoadNDiscs(4);
+
+ EXPECT_TRUE(my_index_loop_.output.FetchLatest());
+ EXPECT_EQ(my_index_loop_.output->transfer_voltage, 0.0);
+ EXPECT_TRUE(my_index_loop_.status.FetchLatest());
+ EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 4);
+ EXPECT_FALSE(my_index_loop_.status->ready_to_intake);
+ EXPECT_EQ(static_cast<size_t>(4), index_motor_plant_.frisbees.size());
+ EXPECT_NEAR(
+ IndexMotor::kIndexStartPosition + IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
+ index_motor_plant_.frisbees[3].position(), 0.10);
+ EXPECT_NEAR(
+ IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
+ (index_motor_plant_.frisbees[0].position() -
+ index_motor_plant_.frisbees[1].position()), 0.10);
+ EXPECT_NEAR(
+ IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
+ (index_motor_plant_.frisbees[1].position() -
+ index_motor_plant_.frisbees[2].position()), 0.10);
+ EXPECT_NEAR(
+ IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
+ (index_motor_plant_.frisbees[2].position() -
+ index_motor_plant_.frisbees[3].position()), 0.10);
+}
+
+// Tests that shooting 4 discs works.
+TEST_F(IndexTest, ShootFourDiscs) {
+ LoadNDiscs(4);
+
+ EXPECT_EQ(static_cast<size_t>(4), index_motor_plant_.frisbees.size());
+
+ my_index_loop_.goal.MakeWithBuilder().goal_state(4).Send();
+
+ // Lifting and shooting takes a while...
+ for (int i = 0; i < 300; ++i) {
+ index_motor_plant_.SendPositionMessage();
+ index_motor_.Iterate();
+ index_motor_plant_.Simulate();
+ SendDSPacket(true);
+ UpdateTime();
+ }
+
+ my_index_loop_.status.FetchLatest();
+ EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 0);
+ EXPECT_EQ(my_index_loop_.status->total_disc_count, 4);
+ my_index_loop_.output.FetchLatest();
+ EXPECT_FALSE(my_index_loop_.output->disc_clamped);
+ EXPECT_FALSE(my_index_loop_.output->loader_up);
+ EXPECT_FALSE(my_index_loop_.output->disc_ejected);
+
+ EXPECT_EQ(static_cast<size_t>(4), index_motor_plant_.shot_frisbees.size());
+}
+
+// Test that it doesn't abort preloading at the wrong time.
+
+// Test that we can loose values and handle disabling without dying.
+// Not necesarily gracefully or anything.
} // namespace testing
} // namespace control_loops
diff --git a/frc971/control_loops/index_main.cc b/frc971/control_loops/index_main.cc
index cf18412..6d03678 100644
--- a/frc971/control_loops/index_main.cc
+++ b/frc971/control_loops/index_main.cc
@@ -4,8 +4,8 @@
int main() {
::aos::Init();
- frc971::control_loops::WristMotor wrist;
- looper.Run();
+ frc971::control_loops::IndexMotor indexer;
+ indexer.Run();
::aos::Cleanup();
return 0;
}
diff --git a/frc971/control_loops/index_motor.q b/frc971/control_loops/index_motor.q
index bd1564b..35a2e2a 100644
--- a/frc971/control_loops/index_motor.q
+++ b/frc971/control_loops/index_motor.q
@@ -29,8 +29,8 @@
// Index roller. Positive means up towards the shooter.
double index_voltage;
// Loader pistons.
- bool loader_up;
bool disc_clamped;
+ bool loader_up;
bool disc_ejected;
};
@@ -39,8 +39,9 @@
int32_t hopper_disc_count;
// Number of shot since reboot.
int32_t total_disc_count;
- // Ready to load.
+ // Disc ready in the loader.
bool preloaded;
+ // Indexer ready to accept more discs.
bool ready_to_intake;
};
@@ -50,4 +51,4 @@
queue Status status;
};
-queue_group IndexLoop index;
+queue_group IndexLoop index_loop;
diff --git a/frc971/control_loops/index_motor_plant.cc b/frc971/control_loops/index_motor_plant.cc
index 5b77deb..0d257f4 100644
--- a/frc971/control_loops/index_motor_plant.cc
+++ b/frc971/control_loops/index_motor_plant.cc
@@ -26,7 +26,7 @@
Eigen::Matrix<double, 2, 1> L;
L << 1.64731520998, 56.0569452572;
Eigen::Matrix<double, 1, 2> K;
- K << 1.06905877421, 0.0368709177253;
+ K << 2.40538224198, 0.0619371641882;
return StateFeedbackLoop<2, 1, 1>(L, K, MakeIndexPlant());
}
diff --git a/frc971/control_loops/python/index.py b/frc971/control_loops/python/index.py
index 5ce7613..c65397b 100755
--- a/frc971/control_loops/python/index.py
+++ b/frc971/control_loops/python/index.py
@@ -43,7 +43,7 @@
self.ContinuousToDiscrete(self.A_continuous, self.B_continuous,
self.dt, self.C)
- self.PlaceControllerPoles([.75, .6])
+ self.PlaceControllerPoles([.5, .55])
self.rpl = .05
self.ipl = 0.008
@@ -82,10 +82,13 @@
# Write the generated constants out to a file.
if len(argv) != 3:
- print "Expected .cc file name and .h file name"
+ print "Expected .h file name and .c file name"
else:
- index.DumpHeaderFile(argv[1])
- index.DumpCppFile(argv[2], argv[1])
+ if argv[1][-3:] == '.cc':
+ print '.cc file is second'
+ else:
+ index.DumpHeaderFile(argv[1])
+ index.DumpCppFile(argv[2], argv[1])
if __name__ == '__main__':
sys.exit(main(sys.argv))