blob: fa1940e7403b00c33c7252ae28840b2bc3e321a9 [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 Schuhf8c52252013-03-03 02:25:49 -0800154 double transfer_voltage = 0.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800155 if (output) {
156 output->transfer_voltage = 0.0;
157 output->index_voltage = 0.0;
158 }
159
160 status->ready_to_intake = false;
161
Austin Schuhf8c52252013-03-03 02:25:49 -0800162 // Compute a safe index position that we can use.
Austin Schuhd78ab542013-03-01 22:22:19 -0800163 if (position) {
164 wrist_loop_->Y << position->index_position;
165 }
166 const double index_position = wrist_loop_->X_hat(0, 0);
167
Austin Schuhf8c52252013-03-03 02:25:49 -0800168 // TODO(aschuh): Watch for top disc detect and update the frisbee
169 // position.
170
171 // TODO(aschuh): Horizontal and centering should be here as well...
172
173 // Bool to track if it is safe for the goal to change yet.
Austin Schuhd78ab542013-03-01 22:22:19 -0800174 bool safe_to_change_state_ = true;
175 switch (safe_goal_) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800176 case Goal::HOLD:
Austin Schuhd78ab542013-03-01 22:22:19 -0800177 // The goal should already be good, so sit tight with everything the same
178 // as it was.
Austin Schuhd78ab542013-03-01 22:22:19 -0800179 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800180 case Goal::READY_LOWER:
181 case Goal::INTAKE:
Austin Schuhd78ab542013-03-01 22:22:19 -0800182 {
183 Time now = Time::Now();
Austin Schuhd78ab542013-03-01 22:22:19 -0800184 // Posedge of the disc entering the beam break.
185 if (position) {
Austin Schuh89955e42013-03-03 02:37:08 -0800186 // TODO(aschuh): Catch the edges on the FPGA since this is too slow.
187 // This means that we need to pass back enough data so that we can
188 // miss packets and everything works.
Austin Schuhd78ab542013-03-01 22:22:19 -0800189 if (position->bottom_disc_detect && !last_bottom_disc_detect_) {
190 transfer_frisbee_.Reset();
191 transfer_frisbee_.bottom_posedge_time_ = now;
192 printf("Posedge of bottom disc %f\n",
193 transfer_frisbee_.bottom_posedge_time_.ToSeconds());
194 ++hopper_disc_count_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800195 ++total_disc_count_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800196 }
197
198 // Disc exited the beam break now.
199 if (!position->bottom_disc_detect && last_bottom_disc_detect_) {
200 transfer_frisbee_.bottom_negedge_time_ = now;
201 printf("Negedge of bottom disc %f\n",
202 transfer_frisbee_.bottom_negedge_time_.ToSeconds());
203 frisbees_.push_front(transfer_frisbee_);
204 }
205
206 if (position->bottom_disc_detect) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800207 transfer_voltage = 12.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800208 // Must wait until the disc gets out before we can change state.
209 safe_to_change_state_ = false;
210
Austin Schuhf8c52252013-03-03 02:25:49 -0800211 // TODO(aschuh): A disc on the way through needs to start moving
212 // the indexer if it isn't already moving. Maybe?
Austin Schuhd78ab542013-03-01 22:22:19 -0800213
214 Time elapsed_posedge_time = now -
215 transfer_frisbee_.bottom_posedge_time_;
216 if (elapsed_posedge_time >= Time::InSeconds(0.3)) {
217 // It has been too long. The disc must be jammed.
218 LOG(ERROR, "Been way too long. Jammed disc?\n");
219 printf("Been way too long. Jammed disc?\n");
220 }
221 }
222
Austin Schuhf8c52252013-03-03 02:25:49 -0800223 // Check all non-indexed discs and see if they should be indexed.
Austin Schuhd78ab542013-03-01 22:22:19 -0800224 for (Frisbee &frisbee : frisbees_) {
225 if (!frisbee.has_been_indexed_) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800226 transfer_voltage = 12.0;
227 Time elapsed_negedge_time = now -
228 frisbee.bottom_negedge_time_;
229 if (elapsed_negedge_time >= Time::InSeconds(0.005)) {
Austin Schuhd78ab542013-03-01 22:22:19 -0800230 // Should have just engaged.
231 // Save the indexer position, and the time.
232
233 // It has been long enough since the disc entered the indexer.
234 // Treat now as the time at which it contacted the indexer.
235 LOG(INFO, "Grabbed on the index now at %f\n", index_position);
236 printf("Grabbed on the index now at %f\n", index_position);
237 frisbee.has_been_indexed_ = true;
238 frisbee.index_start_position_ = index_position;
Austin Schuhd78ab542013-03-01 22:22:19 -0800239 }
240 }
241 if (!frisbee.has_been_indexed_) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800242 // All discs must be indexed before it is safe to stop indexing.
Austin Schuhd78ab542013-03-01 22:22:19 -0800243 safe_to_change_state_ = false;
244 }
245 }
246
Austin Schuhf8c52252013-03-03 02:25:49 -0800247 // Figure out where the indexer should be to move the discs down to
248 // the right position.
249 double max_disc_position;
250 if (MaxDiscPosition(&max_disc_position)) {
251 printf("There is a disc down here!\n");
252 // TODO(aschuh): Figure out what to do if grabbing the next one
253 // would cause things to jam into the loader.
254 // Say we aren't ready any more. Undefined behavior will result if
255 // that isn't observed.
256 double bottom_disc_position =
257 max_disc_position + ConvertDiscAngleToIndex(M_PI);
258 wrist_loop_->R << bottom_disc_position, 0.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800259
Austin Schuhf8c52252013-03-03 02:25:49 -0800260 // Verify that we are close enough to the goal so that we should be
261 // fine accepting the next disc.
262 double disc_error_meters = ConvertIndexToDiscPosition(
263 wrist_loop_->X_hat(0, 0) - bottom_disc_position);
264 // We are ready for the next disc if the first one is in the first
265 // half circle of the indexer. It will take time for the disc to
266 // come into the indexer, so we will be able to move it out of the
267 // way in time.
268 // This choice also makes sure that we don't claim that we aren't
269 // ready between full speed intaking.
270 if (-ConvertDiscAngleToIndex(M_PI) < disc_error_meters &&
271 disc_error_meters < 0.04) {
272 // We are only ready if we aren't being asked to change state or
273 // are full.
274 status->ready_to_intake =
275 (safe_goal_ == goal_enum) && hopper_disc_count_ < 4;
276 } else {
277 status->ready_to_intake = false;
Austin Schuhd78ab542013-03-01 22:22:19 -0800278 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800279 } else {
280 // No discs! We are always ready for more if we aren't being
281 // asked to change state.
282 status->ready_to_intake = (safe_goal_ == goal_enum);
Austin Schuhd78ab542013-03-01 22:22:19 -0800283 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800284
285 // Turn on the transfer roller if we are ready.
286 if (status->ready_to_intake && hopper_disc_count_ < 4 &&
287 safe_goal_ == Goal::INTAKE) {
288 transfer_voltage = 12.0;
289 }
Austin Schuhd78ab542013-03-01 22:22:19 -0800290 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800291 printf("INTAKE\n");
Austin Schuhd78ab542013-03-01 22:22:19 -0800292 }
293 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800294 case Goal::READY_SHOOTER:
295 case Goal::SHOOT:
296 // Check if we have any discs to shoot or load and handle them.
297 double min_disc_position;
298 if (MinDiscPosition(&min_disc_position)) {
299 const double ready_disc_position =
300 min_disc_position + ConvertDiscPositionToIndex(kIndexFreeLength) -
301 ConvertDiscAngleToIndex(M_PI / 6.0);
302
303 const double grabbed_disc_position =
304 min_disc_position +
305 ConvertDiscPositionToIndex(kReadyToLiftPosition -
306 kIndexStartPosition + 0.03);
307
308 // Check the state of the loader FSM.
309 // If it is ready to load discs, position the disc so that it is ready
310 // to be grabbed.
311 // If it isn't ready, there is a disc in there. It needs to finish it's
312 // cycle first.
313 if (loader_state_ != LoaderState::READY) {
314 // We already have a disc in the loader.
315 // Stage the discs back a bit.
316 wrist_loop_->R << ready_disc_position, 0.0;
317
318 // Must wait until it has been grabbed to continue.
319 if (loader_state_ == LoaderState::GRABBING) {
320 safe_to_change_state_ = false;
321 }
322 } else {
323 // No disc up top right now.
324 wrist_loop_->R << grabbed_disc_position, 0.0;
325
326 // See if the disc has gotten pretty far up yet.
327 if (wrist_loop_->X_hat(0, 0) > ready_disc_position) {
328 // Point of no return. We are committing to grabbing it now.
329 safe_to_change_state_ = false;
330 const double robust_grabbed_disc_position =
331 (grabbed_disc_position -
332 ConvertDiscPositionToIndex(kGrabberLength));
333
334 // If close, start grabbing and/or shooting.
335 if (wrist_loop_->X_hat(0, 0) > robust_grabbed_disc_position) {
336 // Start the state machine.
337 if (safe_goal_ == Goal::SHOOT) {
338 loader_goal_ = LoaderGoal::SHOOT_AND_RESET;
339 } else {
340 loader_goal_ = LoaderGoal::GRAB;
341 }
342 // This frisbee is now gone. Take it out of the queue.
343 frisbees_.pop_back();
344 --hopper_disc_count_;
345 }
346 }
347 }
348 }
349
350 printf("READY_SHOOTER or SHOOT\n");
Austin Schuhd78ab542013-03-01 22:22:19 -0800351 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800352 }
353
354 // The only way out of the loader is to shoot the disc. The FSM can only go
355 // forwards.
356 switch (loader_state_) {
357 case LoaderState::READY:
358 printf("Loader READY\n");
359 // Open and down, ready to accept a disc.
360 loader_up_ = false;
361 disc_clamped_ = false;
362 disc_ejected_ = false;
363 if (loader_goal_ == LoaderGoal::GRAB ||
364 loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
365 if (loader_goal_ == LoaderGoal::GRAB) {
366 printf("Told to GRAB, moving on\n");
367 } else {
368 printf("Told to SHOOT_AND_RESET, moving on\n");
369 }
370 loader_state_ = LoaderState::GRABBING;
371 loader_countdown_ = kGrabbingDelay;
372 } else {
373 break;
374 }
375 case LoaderState::GRABBING:
376 printf("Loader GRABBING %d\n", loader_countdown_);
377 // Closing the grabber.
378 loader_up_ = false;
379 disc_clamped_ = true;
380 disc_ejected_ = false;
381 if (loader_countdown_ > 0) {
382 --loader_countdown_;
383 break;
384 } else {
385 loader_state_ = LoaderState::GRABBED;
386 }
387 case LoaderState::GRABBED:
388 printf("Loader GRABBED\n");
389 // Grabber closed.
390 loader_up_ = false;
391 disc_clamped_ = true;
392 disc_ejected_ = false;
393 if (loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
394 // TODO(aschuh): Only shoot if the shooter is up to speed.
395 // Seems like that would have us shooting a bit later than we could be,
396 // but it also probably spins back up real fast.
397 loader_state_ = LoaderState::LIFTING;
398 loader_countdown_ = kLiftingDelay;
399 printf("Told to SHOOT_AND_RESET, moving on\n");
400 } else if (loader_goal_ == LoaderGoal::READY) {
401 LOG(ERROR, "Can't go to ready when we have something grabbed.\n");
402 printf("Can't go to ready when we have something grabbed.\n");
403 break;
404 } else {
405 break;
406 }
407 case LoaderState::LIFTING:
408 printf("Loader LIFTING %d\n", loader_countdown_);
409 // Lifting the disc.
410 loader_up_ = true;
411 disc_clamped_ = true;
412 disc_ejected_ = false;
413 if (loader_countdown_ > 0) {
414 --loader_countdown_;
415 break;
416 } else {
417 loader_state_ = LoaderState::LIFTED;
418 }
419 case LoaderState::LIFTED:
420 printf("Loader LIFTED\n");
421 // Disc lifted. Time to eject it out.
422 loader_up_ = true;
423 disc_clamped_ = true;
424 disc_ejected_ = false;
425 loader_state_ = LoaderState::SHOOTING;
426 loader_countdown_ = kShootingDelay;
427 case LoaderState::SHOOTING:
428 printf("Loader SHOOTING %d\n", loader_countdown_);
429 // Ejecting the disc into the shooter.
430 loader_up_ = true;
431 disc_clamped_ = false;
432 disc_ejected_ = true;
433 if (loader_countdown_ > 0) {
434 --loader_countdown_;
435 break;
436 } else {
437 loader_state_ = LoaderState::SHOOT;
438 }
439 case LoaderState::SHOOT:
440 printf("Loader SHOOT\n");
441 // The disc has been shot.
442 loader_up_ = true;
443 disc_clamped_ = false;
444 disc_ejected_ = true;
445 loader_state_ = LoaderState::LOWERING;
446 loader_countdown_ = kLoweringDelay;
447 case LoaderState::LOWERING:
448 printf("Loader LOWERING %d\n", loader_countdown_);
449 // Lowering the loader back down.
450 loader_up_ = false;
451 disc_clamped_ = false;
452 disc_ejected_ = true;
453 if (loader_countdown_ > 0) {
454 --loader_countdown_;
455 break;
456 } else {
457 loader_state_ = LoaderState::LOWERED;
458 }
459 case LoaderState::LOWERED:
460 printf("Loader LOWERED\n");
461 // The indexer is lowered.
462 loader_up_ = false;
463 disc_clamped_ = false;
464 disc_ejected_ = false;
465 loader_state_ = LoaderState::READY;
466 // Once we have shot, we need to hang out in READY until otherwise
467 // notified.
468 loader_goal_ = LoaderGoal::READY;
Austin Schuhd78ab542013-03-01 22:22:19 -0800469 break;
470 }
471
472 // Update the observer.
473 wrist_loop_->Update(position != NULL, output == NULL);
474
475 if (position) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800476 LOG(DEBUG, "pos=%f\n", position->index_position);
Austin Schuhd78ab542013-03-01 22:22:19 -0800477 last_bottom_disc_detect_ = position->bottom_disc_detect;
478 }
479
480 status->hopper_disc_count = hopper_disc_count_;
481 status->total_disc_count = total_disc_count_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800482 status->preloaded = (loader_state_ != LoaderState::READY);
Austin Schuhd78ab542013-03-01 22:22:19 -0800483
484 if (output) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800485 output->transfer_voltage = transfer_voltage;
Austin Schuhd78ab542013-03-01 22:22:19 -0800486 output->index_voltage = wrist_loop_->U(0, 0);
Austin Schuhf8c52252013-03-03 02:25:49 -0800487 output->loader_up = loader_up_;
488 output->disc_clamped = disc_clamped_;
489 output->disc_ejected = disc_ejected_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800490 }
491
492 if (safe_to_change_state_) {
493 safe_goal_ = goal_enum;
494 }
495}
496
497} // namespace control_loops
498} // namespace frc971