blob: 95e69d726d1d0abb174ee745d37fa3a8491876c6 [file] [log] [blame]
Austin Schuh00558222013-03-03 14:16:16 -08001#include "frc971/control_loops/index/index.h"
Austin Schuhd78ab542013-03-01 22:22:19 -08002
3#include <stdio.h>
4
5#include <algorithm>
6
7#include "aos/aos_core.h"
8
9#include "aos/common/messages/RobotState.q.h"
10#include "aos/common/control_loop/control_loops.q.h"
11#include "aos/common/logging/logging.h"
12
13#include "frc971/constants.h"
Austin Schuh00558222013-03-03 14:16:16 -080014#include "frc971/control_loops/index/index_motor_plant.h"
Austin Schuhd78ab542013-03-01 22:22:19 -080015
16using ::aos::time::Time;
17
18namespace frc971 {
19namespace control_loops {
20
Austin Schuhdff24e22013-03-06 00:41:21 -080021double IndexMotor::Frisbee::ObserveNoTopDiscSensor(
Austin Schuh825bde92013-03-06 00:16:46 -080022 double index_position, double index_velocity) {
Austin Schuhdff24e22013-03-06 00:41:21 -080023 // The absolute disc position in meters.
Austin Schuh1b864a12013-03-07 00:46:50 -080024 double disc_position = absolute_position(index_position);
Austin Schuh825bde92013-03-06 00:16:46 -080025 if (IndexMotor::kTopDiscDetectStart <= disc_position &&
26 disc_position <= IndexMotor::kTopDiscDetectStop) {
27 // Whoops, this shouldn't be happening.
28 // Move the disc off the way that makes most sense.
Austin Schuhdff24e22013-03-06 00:41:21 -080029 double distance_to_above = IndexMotor::ConvertDiscPositionToIndex(
30 ::std::abs(disc_position - IndexMotor::kTopDiscDetectStop));
31 double distance_to_below = IndexMotor::ConvertDiscPositionToIndex(
32 ::std::abs(disc_position - IndexMotor::kTopDiscDetectStart));
Austin Schuh825bde92013-03-06 00:16:46 -080033 if (::std::abs(index_velocity) < 100) {
34 if (distance_to_above < distance_to_below) {
Austin Schuhdff24e22013-03-06 00:41:21 -080035 printf("Moving disc to top slow.\n");
Austin Schuh825bde92013-03-06 00:16:46 -080036 // Move it up.
Austin Schuhdff24e22013-03-06 00:41:21 -080037 index_start_position_ -= distance_to_above;
38 return -distance_to_above;
Austin Schuh825bde92013-03-06 00:16:46 -080039 } else {
Austin Schuhdff24e22013-03-06 00:41:21 -080040 printf("Moving disc to bottom slow.\n");
41 index_start_position_ += distance_to_below;
42 return distance_to_below;
Austin Schuh825bde92013-03-06 00:16:46 -080043 }
44 } else {
45 if (index_velocity > 0) {
46 // Now going up. If we didn't see it before, and we don't see it
47 // now but it should be in view, it must still be below. If it were
48 // above, it would be going further away from us.
Austin Schuh1b864a12013-03-07 00:46:50 -080049 printf("Moving fast up, shifting disc down. Disc was at %f\n",
50 absolute_position(index_position));
Austin Schuhdff24e22013-03-06 00:41:21 -080051 index_start_position_ += distance_to_below;
Austin Schuh1b864a12013-03-07 00:46:50 -080052 printf("Moving fast up, shifting disc down. Disc now at %f\n",
53 absolute_position(index_position));
Austin Schuhdff24e22013-03-06 00:41:21 -080054 return distance_to_below;
Austin Schuh825bde92013-03-06 00:16:46 -080055 } else {
Austin Schuh1b864a12013-03-07 00:46:50 -080056 printf("Moving fast down, shifting disc up. Disc was at %f\n",
57 absolute_position(index_position));
Austin Schuhdff24e22013-03-06 00:41:21 -080058 index_start_position_ -= distance_to_above;
Austin Schuh1b864a12013-03-07 00:46:50 -080059 printf("Moving fast down, shifting disc up. Disc now at %f\n",
60 absolute_position(index_position));
Austin Schuhdff24e22013-03-06 00:41:21 -080061 return -distance_to_above;
Austin Schuh825bde92013-03-06 00:16:46 -080062 }
63 }
64 }
Austin Schuhdff24e22013-03-06 00:41:21 -080065 return 0.0;
Austin Schuh825bde92013-03-06 00:16:46 -080066}
67
Austin Schuhd78ab542013-03-01 22:22:19 -080068IndexMotor::IndexMotor(control_loops::IndexLoop *my_index)
69 : aos::control_loops::ControlLoop<control_loops::IndexLoop>(my_index),
Austin Schuh93485832013-03-04 00:01:34 -080070 wrist_loop_(new IndexStateFeedbackLoop(MakeIndexLoop())),
Austin Schuhd78ab542013-03-01 22:22:19 -080071 hopper_disc_count_(0),
72 total_disc_count_(0),
Austin Schuhf8c52252013-03-03 02:25:49 -080073 safe_goal_(Goal::HOLD),
74 loader_goal_(LoaderGoal::READY),
75 loader_state_(LoaderState::READY),
Austin Schuhd78ab542013-03-01 22:22:19 -080076 loader_up_(false),
77 disc_clamped_(false),
78 disc_ejected_(false),
Austin Schuhbcdb90c2013-03-03 23:24:58 -080079 last_bottom_disc_detect_(false),
Austin Schuh825bde92013-03-06 00:16:46 -080080 last_top_disc_detect_(false),
Austin Schuhbcdb90c2013-03-03 23:24:58 -080081 no_prior_position_(true),
82 missing_position_count_(0) {
Austin Schuhd78ab542013-03-01 22:22:19 -080083}
84
Austin Schuhf8c52252013-03-03 02:25:49 -080085/*static*/ const double IndexMotor::kTransferStartPosition = 0.0;
86/*static*/ const double IndexMotor::kIndexStartPosition = 0.2159;
87/*static*/ const double IndexMotor::kIndexFreeLength =
88 IndexMotor::ConvertDiscAngleToDiscPosition((360 * 2 + 14) * M_PI / 180);
89/*static*/ const double IndexMotor::kLoaderFreeStopPosition =
90 kIndexStartPosition + kIndexFreeLength;
Austin Schuh1b864a12013-03-07 00:46:50 -080091/*static*/ const double IndexMotor::kReadyToPreload =
92 kLoaderFreeStopPosition - ConvertDiscAngleToDiscPosition(M_PI / 6.0);
Austin Schuhf8c52252013-03-03 02:25:49 -080093/*static*/ const double IndexMotor::kReadyToLiftPosition =
94 kLoaderFreeStopPosition + 0.2921;
95/*static*/ const double IndexMotor::kGrabberLength = 0.03175;
96/*static*/ const double IndexMotor::kGrabberStartPosition =
97 kReadyToLiftPosition - kGrabberLength;
Austin Schuh6328daf2013-03-05 00:53:15 -080098/*static*/ const double IndexMotor::kGrabberMovementVelocity = 0.7;
Austin Schuhf8c52252013-03-03 02:25:49 -080099/*static*/ const double IndexMotor::kLifterStopPosition =
100 kReadyToLiftPosition + 0.161925;
101/*static*/ const double IndexMotor::kLifterMovementVelocity = 1.0;
102/*static*/ const double IndexMotor::kEjectorStopPosition =
103 kLifterStopPosition + 0.01;
104/*static*/ const double IndexMotor::kEjectorMovementVelocity = 1.0;
105/*static*/ const double IndexMotor::kBottomDiscDetectStart = -0.08;
106/*static*/ const double IndexMotor::kBottomDiscDetectStop = 0.200025;
Austin Schuh6328daf2013-03-05 00:53:15 -0800107/*static*/ const double IndexMotor::kBottomDiscIndexDelay = 0.01;
Austin Schuhf8c52252013-03-03 02:25:49 -0800108
109// TODO(aschuh): Figure these out.
Austin Schuh825bde92013-03-06 00:16:46 -0800110/*static*/ const double IndexMotor::kTopDiscDetectStart =
111 (IndexMotor::kLoaderFreeStopPosition -
112 IndexMotor::ConvertDiscAngleToDiscPosition(60 * M_PI / 180));
113// This is a guess for the width of the disc radially. It should be close to 11
114// inches but a bit below.
115/*static*/ const double IndexMotor::kTopDiscDetectStop =
116 IndexMotor::kTopDiscDetectStart + 10 * 0.0254;
Austin Schuhf8c52252013-03-03 02:25:49 -0800117
Austin Schuhd78ab542013-03-01 22:22:19 -0800118const /*static*/ double IndexMotor::kDiscRadius = 10.875 * 0.0254 / 2;
119const /*static*/ double IndexMotor::kRollerRadius = 2.0 * 0.0254 / 2;
Austin Schuhf8c52252013-03-03 02:25:49 -0800120const /*static*/ double IndexMotor::kTransferRollerRadius = 1.25 * 0.0254 / 2;
Austin Schuhd78ab542013-03-01 22:22:19 -0800121
Austin Schuhf8c52252013-03-03 02:25:49 -0800122/*static*/ const int IndexMotor::kGrabbingDelay = 5;
123/*static*/ const int IndexMotor::kLiftingDelay = 20;
124/*static*/ const int IndexMotor::kShootingDelay = 5;
125/*static*/ const int IndexMotor::kLoweringDelay = 20;
Austin Schuhd78ab542013-03-01 22:22:19 -0800126
Austin Schuh93485832013-03-04 00:01:34 -0800127// TODO(aschuh): Tune these.
128/*static*/ const double
129 IndexMotor::IndexStateFeedbackLoop::kMinMotionVoltage = 5.0;
130/*static*/ const double
131 IndexMotor::IndexStateFeedbackLoop::kNoMotionCuttoffCount = 30;
132
Austin Schuhd78ab542013-03-01 22:22:19 -0800133// Distance to move the indexer when grabbing a disc.
134const double kNextPosition = 10.0;
135
136/*static*/ double IndexMotor::ConvertDiscAngleToIndex(const double angle) {
137 return (angle * (1 + (kDiscRadius * 2 + kRollerRadius) / kRollerRadius));
138}
139
Austin Schuhf8c52252013-03-03 02:25:49 -0800140/*static*/ double IndexMotor::ConvertDiscAngleToDiscPosition(
141 const double angle) {
Austin Schuhd78ab542013-03-01 22:22:19 -0800142 return angle * (kDiscRadius + kRollerRadius);
143}
144
Austin Schuhf8c52252013-03-03 02:25:49 -0800145/*static*/ double IndexMotor::ConvertDiscPositionToDiscAngle(
146 const double position) {
147 return position / (kDiscRadius + kRollerRadius);
148}
149
Austin Schuhd78ab542013-03-01 22:22:19 -0800150/*static*/ double IndexMotor::ConvertIndexToDiscAngle(const double angle) {
151 return (angle / (1 + (kDiscRadius * 2 + kRollerRadius) / kRollerRadius));
152}
153
154/*static*/ double IndexMotor::ConvertIndexToDiscPosition(const double angle) {
155 return IndexMotor::ConvertDiscAngleToDiscPosition(
156 ConvertIndexToDiscAngle(angle));
157}
158
Austin Schuhf8c52252013-03-03 02:25:49 -0800159/*static*/ double IndexMotor::ConvertTransferToDiscPosition(
160 const double angle) {
161 const double gear_ratio = (1 + (kDiscRadius * 2 + kTransferRollerRadius) /
162 kTransferRollerRadius);
163 return angle / gear_ratio * (kDiscRadius + kTransferRollerRadius);
164}
165
166/*static*/ double IndexMotor::ConvertDiscPositionToIndex(
167 const double position) {
168 return IndexMotor::ConvertDiscAngleToIndex(
169 ConvertDiscPositionToDiscAngle(position));
170}
171
Austin Schuh1b864a12013-03-07 00:46:50 -0800172bool IndexMotor::MinDiscPosition(double *disc_position, Frisbee **found_disc) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800173 bool found_start = false;
174 for (unsigned int i = 0; i < frisbees_.size(); ++i) {
Austin Schuh1b864a12013-03-07 00:46:50 -0800175 Frisbee &frisbee = frisbees_[i];
Austin Schuhf8c52252013-03-03 02:25:49 -0800176 if (!found_start) {
177 if (frisbee.has_position()) {
178 *disc_position = frisbee.position();
Austin Schuh1b864a12013-03-07 00:46:50 -0800179 if (found_disc) {
180 *found_disc = &frisbee;
181 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800182 found_start = true;
183 }
184 } else {
Austin Schuh1b864a12013-03-07 00:46:50 -0800185 if (frisbee.position() <= *disc_position) {
186 *disc_position = frisbee.position();
187 if (found_disc) {
188 *found_disc = &frisbee;
189 }
190 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800191 }
192 }
193 return found_start;
194}
195
Austin Schuh1b864a12013-03-07 00:46:50 -0800196bool IndexMotor::MaxDiscPosition(double *disc_position, Frisbee **found_disc) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800197 bool found_start = false;
198 for (unsigned int i = 0; i < frisbees_.size(); ++i) {
Austin Schuh1b864a12013-03-07 00:46:50 -0800199 Frisbee &frisbee = frisbees_[i];
Austin Schuhf8c52252013-03-03 02:25:49 -0800200 if (!found_start) {
201 if (frisbee.has_position()) {
202 *disc_position = frisbee.position();
Austin Schuh1b864a12013-03-07 00:46:50 -0800203 if (found_disc) {
204 *found_disc = &frisbee;
205 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800206 found_start = true;
207 }
208 } else {
Austin Schuh1b864a12013-03-07 00:46:50 -0800209 if (frisbee.position() > *disc_position) {
210 *disc_position = frisbee.position();
211 if (found_disc) {
212 *found_disc = &frisbee;
213 }
214 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800215 }
216 }
217 return found_start;
218}
219
Austin Schuh93485832013-03-04 00:01:34 -0800220void IndexMotor::IndexStateFeedbackLoop::CapU() {
221 // If the voltage has been low for a large number of cycles, cut the motor
222 // power. This is generally very bad controls practice since this isn't LTI,
223 // but we don't really care about tracking anything other than large step
224 // inputs, and the loader doesn't need to be that accurate.
225 if (::std::abs(U(0, 0)) < kMinMotionVoltage) {
226 ++low_voltage_count_;
227 if (low_voltage_count_ > kNoMotionCuttoffCount) {
228 printf("Limiting power from %f to 0\n", U(0, 0));
229 U(0, 0) = 0.0;
230 }
231 } else {
232 low_voltage_count_ = 0;
233 }
234
235 for (int i = 0; i < kNumOutputs; ++i) {
236 if (U[i] > plant.U_max[i]) {
237 U[i] = plant.U_max[i];
238 } else if (U[i] < plant.U_min[i]) {
239 U[i] = plant.U_min[i];
240 }
241 }
242}
243
244
Austin Schuhd78ab542013-03-01 22:22:19 -0800245// Positive angle is towards the shooter, and positive power is towards the
246// shooter.
247void IndexMotor::RunIteration(
248 const control_loops::IndexLoop::Goal *goal,
249 const control_loops::IndexLoop::Position *position,
250 control_loops::IndexLoop::Output *output,
251 control_loops::IndexLoop::Status *status) {
Austin Schuhbcdb90c2013-03-03 23:24:58 -0800252 // Make goal easy to work with and sanity check it.
Austin Schuhd78ab542013-03-01 22:22:19 -0800253 Goal goal_enum = static_cast<Goal>(goal->goal_state);
Austin Schuhbcdb90c2013-03-03 23:24:58 -0800254 if (goal->goal_state < 0 || goal->goal_state > 4) {
255 LOG(ERROR, "Goal state is %d which is out of range. Going to HOLD.\n",
256 goal->goal_state);
257 goal_enum = Goal::HOLD;
258 }
Austin Schuhd78ab542013-03-01 22:22:19 -0800259
260 // Disable the motors now so that all early returns will return with the
261 // motors disabled.
Austin Schuhb6d898b2013-03-03 15:34:35 -0800262 double intake_voltage = 0.0;
Austin Schuhf8c52252013-03-03 02:25:49 -0800263 double transfer_voltage = 0.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800264 if (output) {
Austin Schuhb6d898b2013-03-03 15:34:35 -0800265 output->intake_voltage = 0.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800266 output->transfer_voltage = 0.0;
267 output->index_voltage = 0.0;
268 }
269
270 status->ready_to_intake = false;
271
Austin Schuhf8c52252013-03-03 02:25:49 -0800272 // Compute a safe index position that we can use.
Austin Schuhd78ab542013-03-01 22:22:19 -0800273 if (position) {
274 wrist_loop_->Y << position->index_position;
Austin Schuhbcdb90c2013-03-03 23:24:58 -0800275 // Set the goal to be the current position if this is the first time through
276 // so we don't always spin the indexer to the 0 position before starting.
277 if (no_prior_position_) {
278 wrist_loop_->R << wrist_loop_->Y(0, 0), 0.0;
279 no_prior_position_ = false;
Austin Schuh6328daf2013-03-05 00:53:15 -0800280 last_bottom_disc_posedge_count_ = position->bottom_disc_posedge_count;
281 last_bottom_disc_negedge_count_ = position->bottom_disc_negedge_count;
282 last_bottom_disc_negedge_wait_count_ =
283 position->bottom_disc_negedge_wait_count;
Austin Schuh825bde92013-03-06 00:16:46 -0800284 last_top_disc_posedge_count_ = position->top_disc_posedge_count;
Austin Schuhbcdb90c2013-03-03 23:24:58 -0800285 }
286
Austin Schuh1b864a12013-03-07 00:46:50 -0800287 // If the cRIO is gone for over 1/2 of a second, assume that it rebooted.
Austin Schuhbcdb90c2013-03-03 23:24:58 -0800288 if (missing_position_count_ > 50) {
Austin Schuh6328daf2013-03-05 00:53:15 -0800289 last_bottom_disc_posedge_count_ = position->bottom_disc_posedge_count;
290 last_bottom_disc_negedge_count_ = position->bottom_disc_negedge_count;
291 last_bottom_disc_negedge_wait_count_ =
292 position->bottom_disc_negedge_wait_count;
Austin Schuh825bde92013-03-06 00:16:46 -0800293 last_top_disc_posedge_count_ = position->top_disc_posedge_count;
Austin Schuhbcdb90c2013-03-03 23:24:58 -0800294 // Adjust the disc positions so that they don't have to move.
295 const double disc_offset =
296 position->index_position - wrist_loop_->X_hat(0, 0);
297 for (auto frisbee = frisbees_.begin();
298 frisbee != frisbees_.end(); ++frisbee) {
299 frisbee->OffsetDisc(disc_offset);
300 }
301 }
302 missing_position_count_ = 0;
303 } else {
304 ++missing_position_count_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800305 }
306 const double index_position = wrist_loop_->X_hat(0, 0);
307
Austin Schuh825bde92013-03-06 00:16:46 -0800308 if (position) {
309 if (!position->top_disc_detect) {
310 // We don't see a disc. Verify that there are no discs that we should be
311 // seeing.
Austin Schuh1b864a12013-03-07 00:46:50 -0800312 // Assume that discs will move slow enough that we won't miss one as it
313 // goes by. They will either pile up above or below the sensor.
Austin Schuhdff24e22013-03-06 00:41:21 -0800314
315 double cumulative_offset = 0.0;
316 for (auto frisbee = frisbees_.rbegin(), rend = frisbees_.rend();
317 frisbee != rend; ++frisbee) {
318 frisbee->OffsetDisc(cumulative_offset);
319 double amount_moved = frisbee->ObserveNoTopDiscSensor(
Austin Schuh825bde92013-03-06 00:16:46 -0800320 wrist_loop_->X_hat(0, 0), wrist_loop_->X_hat(1, 0));
Austin Schuhdff24e22013-03-06 00:41:21 -0800321 cumulative_offset += amount_moved;
Austin Schuh825bde92013-03-06 00:16:46 -0800322 }
323 }
Austin Schuh1b864a12013-03-07 00:46:50 -0800324
Austin Schuh825bde92013-03-06 00:16:46 -0800325 if (position->top_disc_posedge_count != last_top_disc_posedge_count_) {
Austin Schuh1b864a12013-03-07 00:46:50 -0800326 const double index_position = wrist_loop_->X_hat(0, 0) -
327 position->index_position + position->top_disc_posedge_position;
Austin Schuh825bde92013-03-06 00:16:46 -0800328 // TODO(aschuh): Sanity check this number...
329 // Requires storing when the disc was last seen with the sensor off, and
330 // figuring out what to do if things go south.
331
332 // Find a disc that we should be seeing. There are 3 cases...
333 // 1) The top most disc is going up by the sensor.
334 // 2) There is 1 disc almost in the loader, and past the sensor.
335 // This is the next disc.
336 // 3) The top most disc is coming back down and we are seeing it.
337 if (wrist_loop_->X_hat(1, 0) > 50.0) {
338 // Moving up at a reasonable clip.
Austin Schuh1b864a12013-03-07 00:46:50 -0800339 // Find the highest disc that is below the top disc sensor.
340 // While we are at it, count the number above and log an error if there
341 // are too many.
342 if (frisbees_.size() == 0) {
343 Frisbee new_frisbee;
344 new_frisbee.has_been_indexed_ = true;
345 new_frisbee.index_start_position_ = index_position -
346 ConvertDiscPositionToIndex(kTopDiscDetectStart -
347 kIndexStartPosition);
348 frisbees_.push_front(new_frisbee);
349 LOG(WARNING, "Added a disc to the hopper at the top sensor\n");
350 }
351
352 int above_disc_count = 0;
353 double highest_position = 0;
354 Frisbee *highest_frisbee_below_sensor = NULL;
355 for (auto frisbee = frisbees_.rbegin(), rend = frisbees_.rend();
356 frisbee != rend; ++frisbee) {
357 const double disc_position = frisbee->absolute_position(
358 index_position);
359 // It is save to use the top position for the cuttoff, since the
360 // sensor being low will result in discs being pushed off of it.
361 if (disc_position >= kTopDiscDetectStop) {
362 ++above_disc_count;
363 } else if (!highest_frisbee_below_sensor ||
364 disc_position > highest_position) {
365 highest_frisbee_below_sensor = &*frisbee;
366 highest_position = disc_position;
367 }
368 }
369 if (above_disc_count > 1) {
370 LOG(ERROR, "We have 2 discs above the top sensor.\n");
371 }
372
373 // We now have the disc. Shift all the ones below the sensor up by the
374 // computed delta.
375 const double disc_delta = IndexMotor::ConvertDiscPositionToIndex(
376 highest_position - kTopDiscDetectStart);
377 for (auto frisbee = frisbees_.rbegin(), rend = frisbees_.rend();
378 frisbee != rend; ++frisbee) {
379 const double disc_position = frisbee->absolute_position(
380 index_position);
381 if (disc_position < kTopDiscDetectStop) {
382 frisbee->OffsetDisc(disc_delta);
383 }
384 }
385 printf("Currently have %d discs, saw posedge moving up. "
386 "Moving down by %f to %f\n", frisbees_.size(),
387 ConvertIndexToDiscPosition(disc_delta),
388 highest_frisbee_below_sensor->absolute_position(
389 wrist_loop_->X_hat(0, 0)));
Austin Schuh825bde92013-03-06 00:16:46 -0800390 } else if (wrist_loop_->X_hat(1, 0) < -50.0) {
391 // Moving down at a reasonable clip.
Austin Schuh1b864a12013-03-07 00:46:50 -0800392 // There can only be 1 disc up top that would give us a posedge.
393 // Find it and place it at the one spot that it can be.
394 double min_disc_position;
395 Frisbee *min_frisbee = NULL;
396 MinDiscPosition(&min_disc_position, &min_frisbee);
397 if (!min_frisbee) {
398 // Uh, oh, we see a disc but there isn't one...
399 LOG(ERROR, "Saw a disc up top but there isn't one in the hopper\n");
400 } else {
401 const double disc_position = min_frisbee->absolute_position(
402 index_position);
403
404 const double disc_delta_meters = disc_position - kTopDiscDetectStop;
405 const double disc_delta = IndexMotor::ConvertDiscPositionToIndex(
406 disc_delta_meters);
407 printf("Posedge going down. Moving top disc down by %f\n",
408 disc_delta_meters);
409 for (auto frisbee = frisbees_.begin(), end = frisbees_.end();
410 frisbee != end; ++frisbee) {
411 frisbee->OffsetDisc(disc_delta);
412 }
413 }
Austin Schuh825bde92013-03-06 00:16:46 -0800414 } else {
Austin Schuh1b864a12013-03-07 00:46:50 -0800415 // Save the upper and lower positions that we last saw a disc at.
416 // If there is a big buffer above, must be a disc from below.
417 // If there is a big buffer below, must be a disc from above.
418 // This should work to replace the velocity threshold above.
Austin Schuh825bde92013-03-06 00:16:46 -0800419 // TODO(aschuh): Do something!
Austin Schuh1b864a12013-03-07 00:46:50 -0800420 //
Austin Schuh825bde92013-03-06 00:16:46 -0800421 }
422 }
423 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800424
Austin Schuhf8c52252013-03-03 02:25:49 -0800425 // Bool to track if it is safe for the goal to change yet.
Austin Schuhd78ab542013-03-01 22:22:19 -0800426 bool safe_to_change_state_ = true;
427 switch (safe_goal_) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800428 case Goal::HOLD:
Austin Schuhd78ab542013-03-01 22:22:19 -0800429 // The goal should already be good, so sit tight with everything the same
430 // as it was.
Austin Schuhd78ab542013-03-01 22:22:19 -0800431 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800432 case Goal::READY_LOWER:
433 case Goal::INTAKE:
Austin Schuhd78ab542013-03-01 22:22:19 -0800434 {
435 Time now = Time::Now();
Austin Schuhd78ab542013-03-01 22:22:19 -0800436 if (position) {
Austin Schuh6328daf2013-03-05 00:53:15 -0800437 // Posedge of the disc entering the beam break.
438 if (position->bottom_disc_posedge_count !=
439 last_bottom_disc_posedge_count_) {
Austin Schuhd78ab542013-03-01 22:22:19 -0800440 transfer_frisbee_.Reset();
441 transfer_frisbee_.bottom_posedge_time_ = now;
442 printf("Posedge of bottom disc %f\n",
443 transfer_frisbee_.bottom_posedge_time_.ToSeconds());
444 ++hopper_disc_count_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800445 ++total_disc_count_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800446 }
447
448 // Disc exited the beam break now.
Austin Schuh6328daf2013-03-05 00:53:15 -0800449 if (position->bottom_disc_negedge_count !=
450 last_bottom_disc_negedge_count_) {
Austin Schuhd78ab542013-03-01 22:22:19 -0800451 transfer_frisbee_.bottom_negedge_time_ = now;
452 printf("Negedge of bottom disc %f\n",
453 transfer_frisbee_.bottom_negedge_time_.ToSeconds());
454 frisbees_.push_front(transfer_frisbee_);
455 }
456
457 if (position->bottom_disc_detect) {
Austin Schuhb6d898b2013-03-03 15:34:35 -0800458 intake_voltage = transfer_voltage = 12.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800459 // Must wait until the disc gets out before we can change state.
460 safe_to_change_state_ = false;
461
Austin Schuhf8c52252013-03-03 02:25:49 -0800462 // TODO(aschuh): A disc on the way through needs to start moving
463 // the indexer if it isn't already moving. Maybe?
Austin Schuhd78ab542013-03-01 22:22:19 -0800464
465 Time elapsed_posedge_time = now -
466 transfer_frisbee_.bottom_posedge_time_;
467 if (elapsed_posedge_time >= Time::InSeconds(0.3)) {
468 // It has been too long. The disc must be jammed.
469 LOG(ERROR, "Been way too long. Jammed disc?\n");
470 printf("Been way too long. Jammed disc?\n");
471 }
472 }
473
Austin Schuhf8c52252013-03-03 02:25:49 -0800474 // Check all non-indexed discs and see if they should be indexed.
Austin Schuhb6d898b2013-03-03 15:34:35 -0800475 for (auto frisbee = frisbees_.begin();
Austin Schuhbcdb90c2013-03-03 23:24:58 -0800476 frisbee != frisbees_.end(); ++frisbee) {
Austin Schuhb6d898b2013-03-03 15:34:35 -0800477 if (!frisbee->has_been_indexed_) {
478 intake_voltage = transfer_voltage = 12.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800479
Austin Schuh6328daf2013-03-05 00:53:15 -0800480 if (last_bottom_disc_negedge_wait_count_ !=
481 position->bottom_disc_negedge_wait_count) {
482 // We have an index difference.
483 // Save the indexer position, and the time.
484 if (last_bottom_disc_negedge_wait_count_ + 1 !=
485 position->bottom_disc_negedge_wait_count) {
486 LOG(ERROR, "Funny, we got 2 edges since we last checked.\n");
487 }
488
489 // Save the captured position as the position at which the disc
490 // touched the indexer.
Austin Schuhd78ab542013-03-01 22:22:19 -0800491 LOG(INFO, "Grabbed on the index now at %f\n", index_position);
492 printf("Grabbed on the index now at %f\n", index_position);
Austin Schuhb6d898b2013-03-03 15:34:35 -0800493 frisbee->has_been_indexed_ = true;
Austin Schuh6328daf2013-03-05 00:53:15 -0800494 frisbee->index_start_position_ =
495 position->bottom_disc_negedge_wait_position;
Austin Schuhd78ab542013-03-01 22:22:19 -0800496 }
497 }
Austin Schuhb6d898b2013-03-03 15:34:35 -0800498 if (!frisbee->has_been_indexed_) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800499 // All discs must be indexed before it is safe to stop indexing.
Austin Schuhd78ab542013-03-01 22:22:19 -0800500 safe_to_change_state_ = false;
501 }
502 }
503
Austin Schuhf8c52252013-03-03 02:25:49 -0800504 // Figure out where the indexer should be to move the discs down to
505 // the right position.
506 double max_disc_position;
Austin Schuh1b864a12013-03-07 00:46:50 -0800507 if (MaxDiscPosition(&max_disc_position, NULL)) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800508 printf("There is a disc down here!\n");
509 // TODO(aschuh): Figure out what to do if grabbing the next one
510 // would cause things to jam into the loader.
511 // Say we aren't ready any more. Undefined behavior will result if
512 // that isn't observed.
513 double bottom_disc_position =
514 max_disc_position + ConvertDiscAngleToIndex(M_PI);
515 wrist_loop_->R << bottom_disc_position, 0.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800516
Austin Schuhf8c52252013-03-03 02:25:49 -0800517 // Verify that we are close enough to the goal so that we should be
518 // fine accepting the next disc.
519 double disc_error_meters = ConvertIndexToDiscPosition(
520 wrist_loop_->X_hat(0, 0) - bottom_disc_position);
521 // We are ready for the next disc if the first one is in the first
522 // half circle of the indexer. It will take time for the disc to
523 // come into the indexer, so we will be able to move it out of the
524 // way in time.
525 // This choice also makes sure that we don't claim that we aren't
526 // ready between full speed intaking.
527 if (-ConvertDiscAngleToIndex(M_PI) < disc_error_meters &&
528 disc_error_meters < 0.04) {
529 // We are only ready if we aren't being asked to change state or
530 // are full.
531 status->ready_to_intake =
532 (safe_goal_ == goal_enum) && hopper_disc_count_ < 4;
533 } else {
534 status->ready_to_intake = false;
Austin Schuhd78ab542013-03-01 22:22:19 -0800535 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800536 } else {
537 // No discs! We are always ready for more if we aren't being
538 // asked to change state.
539 status->ready_to_intake = (safe_goal_ == goal_enum);
Austin Schuhd78ab542013-03-01 22:22:19 -0800540 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800541
542 // Turn on the transfer roller if we are ready.
543 if (status->ready_to_intake && hopper_disc_count_ < 4 &&
544 safe_goal_ == Goal::INTAKE) {
Austin Schuhb6d898b2013-03-03 15:34:35 -0800545 intake_voltage = transfer_voltage = 12.0;
Austin Schuhf8c52252013-03-03 02:25:49 -0800546 }
Austin Schuhd78ab542013-03-01 22:22:19 -0800547 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800548 printf("INTAKE\n");
Austin Schuhd78ab542013-03-01 22:22:19 -0800549 }
550 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800551 case Goal::READY_SHOOTER:
552 case Goal::SHOOT:
553 // Check if we have any discs to shoot or load and handle them.
554 double min_disc_position;
Austin Schuh1b864a12013-03-07 00:46:50 -0800555 if (MinDiscPosition(&min_disc_position, NULL)) {
556 const double ready_disc_position = min_disc_position +
557 ConvertDiscPositionToIndex(kReadyToPreload - kIndexStartPosition);
Austin Schuhf8c52252013-03-03 02:25:49 -0800558
559 const double grabbed_disc_position =
560 min_disc_position +
561 ConvertDiscPositionToIndex(kReadyToLiftPosition -
562 kIndexStartPosition + 0.03);
563
564 // Check the state of the loader FSM.
565 // If it is ready to load discs, position the disc so that it is ready
566 // to be grabbed.
567 // If it isn't ready, there is a disc in there. It needs to finish it's
568 // cycle first.
569 if (loader_state_ != LoaderState::READY) {
570 // We already have a disc in the loader.
571 // Stage the discs back a bit.
572 wrist_loop_->R << ready_disc_position, 0.0;
573
Austin Schuhbcdb90c2013-03-03 23:24:58 -0800574 // Shoot if we are grabbed and being asked to shoot.
575 if (loader_state_ == LoaderState::GRABBED &&
576 safe_goal_ == Goal::SHOOT) {
577 loader_goal_ = LoaderGoal::SHOOT_AND_RESET;
578 }
579
Austin Schuhf8c52252013-03-03 02:25:49 -0800580 // Must wait until it has been grabbed to continue.
581 if (loader_state_ == LoaderState::GRABBING) {
582 safe_to_change_state_ = false;
583 }
584 } else {
585 // No disc up top right now.
586 wrist_loop_->R << grabbed_disc_position, 0.0;
587
588 // See if the disc has gotten pretty far up yet.
589 if (wrist_loop_->X_hat(0, 0) > ready_disc_position) {
590 // Point of no return. We are committing to grabbing it now.
591 safe_to_change_state_ = false;
592 const double robust_grabbed_disc_position =
593 (grabbed_disc_position -
594 ConvertDiscPositionToIndex(kGrabberLength));
595
596 // If close, start grabbing and/or shooting.
597 if (wrist_loop_->X_hat(0, 0) > robust_grabbed_disc_position) {
598 // Start the state machine.
599 if (safe_goal_ == Goal::SHOOT) {
600 loader_goal_ = LoaderGoal::SHOOT_AND_RESET;
601 } else {
602 loader_goal_ = LoaderGoal::GRAB;
603 }
604 // This frisbee is now gone. Take it out of the queue.
605 frisbees_.pop_back();
606 --hopper_disc_count_;
607 }
608 }
609 }
610 }
611
612 printf("READY_SHOOTER or SHOOT\n");
Austin Schuhd78ab542013-03-01 22:22:19 -0800613 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800614 }
615
616 // The only way out of the loader is to shoot the disc. The FSM can only go
617 // forwards.
618 switch (loader_state_) {
619 case LoaderState::READY:
620 printf("Loader READY\n");
621 // Open and down, ready to accept a disc.
622 loader_up_ = false;
623 disc_clamped_ = false;
624 disc_ejected_ = false;
625 if (loader_goal_ == LoaderGoal::GRAB ||
626 loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
627 if (loader_goal_ == LoaderGoal::GRAB) {
628 printf("Told to GRAB, moving on\n");
629 } else {
630 printf("Told to SHOOT_AND_RESET, moving on\n");
631 }
632 loader_state_ = LoaderState::GRABBING;
633 loader_countdown_ = kGrabbingDelay;
634 } else {
635 break;
636 }
637 case LoaderState::GRABBING:
638 printf("Loader GRABBING %d\n", loader_countdown_);
639 // Closing the grabber.
640 loader_up_ = false;
641 disc_clamped_ = true;
642 disc_ejected_ = false;
643 if (loader_countdown_ > 0) {
644 --loader_countdown_;
645 break;
646 } else {
647 loader_state_ = LoaderState::GRABBED;
648 }
649 case LoaderState::GRABBED:
650 printf("Loader GRABBED\n");
651 // Grabber closed.
652 loader_up_ = false;
653 disc_clamped_ = true;
654 disc_ejected_ = false;
655 if (loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
656 // TODO(aschuh): Only shoot if the shooter is up to speed.
657 // Seems like that would have us shooting a bit later than we could be,
658 // but it also probably spins back up real fast.
659 loader_state_ = LoaderState::LIFTING;
660 loader_countdown_ = kLiftingDelay;
661 printf("Told to SHOOT_AND_RESET, moving on\n");
662 } else if (loader_goal_ == LoaderGoal::READY) {
663 LOG(ERROR, "Can't go to ready when we have something grabbed.\n");
664 printf("Can't go to ready when we have something grabbed.\n");
665 break;
666 } else {
667 break;
668 }
669 case LoaderState::LIFTING:
670 printf("Loader LIFTING %d\n", loader_countdown_);
671 // Lifting the disc.
672 loader_up_ = true;
673 disc_clamped_ = true;
674 disc_ejected_ = false;
675 if (loader_countdown_ > 0) {
676 --loader_countdown_;
677 break;
678 } else {
679 loader_state_ = LoaderState::LIFTED;
680 }
681 case LoaderState::LIFTED:
682 printf("Loader LIFTED\n");
683 // Disc lifted. Time to eject it out.
684 loader_up_ = true;
685 disc_clamped_ = true;
686 disc_ejected_ = false;
687 loader_state_ = LoaderState::SHOOTING;
688 loader_countdown_ = kShootingDelay;
689 case LoaderState::SHOOTING:
690 printf("Loader SHOOTING %d\n", loader_countdown_);
691 // Ejecting the disc into the shooter.
692 loader_up_ = true;
693 disc_clamped_ = false;
694 disc_ejected_ = true;
695 if (loader_countdown_ > 0) {
696 --loader_countdown_;
697 break;
698 } else {
699 loader_state_ = LoaderState::SHOOT;
700 }
701 case LoaderState::SHOOT:
702 printf("Loader SHOOT\n");
703 // The disc has been shot.
704 loader_up_ = true;
705 disc_clamped_ = false;
706 disc_ejected_ = true;
707 loader_state_ = LoaderState::LOWERING;
708 loader_countdown_ = kLoweringDelay;
709 case LoaderState::LOWERING:
710 printf("Loader LOWERING %d\n", loader_countdown_);
711 // Lowering the loader back down.
712 loader_up_ = false;
713 disc_clamped_ = false;
714 disc_ejected_ = true;
715 if (loader_countdown_ > 0) {
716 --loader_countdown_;
717 break;
718 } else {
719 loader_state_ = LoaderState::LOWERED;
720 }
721 case LoaderState::LOWERED:
722 printf("Loader LOWERED\n");
723 // The indexer is lowered.
724 loader_up_ = false;
725 disc_clamped_ = false;
726 disc_ejected_ = false;
727 loader_state_ = LoaderState::READY;
728 // Once we have shot, we need to hang out in READY until otherwise
729 // notified.
730 loader_goal_ = LoaderGoal::READY;
Austin Schuhd78ab542013-03-01 22:22:19 -0800731 break;
732 }
733
734 // Update the observer.
735 wrist_loop_->Update(position != NULL, output == NULL);
736
737 if (position) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800738 LOG(DEBUG, "pos=%f\n", position->index_position);
Austin Schuhd78ab542013-03-01 22:22:19 -0800739 last_bottom_disc_detect_ = position->bottom_disc_detect;
Austin Schuh825bde92013-03-06 00:16:46 -0800740 last_top_disc_detect_ = position->top_disc_detect;
Austin Schuh6328daf2013-03-05 00:53:15 -0800741 last_bottom_disc_posedge_count_ = position->bottom_disc_posedge_count;
742 last_bottom_disc_negedge_count_ = position->bottom_disc_negedge_count;
743 last_bottom_disc_negedge_wait_count_ =
744 position->bottom_disc_negedge_wait_count;
Austin Schuh825bde92013-03-06 00:16:46 -0800745 last_top_disc_posedge_count_ = position->top_disc_posedge_count;
Austin Schuhd78ab542013-03-01 22:22:19 -0800746 }
747
748 status->hopper_disc_count = hopper_disc_count_;
749 status->total_disc_count = total_disc_count_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800750 status->preloaded = (loader_state_ != LoaderState::READY);
Austin Schuhd78ab542013-03-01 22:22:19 -0800751
752 if (output) {
Austin Schuhb6d898b2013-03-03 15:34:35 -0800753 output->intake_voltage = intake_voltage;
Austin Schuhf8c52252013-03-03 02:25:49 -0800754 output->transfer_voltage = transfer_voltage;
Austin Schuhd78ab542013-03-01 22:22:19 -0800755 output->index_voltage = wrist_loop_->U(0, 0);
Austin Schuhf8c52252013-03-03 02:25:49 -0800756 output->loader_up = loader_up_;
757 output->disc_clamped = disc_clamped_;
758 output->disc_ejected = disc_ejected_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800759 }
760
761 if (safe_to_change_state_) {
762 safe_goal_ = goal_enum;
763 }
764}
765
766} // namespace control_loops
767} // namespace frc971