blob: 6d72fe278787fe2e9f2d9ea5ca763db92293be2f [file] [log] [blame]
Austin Schuhd78ab542013-03-01 22:22:19 -08001#include "frc971/control_loops/index.h"
2
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"
14#include "frc971/control_loops/index_motor_plant.h"
15
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) {
186 if (position->bottom_disc_detect && !last_bottom_disc_detect_) {
187 transfer_frisbee_.Reset();
188 transfer_frisbee_.bottom_posedge_time_ = now;
189 printf("Posedge of bottom disc %f\n",
190 transfer_frisbee_.bottom_posedge_time_.ToSeconds());
191 ++hopper_disc_count_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800192 ++total_disc_count_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800193 }
194
195 // Disc exited the beam break now.
196 if (!position->bottom_disc_detect && last_bottom_disc_detect_) {
197 transfer_frisbee_.bottom_negedge_time_ = now;
198 printf("Negedge of bottom disc %f\n",
199 transfer_frisbee_.bottom_negedge_time_.ToSeconds());
200 frisbees_.push_front(transfer_frisbee_);
201 }
202
203 if (position->bottom_disc_detect) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800204 transfer_voltage = 12.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800205 // Must wait until the disc gets out before we can change state.
206 safe_to_change_state_ = false;
207
Austin Schuhf8c52252013-03-03 02:25:49 -0800208 // TODO(aschuh): A disc on the way through needs to start moving
209 // the indexer if it isn't already moving. Maybe?
Austin Schuhd78ab542013-03-01 22:22:19 -0800210
211 Time elapsed_posedge_time = now -
212 transfer_frisbee_.bottom_posedge_time_;
213 if (elapsed_posedge_time >= Time::InSeconds(0.3)) {
214 // It has been too long. The disc must be jammed.
215 LOG(ERROR, "Been way too long. Jammed disc?\n");
216 printf("Been way too long. Jammed disc?\n");
217 }
218 }
219
Austin Schuhf8c52252013-03-03 02:25:49 -0800220 // Check all non-indexed discs and see if they should be indexed.
Austin Schuhd78ab542013-03-01 22:22:19 -0800221 for (Frisbee &frisbee : frisbees_) {
222 if (!frisbee.has_been_indexed_) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800223 transfer_voltage = 12.0;
224 Time elapsed_negedge_time = now -
225 frisbee.bottom_negedge_time_;
226 if (elapsed_negedge_time >= Time::InSeconds(0.005)) {
Austin Schuhd78ab542013-03-01 22:22:19 -0800227 // Should have just engaged.
228 // Save the indexer position, and the time.
229
230 // It has been long enough since the disc entered the indexer.
231 // Treat now as the time at which it contacted the indexer.
232 LOG(INFO, "Grabbed on the index now at %f\n", index_position);
233 printf("Grabbed on the index now at %f\n", index_position);
234 frisbee.has_been_indexed_ = true;
235 frisbee.index_start_position_ = index_position;
Austin Schuhd78ab542013-03-01 22:22:19 -0800236 }
237 }
238 if (!frisbee.has_been_indexed_) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800239 // All discs must be indexed before it is safe to stop indexing.
Austin Schuhd78ab542013-03-01 22:22:19 -0800240 safe_to_change_state_ = false;
241 }
242 }
243
Austin Schuhf8c52252013-03-03 02:25:49 -0800244 // Figure out where the indexer should be to move the discs down to
245 // the right position.
246 double max_disc_position;
247 if (MaxDiscPosition(&max_disc_position)) {
248 printf("There is a disc down here!\n");
249 // TODO(aschuh): Figure out what to do if grabbing the next one
250 // would cause things to jam into the loader.
251 // Say we aren't ready any more. Undefined behavior will result if
252 // that isn't observed.
253 double bottom_disc_position =
254 max_disc_position + ConvertDiscAngleToIndex(M_PI);
255 wrist_loop_->R << bottom_disc_position, 0.0;
Austin Schuhd78ab542013-03-01 22:22:19 -0800256
Austin Schuhf8c52252013-03-03 02:25:49 -0800257 // Verify that we are close enough to the goal so that we should be
258 // fine accepting the next disc.
259 double disc_error_meters = ConvertIndexToDiscPosition(
260 wrist_loop_->X_hat(0, 0) - bottom_disc_position);
261 // We are ready for the next disc if the first one is in the first
262 // half circle of the indexer. It will take time for the disc to
263 // come into the indexer, so we will be able to move it out of the
264 // way in time.
265 // This choice also makes sure that we don't claim that we aren't
266 // ready between full speed intaking.
267 if (-ConvertDiscAngleToIndex(M_PI) < disc_error_meters &&
268 disc_error_meters < 0.04) {
269 // We are only ready if we aren't being asked to change state or
270 // are full.
271 status->ready_to_intake =
272 (safe_goal_ == goal_enum) && hopper_disc_count_ < 4;
273 } else {
274 status->ready_to_intake = false;
Austin Schuhd78ab542013-03-01 22:22:19 -0800275 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800276 } else {
277 // No discs! We are always ready for more if we aren't being
278 // asked to change state.
279 status->ready_to_intake = (safe_goal_ == goal_enum);
Austin Schuhd78ab542013-03-01 22:22:19 -0800280 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800281
282 // Turn on the transfer roller if we are ready.
283 if (status->ready_to_intake && hopper_disc_count_ < 4 &&
284 safe_goal_ == Goal::INTAKE) {
285 transfer_voltage = 12.0;
286 }
Austin Schuhd78ab542013-03-01 22:22:19 -0800287 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800288 printf("INTAKE\n");
Austin Schuhd78ab542013-03-01 22:22:19 -0800289 }
290 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800291 case Goal::READY_SHOOTER:
292 case Goal::SHOOT:
293 // Check if we have any discs to shoot or load and handle them.
294 double min_disc_position;
295 if (MinDiscPosition(&min_disc_position)) {
296 const double ready_disc_position =
297 min_disc_position + ConvertDiscPositionToIndex(kIndexFreeLength) -
298 ConvertDiscAngleToIndex(M_PI / 6.0);
299
300 const double grabbed_disc_position =
301 min_disc_position +
302 ConvertDiscPositionToIndex(kReadyToLiftPosition -
303 kIndexStartPosition + 0.03);
304
305 // Check the state of the loader FSM.
306 // If it is ready to load discs, position the disc so that it is ready
307 // to be grabbed.
308 // If it isn't ready, there is a disc in there. It needs to finish it's
309 // cycle first.
310 if (loader_state_ != LoaderState::READY) {
311 // We already have a disc in the loader.
312 // Stage the discs back a bit.
313 wrist_loop_->R << ready_disc_position, 0.0;
314
315 // Must wait until it has been grabbed to continue.
316 if (loader_state_ == LoaderState::GRABBING) {
317 safe_to_change_state_ = false;
318 }
319 } else {
320 // No disc up top right now.
321 wrist_loop_->R << grabbed_disc_position, 0.0;
322
323 // See if the disc has gotten pretty far up yet.
324 if (wrist_loop_->X_hat(0, 0) > ready_disc_position) {
325 // Point of no return. We are committing to grabbing it now.
326 safe_to_change_state_ = false;
327 const double robust_grabbed_disc_position =
328 (grabbed_disc_position -
329 ConvertDiscPositionToIndex(kGrabberLength));
330
331 // If close, start grabbing and/or shooting.
332 if (wrist_loop_->X_hat(0, 0) > robust_grabbed_disc_position) {
333 // Start the state machine.
334 if (safe_goal_ == Goal::SHOOT) {
335 loader_goal_ = LoaderGoal::SHOOT_AND_RESET;
336 } else {
337 loader_goal_ = LoaderGoal::GRAB;
338 }
339 // This frisbee is now gone. Take it out of the queue.
340 frisbees_.pop_back();
341 --hopper_disc_count_;
342 }
343 }
344 }
345 }
346
347 printf("READY_SHOOTER or SHOOT\n");
Austin Schuhd78ab542013-03-01 22:22:19 -0800348 break;
Austin Schuhf8c52252013-03-03 02:25:49 -0800349 }
350
351 // The only way out of the loader is to shoot the disc. The FSM can only go
352 // forwards.
353 switch (loader_state_) {
354 case LoaderState::READY:
355 printf("Loader READY\n");
356 // Open and down, ready to accept a disc.
357 loader_up_ = false;
358 disc_clamped_ = false;
359 disc_ejected_ = false;
360 if (loader_goal_ == LoaderGoal::GRAB ||
361 loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
362 if (loader_goal_ == LoaderGoal::GRAB) {
363 printf("Told to GRAB, moving on\n");
364 } else {
365 printf("Told to SHOOT_AND_RESET, moving on\n");
366 }
367 loader_state_ = LoaderState::GRABBING;
368 loader_countdown_ = kGrabbingDelay;
369 } else {
370 break;
371 }
372 case LoaderState::GRABBING:
373 printf("Loader GRABBING %d\n", loader_countdown_);
374 // Closing the grabber.
375 loader_up_ = false;
376 disc_clamped_ = true;
377 disc_ejected_ = false;
378 if (loader_countdown_ > 0) {
379 --loader_countdown_;
380 break;
381 } else {
382 loader_state_ = LoaderState::GRABBED;
383 }
384 case LoaderState::GRABBED:
385 printf("Loader GRABBED\n");
386 // Grabber closed.
387 loader_up_ = false;
388 disc_clamped_ = true;
389 disc_ejected_ = false;
390 if (loader_goal_ == LoaderGoal::SHOOT_AND_RESET) {
391 // TODO(aschuh): Only shoot if the shooter is up to speed.
392 // Seems like that would have us shooting a bit later than we could be,
393 // but it also probably spins back up real fast.
394 loader_state_ = LoaderState::LIFTING;
395 loader_countdown_ = kLiftingDelay;
396 printf("Told to SHOOT_AND_RESET, moving on\n");
397 } else if (loader_goal_ == LoaderGoal::READY) {
398 LOG(ERROR, "Can't go to ready when we have something grabbed.\n");
399 printf("Can't go to ready when we have something grabbed.\n");
400 break;
401 } else {
402 break;
403 }
404 case LoaderState::LIFTING:
405 printf("Loader LIFTING %d\n", loader_countdown_);
406 // Lifting the disc.
407 loader_up_ = true;
408 disc_clamped_ = true;
409 disc_ejected_ = false;
410 if (loader_countdown_ > 0) {
411 --loader_countdown_;
412 break;
413 } else {
414 loader_state_ = LoaderState::LIFTED;
415 }
416 case LoaderState::LIFTED:
417 printf("Loader LIFTED\n");
418 // Disc lifted. Time to eject it out.
419 loader_up_ = true;
420 disc_clamped_ = true;
421 disc_ejected_ = false;
422 loader_state_ = LoaderState::SHOOTING;
423 loader_countdown_ = kShootingDelay;
424 case LoaderState::SHOOTING:
425 printf("Loader SHOOTING %d\n", loader_countdown_);
426 // Ejecting the disc into the shooter.
427 loader_up_ = true;
428 disc_clamped_ = false;
429 disc_ejected_ = true;
430 if (loader_countdown_ > 0) {
431 --loader_countdown_;
432 break;
433 } else {
434 loader_state_ = LoaderState::SHOOT;
435 }
436 case LoaderState::SHOOT:
437 printf("Loader SHOOT\n");
438 // The disc has been shot.
439 loader_up_ = true;
440 disc_clamped_ = false;
441 disc_ejected_ = true;
442 loader_state_ = LoaderState::LOWERING;
443 loader_countdown_ = kLoweringDelay;
444 case LoaderState::LOWERING:
445 printf("Loader LOWERING %d\n", loader_countdown_);
446 // Lowering the loader back down.
447 loader_up_ = false;
448 disc_clamped_ = false;
449 disc_ejected_ = true;
450 if (loader_countdown_ > 0) {
451 --loader_countdown_;
452 break;
453 } else {
454 loader_state_ = LoaderState::LOWERED;
455 }
456 case LoaderState::LOWERED:
457 printf("Loader LOWERED\n");
458 // The indexer is lowered.
459 loader_up_ = false;
460 disc_clamped_ = false;
461 disc_ejected_ = false;
462 loader_state_ = LoaderState::READY;
463 // Once we have shot, we need to hang out in READY until otherwise
464 // notified.
465 loader_goal_ = LoaderGoal::READY;
Austin Schuhd78ab542013-03-01 22:22:19 -0800466 break;
467 }
468
469 // Update the observer.
470 wrist_loop_->Update(position != NULL, output == NULL);
471
472 if (position) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800473 LOG(DEBUG, "pos=%f\n", position->index_position);
Austin Schuhd78ab542013-03-01 22:22:19 -0800474 last_bottom_disc_detect_ = position->bottom_disc_detect;
475 }
476
477 status->hopper_disc_count = hopper_disc_count_;
478 status->total_disc_count = total_disc_count_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800479 status->preloaded = (loader_state_ != LoaderState::READY);
Austin Schuhd78ab542013-03-01 22:22:19 -0800480
481 if (output) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800482 output->transfer_voltage = transfer_voltage;
Austin Schuhd78ab542013-03-01 22:22:19 -0800483 output->index_voltage = wrist_loop_->U(0, 0);
Austin Schuhf8c52252013-03-03 02:25:49 -0800484 output->loader_up = loader_up_;
485 output->disc_clamped = disc_clamped_;
486 output->disc_ejected = disc_ejected_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800487 }
488
489 if (safe_to_change_state_) {
490 safe_goal_ = goal_enum;
491 }
492}
493
494} // namespace control_loops
495} // namespace frc971