blob: 98d51b8952bcfffc7f34782205578dc2b81fe9a7 [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
21IndexMotor::IndexMotor(control_loops::IndexLoop *my_index)
22 : aos::control_loops::ControlLoop<control_loops::IndexLoop>(my_index),
23 wrist_loop_(new StateFeedbackLoop<2, 1, 1>(MakeIndexLoop())),
24 hopper_disc_count_(0),
25 total_disc_count_(0),
Austin Schuhf8c52252013-03-03 02:25:49 -080026 safe_goal_(Goal::HOLD),
27 loader_goal_(LoaderGoal::READY),
28 loader_state_(LoaderState::READY),
Austin Schuhd78ab542013-03-01 22:22:19 -080029 loader_up_(false),
30 disc_clamped_(false),
31 disc_ejected_(false),
32 last_bottom_disc_detect_(false) {
33}
34
Austin Schuhf8c52252013-03-03 02:25:49 -080035/*static*/ const double IndexMotor::kTransferStartPosition = 0.0;
36/*static*/ const double IndexMotor::kIndexStartPosition = 0.2159;
37/*static*/ const double IndexMotor::kIndexFreeLength =
38 IndexMotor::ConvertDiscAngleToDiscPosition((360 * 2 + 14) * M_PI / 180);
39/*static*/ const double IndexMotor::kLoaderFreeStopPosition =
40 kIndexStartPosition + kIndexFreeLength;
41/*static*/ const double IndexMotor::kReadyToLiftPosition =
42 kLoaderFreeStopPosition + 0.2921;
43/*static*/ const double IndexMotor::kGrabberLength = 0.03175;
44/*static*/ const double IndexMotor::kGrabberStartPosition =
45 kReadyToLiftPosition - kGrabberLength;
46/*static*/ const double IndexMotor::kGrabberMovementVelocity = 0.5;
47/*static*/ const double IndexMotor::kLifterStopPosition =
48 kReadyToLiftPosition + 0.161925;
49/*static*/ const double IndexMotor::kLifterMovementVelocity = 1.0;
50/*static*/ const double IndexMotor::kEjectorStopPosition =
51 kLifterStopPosition + 0.01;
52/*static*/ const double IndexMotor::kEjectorMovementVelocity = 1.0;
53/*static*/ const double IndexMotor::kBottomDiscDetectStart = -0.08;
54/*static*/ const double IndexMotor::kBottomDiscDetectStop = 0.200025;
55
56// TODO(aschuh): Figure these out.
57/*static*/ const double IndexMotor::kTopDiscDetectStart = 18.0;
58/*static*/ const double IndexMotor::kTopDiscDetectStop = 19.0;
59
Austin Schuhd78ab542013-03-01 22:22:19 -080060const /*static*/ double IndexMotor::kDiscRadius = 10.875 * 0.0254 / 2;
61const /*static*/ double IndexMotor::kRollerRadius = 2.0 * 0.0254 / 2;
Austin Schuhf8c52252013-03-03 02:25:49 -080062const /*static*/ double IndexMotor::kTransferRollerRadius = 1.25 * 0.0254 / 2;
Austin Schuhd78ab542013-03-01 22:22:19 -080063
Austin Schuhf8c52252013-03-03 02:25:49 -080064/*static*/ const int IndexMotor::kGrabbingDelay = 5;
65/*static*/ const int IndexMotor::kLiftingDelay = 20;
66/*static*/ const int IndexMotor::kShootingDelay = 5;
67/*static*/ const int IndexMotor::kLoweringDelay = 20;
Austin Schuhd78ab542013-03-01 22:22:19 -080068
69// Distance to move the indexer when grabbing a disc.
70const double kNextPosition = 10.0;
71
72/*static*/ double IndexMotor::ConvertDiscAngleToIndex(const double angle) {
73 return (angle * (1 + (kDiscRadius * 2 + kRollerRadius) / kRollerRadius));
74}
75
Austin Schuhf8c52252013-03-03 02:25:49 -080076/*static*/ double IndexMotor::ConvertDiscAngleToDiscPosition(
77 const double angle) {
Austin Schuhd78ab542013-03-01 22:22:19 -080078 return angle * (kDiscRadius + kRollerRadius);
79}
80
Austin Schuhf8c52252013-03-03 02:25:49 -080081/*static*/ double IndexMotor::ConvertDiscPositionToDiscAngle(
82 const double position) {
83 return position / (kDiscRadius + kRollerRadius);
84}
85
Austin Schuhd78ab542013-03-01 22:22:19 -080086/*static*/ double IndexMotor::ConvertIndexToDiscAngle(const double angle) {
87 return (angle / (1 + (kDiscRadius * 2 + kRollerRadius) / kRollerRadius));
88}
89
90/*static*/ double IndexMotor::ConvertIndexToDiscPosition(const double angle) {
91 return IndexMotor::ConvertDiscAngleToDiscPosition(
92 ConvertIndexToDiscAngle(angle));
93}
94
Austin Schuhf8c52252013-03-03 02:25:49 -080095/*static*/ double IndexMotor::ConvertTransferToDiscPosition(
96 const double angle) {
97 const double gear_ratio = (1 + (kDiscRadius * 2 + kTransferRollerRadius) /
98 kTransferRollerRadius);
99 return angle / gear_ratio * (kDiscRadius + kTransferRollerRadius);
100}
101
102/*static*/ double IndexMotor::ConvertDiscPositionToIndex(
103 const double position) {
104 return IndexMotor::ConvertDiscAngleToIndex(
105 ConvertDiscPositionToDiscAngle(position));
106}
107
108bool IndexMotor::MinDiscPosition(double *disc_position) {
109 bool found_start = false;
110 for (unsigned int i = 0; i < frisbees_.size(); ++i) {
111 const Frisbee &frisbee = frisbees_[i];
112 if (!found_start) {
113 if (frisbee.has_position()) {
114 *disc_position = frisbee.position();
115 found_start = true;
116 }
117 } else {
118 *disc_position = ::std::min(frisbee.position(),
119 *disc_position);
120 }
121 }
122 return found_start;
123}
124
125bool IndexMotor::MaxDiscPosition(double *disc_position) {
126 bool found_start = false;
127 for (unsigned int i = 0; i < frisbees_.size(); ++i) {
128 const Frisbee &frisbee = frisbees_[i];
129 if (!found_start) {
130 if (frisbee.has_position()) {
131 *disc_position = frisbee.position();
132 found_start = true;
133 }
134 } else {
135 *disc_position = ::std::max(frisbee.position(),
136 *disc_position);
137 }
138 }
139 return found_start;
140}
141
Austin Schuhd78ab542013-03-01 22:22:19 -0800142// Positive angle is towards the shooter, and positive power is towards the
143// shooter.
144void IndexMotor::RunIteration(
145 const control_loops::IndexLoop::Goal *goal,
146 const control_loops::IndexLoop::Position *position,
147 control_loops::IndexLoop::Output *output,
148 control_loops::IndexLoop::Status *status) {
149 // Make goal easy to work with.
150 Goal goal_enum = static_cast<Goal>(goal->goal_state);
151
152 // Disable the motors now so that all early returns will return with the
153 // motors disabled.
Austin Schuhb6d898b2013-03-03 15:34:35 -0800154 double intake_voltage = 0.0;
Austin Schuhf8c52252013-03-03 02:25:49 -0800155 double transfer_voltage = 0.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800156 if (output) {
Austin Schuhb6d898b2013-03-03 15:34:35 -0800157 output->intake_voltage = 0.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800158 output->transfer_voltage = 0.0;
159 output->index_voltage = 0.0;
160 }
161
162 status->ready_to_intake = false;
163
Austin Schuhf8c52252013-03-03 02:25:49 -0800164 // Compute a safe index position that we can use.
Austin Schuhd78ab542013-03-01 22:22:19 -0800165 if (position) {
166 wrist_loop_->Y << position->index_position;
167 }
168 const double index_position = wrist_loop_->X_hat(0, 0);
169
Austin Schuhf8c52252013-03-03 02:25:49 -0800170 // TODO(aschuh): Watch for top disc detect and update the frisbee
171 // position.
172
173 // TODO(aschuh): Horizontal and centering should be here as well...
174
175 // Bool to track if it is safe for the goal to change yet.
Austin Schuhd78ab542013-03-01 22:22:19 -0800176 bool safe_to_change_state_ = true;
177 switch (safe_goal_) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800178 case Goal::HOLD:
Austin Schuhd78ab542013-03-01 22:22:19 -0800179 // The goal should already be good, so sit tight with everything the same
180 // as it was.
Austin Schuhd78ab542013-03-01 22:22:19 -0800181 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800182 case Goal::READY_LOWER:
183 case Goal::INTAKE:
Austin Schuhd78ab542013-03-01 22:22:19 -0800184 {
185 Time now = Time::Now();
Austin Schuhd78ab542013-03-01 22:22:19 -0800186 // Posedge of the disc entering the beam break.
187 if (position) {
Austin Schuh89955e42013-03-03 02:37:08 -0800188 // TODO(aschuh): Catch the edges on the FPGA since this is too slow.
189 // This means that we need to pass back enough data so that we can
190 // miss packets and everything works.
Austin Schuhd78ab542013-03-01 22:22:19 -0800191 if (position->bottom_disc_detect && !last_bottom_disc_detect_) {
192 transfer_frisbee_.Reset();
193 transfer_frisbee_.bottom_posedge_time_ = now;
194 printf("Posedge of bottom disc %f\n",
195 transfer_frisbee_.bottom_posedge_time_.ToSeconds());
196 ++hopper_disc_count_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800197 ++total_disc_count_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800198 }
199
200 // Disc exited the beam break now.
201 if (!position->bottom_disc_detect && last_bottom_disc_detect_) {
202 transfer_frisbee_.bottom_negedge_time_ = now;
203 printf("Negedge of bottom disc %f\n",
204 transfer_frisbee_.bottom_negedge_time_.ToSeconds());
205 frisbees_.push_front(transfer_frisbee_);
206 }
207
208 if (position->bottom_disc_detect) {
Austin Schuhb6d898b2013-03-03 15:34:35 -0800209 intake_voltage = transfer_voltage = 12.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800210 // Must wait until the disc gets out before we can change state.
211 safe_to_change_state_ = false;
212
Austin Schuhf8c52252013-03-03 02:25:49 -0800213 // TODO(aschuh): A disc on the way through needs to start moving
214 // the indexer if it isn't already moving. Maybe?
Austin Schuhd78ab542013-03-01 22:22:19 -0800215
216 Time elapsed_posedge_time = now -
217 transfer_frisbee_.bottom_posedge_time_;
218 if (elapsed_posedge_time >= Time::InSeconds(0.3)) {
219 // It has been too long. The disc must be jammed.
220 LOG(ERROR, "Been way too long. Jammed disc?\n");
221 printf("Been way too long. Jammed disc?\n");
222 }
223 }
224
Austin Schuhf8c52252013-03-03 02:25:49 -0800225 // Check all non-indexed discs and see if they should be indexed.
Austin Schuhb6d898b2013-03-03 15:34:35 -0800226 for (auto frisbee = frisbees_.begin();
227 frisbee != frisbees_.end(); ++frisbee) {
228 if (!frisbee->has_been_indexed_) {
229 intake_voltage = transfer_voltage = 12.0;
Austin Schuhf8c52252013-03-03 02:25:49 -0800230 Time elapsed_negedge_time = now -
Austin Schuhb6d898b2013-03-03 15:34:35 -0800231 frisbee->bottom_negedge_time_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800232 if (elapsed_negedge_time >= Time::InSeconds(0.005)) {
Austin Schuhd78ab542013-03-01 22:22:19 -0800233 // Should have just engaged.
234 // Save the indexer position, and the time.
235
236 // It has been long enough since the disc entered the indexer.
237 // Treat now as the time at which it contacted the indexer.
238 LOG(INFO, "Grabbed on the index now at %f\n", index_position);
239 printf("Grabbed on the index now at %f\n", index_position);
Austin Schuhb6d898b2013-03-03 15:34:35 -0800240 frisbee->has_been_indexed_ = true;
241 frisbee->index_start_position_ = index_position;
Austin Schuhd78ab542013-03-01 22:22:19 -0800242 }
243 }
Austin Schuhb6d898b2013-03-03 15:34:35 -0800244 if (!frisbee->has_been_indexed_) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800245 // All discs must be indexed before it is safe to stop indexing.
Austin Schuhd78ab542013-03-01 22:22:19 -0800246 safe_to_change_state_ = false;
247 }
248 }
249
Austin Schuhf8c52252013-03-03 02:25:49 -0800250 // Figure out where the indexer should be to move the discs down to
251 // the right position.
252 double max_disc_position;
253 if (MaxDiscPosition(&max_disc_position)) {
254 printf("There is a disc down here!\n");
255 // TODO(aschuh): Figure out what to do if grabbing the next one
256 // would cause things to jam into the loader.
257 // Say we aren't ready any more. Undefined behavior will result if
258 // that isn't observed.
259 double bottom_disc_position =
260 max_disc_position + ConvertDiscAngleToIndex(M_PI);
261 wrist_loop_->R << bottom_disc_position, 0.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800262
Austin Schuhf8c52252013-03-03 02:25:49 -0800263 // Verify that we are close enough to the goal so that we should be
264 // fine accepting the next disc.
265 double disc_error_meters = ConvertIndexToDiscPosition(
266 wrist_loop_->X_hat(0, 0) - bottom_disc_position);
267 // We are ready for the next disc if the first one is in the first
268 // half circle of the indexer. It will take time for the disc to
269 // come into the indexer, so we will be able to move it out of the
270 // way in time.
271 // This choice also makes sure that we don't claim that we aren't
272 // ready between full speed intaking.
273 if (-ConvertDiscAngleToIndex(M_PI) < disc_error_meters &&
274 disc_error_meters < 0.04) {
275 // We are only ready if we aren't being asked to change state or
276 // are full.
277 status->ready_to_intake =
278 (safe_goal_ == goal_enum) && hopper_disc_count_ < 4;
279 } else {
280 status->ready_to_intake = false;
Austin Schuhd78ab542013-03-01 22:22:19 -0800281 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800282 } else {
283 // No discs! We are always ready for more if we aren't being
284 // asked to change state.
285 status->ready_to_intake = (safe_goal_ == goal_enum);
Austin Schuhd78ab542013-03-01 22:22:19 -0800286 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800287
288 // Turn on the transfer roller if we are ready.
289 if (status->ready_to_intake && hopper_disc_count_ < 4 &&
290 safe_goal_ == Goal::INTAKE) {
Austin Schuhb6d898b2013-03-03 15:34:35 -0800291 intake_voltage = transfer_voltage = 12.0;
Austin Schuhf8c52252013-03-03 02:25:49 -0800292 }
Austin Schuhd78ab542013-03-01 22:22:19 -0800293 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800294 printf("INTAKE\n");
Austin Schuhd78ab542013-03-01 22:22:19 -0800295 }
296 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800297 case Goal::READY_SHOOTER:
298 case Goal::SHOOT:
299 // Check if we have any discs to shoot or load and handle them.
300 double min_disc_position;
301 if (MinDiscPosition(&min_disc_position)) {
302 const double ready_disc_position =
303 min_disc_position + ConvertDiscPositionToIndex(kIndexFreeLength) -
304 ConvertDiscAngleToIndex(M_PI / 6.0);
305
306 const double grabbed_disc_position =
307 min_disc_position +
308 ConvertDiscPositionToIndex(kReadyToLiftPosition -
309 kIndexStartPosition + 0.03);
310
311 // Check the state of the loader FSM.
312 // If it is ready to load discs, position the disc so that it is ready
313 // to be grabbed.
314 // If it isn't ready, there is a disc in there. It needs to finish it's
315 // cycle first.
316 if (loader_state_ != LoaderState::READY) {
317 // We already have a disc in the loader.
318 // Stage the discs back a bit.
319 wrist_loop_->R << ready_disc_position, 0.0;
320
321 // Must wait until it has been grabbed to continue.
322 if (loader_state_ == LoaderState::GRABBING) {
323 safe_to_change_state_ = false;
324 }
325 } else {
326 // No disc up top right now.
327 wrist_loop_->R << grabbed_disc_position, 0.0;
328
329 // See if the disc has gotten pretty far up yet.
330 if (wrist_loop_->X_hat(0, 0) > ready_disc_position) {
331 // Point of no return. We are committing to grabbing it now.
332 safe_to_change_state_ = false;
333 const double robust_grabbed_disc_position =
334 (grabbed_disc_position -
335 ConvertDiscPositionToIndex(kGrabberLength));
336
337 // If close, start grabbing and/or shooting.
338 if (wrist_loop_->X_hat(0, 0) > robust_grabbed_disc_position) {
339 // Start the state machine.
340 if (safe_goal_ == Goal::SHOOT) {
341 loader_goal_ = LoaderGoal::SHOOT_AND_RESET;
342 } else {
343 loader_goal_ = LoaderGoal::GRAB;
344 }
345 // This frisbee is now gone. Take it out of the queue.
346 frisbees_.pop_back();
347 --hopper_disc_count_;
348 }
349 }
350 }
351 }
352
353 printf("READY_SHOOTER or SHOOT\n");
Austin Schuhd78ab542013-03-01 22:22:19 -0800354 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800355 }
356
357 // The only way out of the loader is to shoot the disc. The FSM can only go
358 // forwards.
359 switch (loader_state_) {
360 case LoaderState::READY:
361 printf("Loader READY\n");
362 // Open and down, ready to accept a disc.
363 loader_up_ = false;
364 disc_clamped_ = false;
365 disc_ejected_ = false;
366 if (loader_goal_ == LoaderGoal::GRAB ||
367 loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
368 if (loader_goal_ == LoaderGoal::GRAB) {
369 printf("Told to GRAB, moving on\n");
370 } else {
371 printf("Told to SHOOT_AND_RESET, moving on\n");
372 }
373 loader_state_ = LoaderState::GRABBING;
374 loader_countdown_ = kGrabbingDelay;
375 } else {
376 break;
377 }
378 case LoaderState::GRABBING:
379 printf("Loader GRABBING %d\n", loader_countdown_);
380 // Closing the grabber.
381 loader_up_ = false;
382 disc_clamped_ = true;
383 disc_ejected_ = false;
384 if (loader_countdown_ > 0) {
385 --loader_countdown_;
386 break;
387 } else {
388 loader_state_ = LoaderState::GRABBED;
389 }
390 case LoaderState::GRABBED:
391 printf("Loader GRABBED\n");
392 // Grabber closed.
393 loader_up_ = false;
394 disc_clamped_ = true;
395 disc_ejected_ = false;
396 if (loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
397 // TODO(aschuh): Only shoot if the shooter is up to speed.
398 // Seems like that would have us shooting a bit later than we could be,
399 // but it also probably spins back up real fast.
400 loader_state_ = LoaderState::LIFTING;
401 loader_countdown_ = kLiftingDelay;
402 printf("Told to SHOOT_AND_RESET, moving on\n");
403 } else if (loader_goal_ == LoaderGoal::READY) {
404 LOG(ERROR, "Can't go to ready when we have something grabbed.\n");
405 printf("Can't go to ready when we have something grabbed.\n");
406 break;
407 } else {
408 break;
409 }
410 case LoaderState::LIFTING:
411 printf("Loader LIFTING %d\n", loader_countdown_);
412 // Lifting the disc.
413 loader_up_ = true;
414 disc_clamped_ = true;
415 disc_ejected_ = false;
416 if (loader_countdown_ > 0) {
417 --loader_countdown_;
418 break;
419 } else {
420 loader_state_ = LoaderState::LIFTED;
421 }
422 case LoaderState::LIFTED:
423 printf("Loader LIFTED\n");
424 // Disc lifted. Time to eject it out.
425 loader_up_ = true;
426 disc_clamped_ = true;
427 disc_ejected_ = false;
428 loader_state_ = LoaderState::SHOOTING;
429 loader_countdown_ = kShootingDelay;
430 case LoaderState::SHOOTING:
431 printf("Loader SHOOTING %d\n", loader_countdown_);
432 // Ejecting the disc into the shooter.
433 loader_up_ = true;
434 disc_clamped_ = false;
435 disc_ejected_ = true;
436 if (loader_countdown_ > 0) {
437 --loader_countdown_;
438 break;
439 } else {
440 loader_state_ = LoaderState::SHOOT;
441 }
442 case LoaderState::SHOOT:
443 printf("Loader SHOOT\n");
444 // The disc has been shot.
445 loader_up_ = true;
446 disc_clamped_ = false;
447 disc_ejected_ = true;
448 loader_state_ = LoaderState::LOWERING;
449 loader_countdown_ = kLoweringDelay;
450 case LoaderState::LOWERING:
451 printf("Loader LOWERING %d\n", loader_countdown_);
452 // Lowering the loader back down.
453 loader_up_ = false;
454 disc_clamped_ = false;
455 disc_ejected_ = true;
456 if (loader_countdown_ > 0) {
457 --loader_countdown_;
458 break;
459 } else {
460 loader_state_ = LoaderState::LOWERED;
461 }
462 case LoaderState::LOWERED:
463 printf("Loader LOWERED\n");
464 // The indexer is lowered.
465 loader_up_ = false;
466 disc_clamped_ = false;
467 disc_ejected_ = false;
468 loader_state_ = LoaderState::READY;
469 // Once we have shot, we need to hang out in READY until otherwise
470 // notified.
471 loader_goal_ = LoaderGoal::READY;
Austin Schuhd78ab542013-03-01 22:22:19 -0800472 break;
473 }
474
475 // Update the observer.
476 wrist_loop_->Update(position != NULL, output == NULL);
477
478 if (position) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800479 LOG(DEBUG, "pos=%f\n", position->index_position);
Austin Schuhd78ab542013-03-01 22:22:19 -0800480 last_bottom_disc_detect_ = position->bottom_disc_detect;
481 }
482
483 status->hopper_disc_count = hopper_disc_count_;
484 status->total_disc_count = total_disc_count_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800485 status->preloaded = (loader_state_ != LoaderState::READY);
Austin Schuhd78ab542013-03-01 22:22:19 -0800486
487 if (output) {
Austin Schuhb6d898b2013-03-03 15:34:35 -0800488 output->intake_voltage = intake_voltage;
Austin Schuhf8c52252013-03-03 02:25:49 -0800489 output->transfer_voltage = transfer_voltage;
Austin Schuhd78ab542013-03-01 22:22:19 -0800490 output->index_voltage = wrist_loop_->U(0, 0);
Austin Schuhf8c52252013-03-03 02:25:49 -0800491 output->loader_up = loader_up_;
492 output->disc_clamped = disc_clamped_;
493 output->disc_ejected = disc_ejected_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800494 }
495
496 if (safe_to_change_state_) {
497 safe_goal_ = goal_enum;
498 }
499}
500
501} // namespace control_loops
502} // namespace frc971