blob: 418a9b606a76d54f1b8dce21b5a7f348760f219c [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
21// TODO(aschuh): Figure out these constants.
22const double kTransferStartPosition = 0.0;
23const double kIndexStartPosition = 0.5;
24const double kIndexStopPosition = 2.5;
25const double kGrabberStopPosition = 2.625;
26const double kGrabberMovementVelocity = 0.4;
27
28// Start and stop position of the bottom disc detect sensor in meters.
29const double kBottomDiscDetectStart = -0.08;
30const double kBottomDiscDetectStop = 0.200025;
31
32const double kTopDiscDetectStart = 18.0;
33const double kTopDiscDetectStop = 19.0;
34
35// Disc radius in meters.
36const double kDiscRadius = 11.875 * 0.0254 / 2;
37// Roller radius in meters.
38const double kRollerRadius = 2.0 * 0.0254 / 2;
39
40class Frisbee {
41 public:
42 // Creates a frisbee starting at the specified position in the frisbee path,
43 // and with the transfer and index rollers at the specified positions.
44 Frisbee(double transfer_roller_position,
45 double index_roller_position,
46 double position = 0.0)
47 : transfer_roller_position_(transfer_roller_position),
48 index_roller_position_(index_roller_position),
49 clamped_(false),
50 position_(position) {
51 }
52
53 // Returns true if the frisbee is controlled by the transfer roller.
54 bool IsTouchingTransfer() const {
55 return (position_ >= kTransferStartPosition &&
56 position_ <= kIndexStartPosition);
57 }
58
59 // Returns true if the frisbee is controlled by the indexing roller.
60 bool IsTouchingIndex() const {
61 return (position_ >= kIndexStartPosition &&
62 position_ <= kIndexStopPosition);
63 }
64
65 // Returns true if the frisbee is in a position such that the grabber will
66 // pull it into the loader.
67 bool IsTouchingGrabber() const {
68 return (position_ >= kIndexStopPosition &&
69 position_ <= kGrabberStopPosition);
70 }
71
72 // Returns true if the disc is triggering the bottom disc detect sensor.
73 bool bottom_disc_detect() const {
74 return (position_ >= kBottomDiscDetectStart &&
75 position_ <= kBottomDiscDetectStop);
76 }
77
78 // Returns true if the disc is triggering the top disc detect sensor.
79 bool top_disc_detect() const {
80 return (position_ >= kTopDiscDetectStart &&
81 position_ <= kTopDiscDetectStop);
82 }
83
84 // Converts the angle of the indexer to the distance traveled by the center of
85 // the disc.
86 double ConvertIndexToDiscPosition(const double angle) const {
87 return (angle * (kDiscRadius + kRollerRadius) /
88 (1 + (kDiscRadius * 2 + kRollerRadius) / kRollerRadius));
89 }
90
91 // Converts the angle of the transfer to the distance traveled by the center
92 // of the disc.
93 double ConvertTransferToDiscPosition(const double angle) const {
94 return ConvertIndexToDiscPosition(angle);
95 }
96
97 // Updates the position of the frisbee in the frisbee path.
98 void UpdatePosition(double transfer_roller_position,
99 double index_roller_position,
100 bool clamped) {
101 // TODO(aschuh): Assert that you can't slide the frisbee through the
102 // clamp.
103 if (IsTouchingTransfer()) {
104 position_ += ConvertTransferToDiscPosition(transfer_roller_position -
105 transfer_roller_position_);
106 } else if (IsTouchingIndex()) {
107 position_ += ConvertIndexToDiscPosition(index_roller_position -
108 index_roller_position_);
109 } else if (IsTouchingGrabber()) {
110 if (clamped) {
111 position_ = ::std::min(position_ + kGrabberMovementVelocity / 100.0,
112 kGrabberStopPosition);
113 }
114 } else {
115 // TODO(aschuh): Deal with lifting.
116 // TODO(aschuh): Deal with shooting.
117 // We must wait long enough for the disc to leave the loader before
118 // lowering.
119 }
120 transfer_roller_position_ = transfer_roller_position;
121 index_roller_position_ = index_roller_position;
122 clamped_ = clamped;
123 printf("Disc is at %f\n", position_);
124 }
125
126 double position() const {
127 return position_;
128 }
129
130 private:
131 double transfer_roller_position_;
132 double index_roller_position_;
133 bool clamped_;
134 double position_;
135};
136
137
138// Class which simulates the index and sends out queue messages containing the
139// position.
140class IndexMotorSimulation {
141 public:
142 // Constructs a motor simulation. initial_position is the inital angle of the
143 // index, which will be treated as 0 by the encoder.
144 IndexMotorSimulation()
145 : index_plant_(new StateFeedbackPlant<2, 1, 1>(MakeIndexPlant())),
146 transfer_plant_(new StateFeedbackPlant<2, 1, 1>(MakeTransferPlant())),
147 my_index_loop_(".frc971.control_loops.index",
148 0x1a7b7094, ".frc971.control_loops.index.goal",
149 ".frc971.control_loops.index.position",
150 ".frc971.control_loops.index.output",
151 ".frc971.control_loops.index.status") {
152 }
153
154 // Starts a disc at the start of the index.
155 void InsertDisc() {
156 frisbees.push_back(Frisbee(transfer_roller_position(),
157 index_roller_position()));
158 }
159
160 // Returns true if the bottom disc sensor is triggered.
161 bool BottomDiscDetect() const {
162 bool bottom_disc_detect = false;
163 for (const Frisbee &frisbee : frisbees) {
164 bottom_disc_detect |= frisbee.bottom_disc_detect();
165 }
166 return bottom_disc_detect;
167 }
168
169 // Returns true if the top disc sensor is triggered.
170 bool TopDiscDetect() const {
171 bool top_disc_detect = false;
172 for (const Frisbee &frisbee : frisbees) {
173 top_disc_detect |= frisbee.top_disc_detect();
174 }
175 return top_disc_detect;
176 }
177
178 void UpdateDiscs(bool clamped) {
179 for (Frisbee &frisbee : frisbees) {
180 // TODO(aschuh): Simulate clamping
181 frisbee.UpdatePosition(transfer_roller_position(),
182 index_roller_position(),
183 clamped);
184 }
185 }
186
187 // Sends out the position queue messages.
188 void SendPositionMessage() {
189 ::aos::ScopedMessagePtr<control_loops::IndexLoop::Position> position =
190 my_index_loop_.position.MakeMessage();
191 position->index_position = index_roller_position();
192 position->bottom_disc_detect = BottomDiscDetect();
193 position->top_disc_detect = TopDiscDetect();
194 printf("bdd: %x tdd: %x\n", position->bottom_disc_detect,
195 position->top_disc_detect);
196 position.Send();
197 }
198
199 // Simulates the index moving for one timestep.
200 void Simulate() {
201 EXPECT_TRUE(my_index_loop_.output.FetchLatest());
202
203 index_plant_->U << my_index_loop_.output->index_voltage;
204 index_plant_->Update();
205
206 transfer_plant_->U << my_index_loop_.output->transfer_voltage;
207 transfer_plant_->Update();
208 printf("tv: %f iv: %f tp : %f ip: %f\n",
209 my_index_loop_.output->transfer_voltage,
210 my_index_loop_.output->index_voltage,
211 transfer_roller_position(), index_roller_position());
212
213 UpdateDiscs(my_index_loop_.output->disc_clamped);
214 }
215
216 ::std::unique_ptr<StateFeedbackPlant<2, 1, 1>> index_plant_;
217 ::std::unique_ptr<StateFeedbackPlant<2, 1, 1>> transfer_plant_;
218
219 // Returns the absolute angle of the index.
220 double index_roller_position() const {
221 return index_plant_->Y(0, 0);
222 }
223
224 // Returns the absolute angle of the index.
225 double transfer_roller_position() const {
226 return transfer_plant_->Y(0, 0);
227 }
228
229 ::std::vector<Frisbee> frisbees;
230
231 private:
232 IndexLoop my_index_loop_;
233};
234
235
236class IndexTest : public ::testing::Test {
237 protected:
238 IndexTest() : my_index_loop_(".frc971.control_loops.index",
239 0x1a7b7094, ".frc971.control_loops.index.goal",
240 ".frc971.control_loops.index.position",
241 ".frc971.control_loops.index.output",
242 ".frc971.control_loops.index.status"),
243 index_motor_(&my_index_loop_),
244 index_motor_plant_(),
245 loop_count_(0) {
246 // Flush the robot state queue so we can use clean shared memory for this
247 // test.
248 ::aos::robot_state.Clear();
249 SendDSPacket(true);
250 Time::EnableMockTime(Time(0, 0));
251 }
252
253 virtual ~IndexTest() {
254 ::aos::robot_state.Clear();
255 Time::DisableMockTime();
256 }
257
258 // Sends a DS packet with the enable bit set to enabled.
259 void SendDSPacket(bool enabled) {
260 ::aos::robot_state.MakeWithBuilder().enabled(enabled)
261 .autonomous(false)
262 .team_id(971).Send();
263 ::aos::robot_state.FetchLatest();
264 }
265
266 // Updates the current mock time.
267 void UpdateTime() {
268 loop_count_ += 1;
269 Time::SetMockTime(Time::InMS(10 * loop_count_));
270 }
271
272 ::aos::common::testing::GlobalCoreInstance my_core;
273
274 // Create a new instance of the test queue so that it invalidates the queue
275 // that it points to. Otherwise, we will have a pointer to shared memory that
276 // is no longer valid.
277 IndexLoop my_index_loop_;
278
279 // Create a loop and simulation plant.
280 IndexMotor index_motor_;
281 IndexMotorSimulation index_motor_plant_;
282
283 int loop_count_;
284};
285
286// Tests that the index grabs 1 disc and places it at the correct position.
287TEST_F(IndexTest, GrabSingleDisc) {
288 my_index_loop_.goal.MakeWithBuilder().goal_state(2).Send();
289 for (int i = 0; i < 250; ++i) {
290 index_motor_plant_.SendPositionMessage();
291 index_motor_.Iterate();
292 if (i == 100) {
293 EXPECT_EQ(0, index_motor_plant_.index_roller_position());
294 index_motor_plant_.InsertDisc();
295 }
296 index_motor_plant_.Simulate();
297 SendDSPacket(true);
298 UpdateTime();
299 }
300
301 EXPECT_TRUE(my_index_loop_.status.FetchLatest());
302 EXPECT_EQ(my_index_loop_.status->hopper_disc_count, 1);
303 EXPECT_EQ(static_cast<size_t>(1), index_motor_plant_.frisbees.size());
304 EXPECT_NEAR(
305 kIndexStartPosition + IndexMotor::ConvertDiscAngleToDiscPosition(M_PI),
306 index_motor_plant_.frisbees[0].position(), 0.01);
307}
308
309// Test that pulling in a second disc works correctly.
310// Test that HOLD still finishes the disc correctly.
311// Test that pulling a disc down works correctly and ready_to_intake waits.
312
313} // namespace testing
314} // namespace control_loops
315} // namespace frc971