blob: e991bb16844c75462bc400c7a925aeb154477bcd [file] [log] [blame]
Austin Schuhd78ab542013-03-01 22:22:19 -08001#include <unistd.h>
2
3#include <memory>
4
5#include "gtest/gtest.h"
6#include "aos/common/queue.h"
7#include "aos/common/queue_testutils.h"
8#include "frc971/control_loops/index_motor.q.h"
9#include "frc971/control_loops/index.h"
10#include "frc971/control_loops/index_motor_plant.h"
11#include "frc971/control_loops/transfer_motor_plant.h"
12#include "frc971/constants.h"
13
14
15using ::aos::time::Time;
16
17namespace frc971 {
18namespace control_loops {
19namespace testing {
20
Austin Schuhd78ab542013-03-01 22:22:19 -080021class Frisbee {
22 public:
23 // Creates a frisbee starting at the specified position in the frisbee path,
24 // and with the transfer and index rollers at the specified positions.
25 Frisbee(double transfer_roller_position,
26 double index_roller_position,
Austin Schuhf8c52252013-03-03 02:25:49 -080027 double position = IndexMotor::kBottomDiscDetectStart)
Austin Schuhd78ab542013-03-01 22:22:19 -080028 : transfer_roller_position_(transfer_roller_position),
29 index_roller_position_(index_roller_position),
Austin Schuhf8c52252013-03-03 02:25:49 -080030 position_(position),
31 has_been_shot_(false) {
Austin Schuhd78ab542013-03-01 22:22:19 -080032 }
33
34 // Returns true if the frisbee is controlled by the transfer roller.
35 bool IsTouchingTransfer() const {
Austin Schuhf8c52252013-03-03 02:25:49 -080036 return (position_ >= IndexMotor::kBottomDiscDetectStart &&
37 position_ <= IndexMotor::kIndexStartPosition);
38 }
39
40 // Returns true if the frisbee is in a place where it is unsafe to grab.
41 bool IsUnsafeToGrab() const {
42 return (position_ > (IndexMotor::kLoaderFreeStopPosition) &&
43 position_ < IndexMotor::kGrabberStartPosition);
Austin Schuhd78ab542013-03-01 22:22:19 -080044 }
45
46 // Returns true if the frisbee is controlled by the indexing roller.
47 bool IsTouchingIndex() const {
Austin Schuhf8c52252013-03-03 02:25:49 -080048 return (position_ >= IndexMotor::kIndexStartPosition &&
49 position_ < IndexMotor::kGrabberStartPosition);
50 }
51
52 // Returns true if the frisbee is in a position such that the disc can be
53 // lifted.
54 bool IsUnsafeToLift() const {
55 return (position_ >= IndexMotor::kLoaderFreeStopPosition &&
56 position_ <= IndexMotor::kReadyToLiftPosition);
Austin Schuhd78ab542013-03-01 22:22:19 -080057 }
58
59 // Returns true if the frisbee is in a position such that the grabber will
60 // pull it into the loader.
61 bool IsTouchingGrabber() const {
Austin Schuhf8c52252013-03-03 02:25:49 -080062 return (position_ >= IndexMotor::kGrabberStartPosition &&
63 position_ < IndexMotor::kReadyToLiftPosition);
64 }
65
66 // Returns true if the frisbee is in a position such that the disc can be
67 // lifted.
68 bool IsTouchingLoader() const {
69 return (position_ >= IndexMotor::kReadyToLiftPosition &&
70 position_ < IndexMotor::kLifterStopPosition);
71 }
72
73 // Returns true if the frisbee is touching the ejector.
74 bool IsTouchingEjector() const {
75 return (position_ >= IndexMotor::kLifterStopPosition &&
76 position_ < IndexMotor::kEjectorStopPosition);
Austin Schuhd78ab542013-03-01 22:22:19 -080077 }
78
79 // Returns true if the disc is triggering the bottom disc detect sensor.
80 bool bottom_disc_detect() const {
Austin Schuhf8c52252013-03-03 02:25:49 -080081 return (position_ >= IndexMotor::kBottomDiscDetectStart &&
82 position_ <= IndexMotor::kBottomDiscDetectStop);
Austin Schuhd78ab542013-03-01 22:22:19 -080083 }
84
85 // Returns true if the disc is triggering the top disc detect sensor.
86 bool top_disc_detect() const {
Austin Schuhf8c52252013-03-03 02:25:49 -080087 return (position_ >= IndexMotor::kTopDiscDetectStart &&
88 position_ <= IndexMotor::kTopDiscDetectStop);
Austin Schuhd78ab542013-03-01 22:22:19 -080089 }
90
91 // Updates the position of the frisbee in the frisbee path.
92 void UpdatePosition(double transfer_roller_position,
93 double index_roller_position,
Austin Schuhf8c52252013-03-03 02:25:49 -080094 bool clamped,
95 bool lifted,
96 bool ejected) {
97 if (IsTouchingTransfer() || position() < 0.0) {
98 position_ += IndexMotor::ConvertTransferToDiscPosition(
99 transfer_roller_position - transfer_roller_position_);
100 printf("Transfer Roller: ");
Austin Schuhd78ab542013-03-01 22:22:19 -0800101 } else if (IsTouchingIndex()) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800102 position_ += ::std::min(
103 IndexMotor::ConvertIndexToDiscPosition(
104 index_roller_position - index_roller_position_),
105 IndexMotor::kGrabberStartPosition);
106 // Verify that we aren't trying to grab or lift when it isn't safe.
107 EXPECT_FALSE(clamped && IsUnsafeToGrab());
108 EXPECT_FALSE(lifted && IsUnsafeToLift());
109 printf("Index: ");
Austin Schuhd78ab542013-03-01 22:22:19 -0800110 } else if (IsTouchingGrabber()) {
111 if (clamped) {
Austin Schuhf8c52252013-03-03 02:25:49 -0800112 const double grabber_dx = IndexMotor::kGrabberMovementVelocity / 100.0;
113 position_ = ::std::min(position_ + grabber_dx,
114 IndexMotor::kReadyToLiftPosition);
Austin Schuhd78ab542013-03-01 22:22:19 -0800115 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800116 EXPECT_FALSE(lifted);
117 EXPECT_FALSE(ejected);
118 printf("Grabber: ");
119 } else if (IsTouchingLoader()) {
120 if (lifted) {
121 const double lifter_dx = IndexMotor::kLifterMovementVelocity / 100.0;
122 position_ = ::std::min(position_ + lifter_dx,
123 IndexMotor::kLifterStopPosition);
124 }
125 EXPECT_TRUE(clamped);
126 EXPECT_FALSE(ejected);
127 printf("Loader: ");
128 } else if (IsTouchingEjector()) {
129 EXPECT_TRUE(lifted);
130 if (ejected) {
131 const double ejector_dx = IndexMotor::kEjectorMovementVelocity / 100.0;
132 position_ = ::std::min(position_ + ejector_dx,
133 IndexMotor::kEjectorStopPosition);
134 EXPECT_FALSE(clamped);
135 }
136 printf("Ejector: ");
137 } else if (position_ == IndexMotor::kEjectorStopPosition) {
138 printf("Shot: ");
139 has_been_shot_ = true;
Austin Schuhd78ab542013-03-01 22:22:19 -0800140 }
141 transfer_roller_position_ = transfer_roller_position;
142 index_roller_position_ = index_roller_position;
Austin Schuhd78ab542013-03-01 22:22:19 -0800143 printf("Disc is at %f\n", position_);
144 }
145
Austin Schuhf8c52252013-03-03 02:25:49 -0800146 // Returns if the disc has been shot and can be removed from the robot.
147 bool has_been_shot() const {
148 return has_been_shot_;
149 }
150
151 // Returns the position of the disc in the system.
Austin Schuhd78ab542013-03-01 22:22:19 -0800152 double position() const {
153 return position_;
154 }
155
156 private:
Austin Schuhf8c52252013-03-03 02:25:49 -0800157 // Previous transfer roller position for computing deltas.
Austin Schuhd78ab542013-03-01 22:22:19 -0800158 double transfer_roller_position_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800159 // Previous index roller position for computing deltas.
Austin Schuhd78ab542013-03-01 22:22:19 -0800160 double index_roller_position_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800161 // Position in the robot.
Austin Schuhd78ab542013-03-01 22:22:19 -0800162 double position_;
Austin Schuhf8c52252013-03-03 02:25:49 -0800163 // True if the disc has been shot.
164 bool has_been_shot_;
Austin Schuhd78ab542013-03-01 22:22:19 -0800165};
166
167
168// Class which simulates the index and sends out queue messages containing the
169// position.
170class IndexMotorSimulation {
171 public:
172 // Constructs a motor simulation. initial_position is the inital angle of the
173 // index, which will be treated as 0 by the encoder.
174 IndexMotorSimulation()
175 : index_plant_(new StateFeedbackPlant<2, 1, 1>(MakeIndexPlant())),
176 transfer_plant_(new StateFeedbackPlant<2, 1, 1>(MakeTransferPlant())),
177 my_index_loop_(".frc971.control_loops.index",
178 0x1a7b7094, ".frc971.control_loops.index.goal",
179 ".frc971.control_loops.index.position",
180 ".frc971.control_loops.index.output",
181 ".frc971.control_loops.index.status") {
182 }
183
184 // Starts a disc at the start of the index.
185 void InsertDisc() {
186 frisbees.push_back(Frisbee(transfer_roller_position(),
187 index_roller_position()));
188 }
189
190 // Returns true if the bottom disc sensor is triggered.
191 bool BottomDiscDetect() const {
192 bool bottom_disc_detect = false;
193 for (const Frisbee &frisbee : frisbees) {
194 bottom_disc_detect |= frisbee.bottom_disc_detect();
195 }
196 return bottom_disc_detect;
197 }
198
199 // Returns true if the top disc sensor is triggered.
200 bool TopDiscDetect() const {
201 bool top_disc_detect = false;
202 for (const Frisbee &frisbee : frisbees) {
203 top_disc_detect |= frisbee.top_disc_detect();
204 }
205 return top_disc_detect;
206 }
207
Austin Schuhf8c52252013-03-03 02:25:49 -0800208 // Updates all discs, and verifies that the state of the system is sane.
209 void UpdateDiscs(bool clamped, bool lifted, bool ejected) {
Austin Schuhd78ab542013-03-01 22:22:19 -0800210 for (Frisbee &frisbee : frisbees) {
Austin Schuhd78ab542013-03-01 22:22:19 -0800211 frisbee.UpdatePosition(transfer_roller_position(),
212 index_roller_position(),
Austin Schuhf8c52252013-03-03 02:25:49 -0800213 clamped,
214 lifted,
215 ejected);
216 }
217
218 // Make sure nobody is too close to anybody else.
219 Frisbee *last_frisbee = NULL;
220 for (Frisbee &frisbee : frisbees) {
221 if (last_frisbee) {
222 const double distance = frisbee.position() - last_frisbee->position();
223 double min_distance;
224 if (frisbee.IsTouchingTransfer() ||
225 last_frisbee->IsTouchingTransfer()) {
226 min_distance = 0.3;
227 } else {
228 min_distance =
229 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI * 2.0 / 3.0);
230 }
231
232 EXPECT_LT(min_distance, ::std::abs(distance)) << "Discs too close";
233 }
234 last_frisbee = &frisbee;
235 }
236
237 // Remove any shot frisbees.
238 for (int i = 0; i < static_cast<int>(frisbees.size()); ++i) {
239 if (frisbees[i].has_been_shot()) {
240 shot_frisbees.push_back(frisbees[i]);
241 frisbees.erase(frisbees.begin() + i);
242 --i;
243 }
Austin Schuhd78ab542013-03-01 22:22:19 -0800244 }
245 }
246
247 // Sends out the position queue messages.
248 void SendPositionMessage() {
249 ::aos::ScopedMessagePtr<control_loops::IndexLoop::Position> position =
250 my_index_loop_.position.MakeMessage();
251 position->index_position = index_roller_position();
252 position->bottom_disc_detect = BottomDiscDetect();
253 position->top_disc_detect = TopDiscDetect();
254 printf("bdd: %x tdd: %x\n", position->bottom_disc_detect,
255 position->top_disc_detect);
256 position.Send();
257 }
258
259 // Simulates the index moving for one timestep.
260 void Simulate() {
261 EXPECT_TRUE(my_index_loop_.output.FetchLatest());
262
263 index_plant_->U << my_index_loop_.output->index_voltage;
264 index_plant_->Update();
265
266 transfer_plant_->U << my_index_loop_.output->transfer_voltage;
267 transfer_plant_->Update();
268 printf("tv: %f iv: %f tp : %f ip: %f\n",
269 my_index_loop_.output->transfer_voltage,
270 my_index_loop_.output->index_voltage,
271 transfer_roller_position(), index_roller_position());
272
Austin Schuhf8c52252013-03-03 02:25:49 -0800273 UpdateDiscs(my_index_loop_.output->disc_clamped,
274 my_index_loop_.output->loader_up,
275 my_index_loop_.output->disc_ejected);
Austin Schuhd78ab542013-03-01 22:22:19 -0800276 }
277
Austin Schuhf8c52252013-03-03 02:25:49 -0800278 // Plants for the index and transfer rollers.
Austin Schuhd78ab542013-03-01 22:22:19 -0800279 ::std::unique_ptr<StateFeedbackPlant<2, 1, 1>> index_plant_;
280 ::std::unique_ptr<StateFeedbackPlant<2, 1, 1>> transfer_plant_;
281
282 // Returns the absolute angle of the index.
283 double index_roller_position() const {
284 return index_plant_->Y(0, 0);
285 }
286
287 // Returns the absolute angle of the index.
288 double transfer_roller_position() const {
289 return transfer_plant_->Y(0, 0);
290 }
291
Austin Schuhf8c52252013-03-03 02:25:49 -0800292 // Frisbees being tracked in the robot.
Austin Schuhd78ab542013-03-01 22:22:19 -0800293 ::std::vector<Frisbee> frisbees;
Austin Schuhf8c52252013-03-03 02:25:49 -0800294 // Frisbees that have been shot.
295 ::std::vector<Frisbee> shot_frisbees;
Austin Schuhd78ab542013-03-01 22:22:19 -0800296
297 private:
Austin Schuhf8c52252013-03-03 02:25:49 -0800298 // Control loop for the indexer.
Austin Schuhd78ab542013-03-01 22:22:19 -0800299 IndexLoop my_index_loop_;
300};
301
302
303class IndexTest : public ::testing::Test {
304 protected:
305 IndexTest() : my_index_loop_(".frc971.control_loops.index",
306 0x1a7b7094, ".frc971.control_loops.index.goal",
307 ".frc971.control_loops.index.position",
308 ".frc971.control_loops.index.output",
309 ".frc971.control_loops.index.status"),
310 index_motor_(&my_index_loop_),
311 index_motor_plant_(),
312 loop_count_(0) {
313 // Flush the robot state queue so we can use clean shared memory for this
314 // test.
315 ::aos::robot_state.Clear();
316 SendDSPacket(true);
317 Time::EnableMockTime(Time(0, 0));
318 }
319
320 virtual ~IndexTest() {
321 ::aos::robot_state.Clear();
322 Time::DisableMockTime();
323 }
324
325 // Sends a DS packet with the enable bit set to enabled.
326 void SendDSPacket(bool enabled) {
327 ::aos::robot_state.MakeWithBuilder().enabled(enabled)
328 .autonomous(false)
329 .team_id(971).Send();
330 ::aos::robot_state.FetchLatest();
331 }
332
333 // Updates the current mock time.
334 void UpdateTime() {
335 loop_count_ += 1;
336 Time::SetMockTime(Time::InMS(10 * loop_count_));
337 }
338
Austin Schuhf8c52252013-03-03 02:25:49 -0800339 // Loads n discs into the indexer at the bottom.
340 void LoadNDiscs(int n) {
341 my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
342 // Spin it up.
343 for (int i = 0; i < 100; ++i) {
344 index_motor_plant_.SendPositionMessage();
345 index_motor_.Iterate();
346 index_motor_plant_.Simulate();
347 SendDSPacket(true);
348 UpdateTime();
349 }
350
351 EXPECT_EQ(0, index_motor_plant_.index_roller_position());
352 my_index_loop_.status.FetchLatest();
353 EXPECT_TRUE(my_index_loop_.status->ready_to_intake);
354
355 // Stuff N discs in, waiting between each one for a tiny bit of time so they
356 // don't get too close.
357 int num_grabbed = 0;
358 int wait_counter = 0;
359 while (num_grabbed < n) {
360 index_motor_plant_.SendPositionMessage();
361 index_motor_.Iterate();
362 if (!index_motor_plant_.BottomDiscDetect()) {
363 if (wait_counter > 0) {
364 --wait_counter;
365 } else {
366 index_motor_plant_.InsertDisc();
367 ++num_grabbed;
368 wait_counter = 3;
369 }
370 }
371 index_motor_plant_.Simulate();
372 SendDSPacket(true);
373 UpdateTime();
374 }
375
376 // Settle.
377 for (int i = 0; i < 100; ++i) {
378 index_motor_plant_.SendPositionMessage();
379 index_motor_.Iterate();
380 index_motor_plant_.Simulate();
381 SendDSPacket(true);
382 UpdateTime();
383 }
384 }
385
386 // Copy of core that works in this process only.
Austin Schuhd78ab542013-03-01 22:22:19 -0800387 ::aos::common::testing::GlobalCoreInstance my_core;
388
389 // Create a new instance of the test queue so that it invalidates the queue
390 // that it points to. Otherwise, we will have a pointer to shared memory that
391 // is no longer valid.
392 IndexLoop my_index_loop_;
393
394 // Create a loop and simulation plant.
395 IndexMotor index_motor_;
396 IndexMotorSimulation index_motor_plant_;
397
Austin Schuhf8c52252013-03-03 02:25:49 -0800398 // Number of loop cycles that have been executed for tracking the current
399 // time.
Austin Schuhd78ab542013-03-01 22:22:19 -0800400 int loop_count_;
401};
402
403// Tests that the index grabs 1 disc and places it at the correct position.
404TEST_F(IndexTest, GrabSingleDisc) {
405 my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
406 for (int i = 0; i < 250; ++i) {
407 index_motor_plant_.SendPositionMessage();
408 index_motor_.Iterate();
409 if (i == 100) {
410 EXPECT_EQ(0, index_motor_plant_.index_roller_position());
411 index_motor_plant_.InsertDisc();
412 }
Austin Schuhf8c52252013-03-03 02:25:49 -0800413 if (i > 0) {
414 EXPECT_TRUE(my_index_loop_.status.FetchLatest());
415 EXPECT_TRUE(my_index_loop_.status->ready_to_intake);
416 }
Austin Schuhd78ab542013-03-01 22:22:19 -0800417 index_motor_plant_.Simulate();
418 SendDSPacket(true);
419 UpdateTime();
420 }
421
Austin Schuhf8c52252013-03-03 02:25:49 -0800422 my_index_loop_.status.FetchLatest();
Austin Schuhd78ab542013-03-01 22:22:19 -0800423 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
424 EXPECT_EQ(static_cast<size_t>(1), index_motor_plant_.frisbees.size());
425 EXPECT_NEAR(
Austin Schuhf8c52252013-03-03 02:25:49 -0800426 IndexMotor::kIndexStartPosition + IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
427 index_motor_plant_.frisbees[0].position(), 0.05);
Austin Schuhd78ab542013-03-01 22:22:19 -0800428}
429
Austin Schuhf8c52252013-03-03 02:25:49 -0800430// Tests that the index grabs 1 disc and places it at the correct position when
431// told to hold immediately after the disc starts into the bot.
432TEST_F(IndexTest, GrabAndHold) {
433 my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
434 for (int i = 0; i < 200; ++i) {
435 index_motor_plant_.SendPositionMessage();
436 index_motor_.Iterate();
437 if (i == 100) {
438 EXPECT_EQ(0, index_motor_plant_.index_roller_position());
439 index_motor_plant_.InsertDisc();
440 } else if (i == 102) {
441 // The disc has been seen. Tell the indexer to now hold.
442 my_index_loop_.goal.MakeWithBuilder().goal_state(0).Send();
443 } else if (i > 102) {
444 my_index_loop_.status.FetchLatest();
445 EXPECT_FALSE(my_index_loop_.status->ready_to_intake);
446 }
447 index_motor_plant_.Simulate();
448 SendDSPacket(true);
449 UpdateTime();
450 }
451
452 my_index_loop_.status.FetchLatest();
453 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
454 EXPECT_EQ(static_cast<size_t>(1), index_motor_plant_.frisbees.size());
455 EXPECT_NEAR(
456 (IndexMotor::kIndexStartPosition +
457 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
458 index_motor_plant_.frisbees[0].position(), 0.04);
459}
460
461// Tests that the index grabs two discs and places them at the correct
462// positions.
463TEST_F(IndexTest, GrabTwoDiscs) {
464 LoadNDiscs(2);
465
466 EXPECT_TRUE(my_index_loop_.status.FetchLatest());
467 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 2);
468 EXPECT_EQ(static_cast<size_t>(2), index_motor_plant_.frisbees.size());
469 EXPECT_NEAR(
470 (IndexMotor::kIndexStartPosition +
471 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
472 index_motor_plant_.frisbees[1].position(), 0.10);
473 EXPECT_NEAR(
474 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
475 (index_motor_plant_.frisbees[0].position() -
476 index_motor_plant_.frisbees[1].position()), 0.10);
477}
478
479// Tests that the index grabs 2 discs, and loads one up into the loader to get
480// ready to shoot. It then pulls the second disc back down to be ready to
481// intake more.
482TEST_F(IndexTest, ReadyGrabsOneDisc) {
483 LoadNDiscs(2);
484
485 // Lift the discs up to the top. Wait a while to let the system settle and
486 // verify that they don't collide.
487 my_index_loop_.goal.MakeWithBuilder().goal_state(3).Send();
488 for (int i = 0; i < 300; ++i) {
489 index_motor_plant_.SendPositionMessage();
490 index_motor_.Iterate();
491 index_motor_plant_.Simulate();
492 SendDSPacket(true);
493 UpdateTime();
494 }
495
496 // Verify that the disc has been grabbed.
497 my_index_loop_.output.FetchLatest();
498 EXPECT_TRUE(my_index_loop_.output->disc_clamped);
499 // And that we are preloaded.
500 my_index_loop_.status.FetchLatest();
501 EXPECT_TRUE(my_index_loop_.status->preloaded);
502
503 // Pull the disc back down and verify that the transfer roller doesn't turn on
504 // until we are ready.
505 my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
506 for (int i = 0; i < 100; ++i) {
507 index_motor_plant_.SendPositionMessage();
508 index_motor_.Iterate();
509
510 EXPECT_TRUE(my_index_loop_.status.FetchLatest());
511 EXPECT_TRUE(my_index_loop_.output.FetchLatest());
512 if (!my_index_loop_.status->ready_to_intake) {
513 EXPECT_EQ(my_index_loop_.output->transfer_voltage, 0)
514 << "Transfer should be off until indexer is ready";
515 }
516
517 index_motor_plant_.Simulate();
518 SendDSPacket(true);
519 UpdateTime();
520 }
521
522 my_index_loop_.status.FetchLatest();
523 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
524 EXPECT_EQ(my_index_loop_.status->total_disc_count, 2);
525 my_index_loop_.output.FetchLatest();
526 EXPECT_TRUE(my_index_loop_.output->disc_clamped);
527
528 EXPECT_EQ(static_cast<size_t>(2), index_motor_plant_.frisbees.size());
529 EXPECT_NEAR(IndexMotor::kReadyToLiftPosition,
530 index_motor_plant_.frisbees[0].position(), 0.01);
531 EXPECT_NEAR(
532 (IndexMotor::kIndexStartPosition +
533 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
534 index_motor_plant_.frisbees[1].position(), 0.10);
535}
536
537// Tests that the index grabs 1 disc and continues to pull it in correctly when
538// in the READY_LOWER state. The transfer roller should be disabled then.
539TEST_F(IndexTest, GrabAndReady) {
540 my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
541 for (int i = 0; i < 200; ++i) {
542 index_motor_plant_.SendPositionMessage();
543 index_motor_.Iterate();
544 if (i == 100) {
545 EXPECT_EQ(0, index_motor_plant_.index_roller_position());
546 index_motor_plant_.InsertDisc();
547 } else if (i == 102) {
548 my_index_loop_.goal.MakeWithBuilder().goal_state(1).Send();
549 } else if (i > 150) {
550 my_index_loop_.status.FetchLatest();
551 EXPECT_TRUE(my_index_loop_.status->ready_to_intake);
552 my_index_loop_.output.FetchLatest();
553 EXPECT_EQ(my_index_loop_.output->transfer_voltage, 0.0);
554 }
555 index_motor_plant_.Simulate();
556 SendDSPacket(true);
557 UpdateTime();
558 }
559
560 my_index_loop_.status.FetchLatest();
561 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
562 EXPECT_EQ(static_cast<size_t>(1), index_motor_plant_.frisbees.size());
563 EXPECT_NEAR(
564 (IndexMotor::kIndexStartPosition +
565 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
566 index_motor_plant_.frisbees[0].position(), 0.04);
567}
568
569// Tests that grabbing 4 discs ends up with 4 discs in the bot and us no longer
570// ready.
571TEST_F(IndexTest, GrabFourDiscs) {
572 LoadNDiscs(4);
573
574 EXPECT_TRUE(my_index_loop_.output.FetchLatest());
575 EXPECT_EQ(my_index_loop_.output->transfer_voltage, 0.0);
576 EXPECT_TRUE(my_index_loop_.status.FetchLatest());
577 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 4);
578 EXPECT_FALSE(my_index_loop_.status->ready_to_intake);
579 EXPECT_EQ(static_cast<size_t>(4), index_motor_plant_.frisbees.size());
580 EXPECT_NEAR(
581 IndexMotor::kIndexStartPosition + IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
582 index_motor_plant_.frisbees[3].position(), 0.10);
583 EXPECT_NEAR(
584 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
585 (index_motor_plant_.frisbees[0].position() -
586 index_motor_plant_.frisbees[1].position()), 0.10);
587 EXPECT_NEAR(
588 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
589 (index_motor_plant_.frisbees[1].position() -
590 index_motor_plant_.frisbees[2].position()), 0.10);
591 EXPECT_NEAR(
592 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
593 (index_motor_plant_.frisbees[2].position() -
594 index_motor_plant_.frisbees[3].position()), 0.10);
595}
596
597// Tests that shooting 4 discs works.
598TEST_F(IndexTest, ShootFourDiscs) {
599 LoadNDiscs(4);
600
601 EXPECT_EQ(static_cast<size_t>(4), index_motor_plant_.frisbees.size());
602
603 my_index_loop_.goal.MakeWithBuilder().goal_state(4).Send();
604
605 // Lifting and shooting takes a while...
606 for (int i = 0; i < 300; ++i) {
607 index_motor_plant_.SendPositionMessage();
608 index_motor_.Iterate();
609 index_motor_plant_.Simulate();
610 SendDSPacket(true);
611 UpdateTime();
612 }
613
614 my_index_loop_.status.FetchLatest();
615 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 0);
616 EXPECT_EQ(my_index_loop_.status->total_disc_count, 4);
617 my_index_loop_.output.FetchLatest();
618 EXPECT_FALSE(my_index_loop_.output->disc_clamped);
619 EXPECT_FALSE(my_index_loop_.output->loader_up);
620 EXPECT_FALSE(my_index_loop_.output->disc_ejected);
621
622 EXPECT_EQ(static_cast<size_t>(4), index_motor_plant_.shot_frisbees.size());
623}
624
Austin Schuh89955e42013-03-03 02:37:08 -0800625// Tests that discs aren't pulled out of the loader half way through being
626// grabbed when being asked to index.
627TEST_F(IndexTest, PreloadToIndexEarlyTransition) {
628 LoadNDiscs(2);
Austin Schuhf8c52252013-03-03 02:25:49 -0800629
Austin Schuh89955e42013-03-03 02:37:08 -0800630 // Lift the discs up to the top. Wait a while to let the system settle and
631 // verify that they don't collide.
632 my_index_loop_.goal.MakeWithBuilder().goal_state(3).Send();
633 for (int i = 0; i < 300; ++i) {
634 index_motor_plant_.SendPositionMessage();
635 index_motor_.Iterate();
636 index_motor_plant_.Simulate();
637 SendDSPacket(true);
638 UpdateTime();
639 // Drop out of the loop as soon as it enters the loader.
640 // This will require it to finish the job before intaking more.
641 my_index_loop_.status.FetchLatest();
642 if (index_motor_plant_.frisbees[0].position() >
643 IndexMotor::kLoaderFreeStopPosition) {
644 break;
645 }
646 }
647
648 // Pull the disc back down and verify that the transfer roller doesn't turn on
649 // until we are ready.
650 my_index_loop_.goal.MakeWithBuilder().goal_state(1).Send();
651 for (int i = 0; i < 100; ++i) {
652 index_motor_plant_.SendPositionMessage();
653 index_motor_.Iterate();
654 index_motor_plant_.Simulate();
655 SendDSPacket(true);
656 UpdateTime();
657 }
658
659 my_index_loop_.status.FetchLatest();
660 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
661 EXPECT_EQ(my_index_loop_.status->total_disc_count, 2);
662 my_index_loop_.output.FetchLatest();
663 EXPECT_TRUE(my_index_loop_.output->disc_clamped);
664
665 EXPECT_EQ(static_cast<size_t>(2), index_motor_plant_.frisbees.size());
666 EXPECT_NEAR(IndexMotor::kReadyToLiftPosition,
667 index_motor_plant_.frisbees[0].position(), 0.01);
668 EXPECT_NEAR(
669 (IndexMotor::kIndexStartPosition +
670 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
671 index_motor_plant_.frisbees[1].position(), 0.10);
672}
673
674// Tests that disabling while grabbing a disc doesn't cause problems.
675TEST_F(IndexTest, HandleDisable) {
676 my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
677 for (int i = 0; i < 200; ++i) {
678 index_motor_plant_.SendPositionMessage();
679 index_motor_.Iterate();
680 if (i == 100) {
681 EXPECT_EQ(0, index_motor_plant_.index_roller_position());
682 index_motor_plant_.InsertDisc();
683 } else if (i == 102) {
684 my_index_loop_.goal.MakeWithBuilder().goal_state(1).Send();
685 } else if (i > 150) {
686 my_index_loop_.status.FetchLatest();
687 EXPECT_TRUE(my_index_loop_.status->ready_to_intake);
688 my_index_loop_.output.FetchLatest();
689 EXPECT_EQ(my_index_loop_.output->transfer_voltage, 0.0);
690 }
691 index_motor_plant_.Simulate();
692 SendDSPacket(i < 102 || i > 110);
693 UpdateTime();
694 }
695
696 my_index_loop_.status.FetchLatest();
697 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
698 EXPECT_EQ(static_cast<size_t>(1), index_motor_plant_.frisbees.size());
699 EXPECT_NEAR(
700 (IndexMotor::kIndexStartPosition +
701 IndexMotor::ConvertDiscAngleToDiscPosition(M_PI)),
702 index_motor_plant_.frisbees[0].position(), 0.04);
703}
Austin Schuhd78ab542013-03-01 22:22:19 -0800704
705} // namespace testing
706} // namespace control_loops
707} // namespace frc971