Austin Schuh | d78ab54 | 2013-03-01 22:22:19 -0800 | [diff] [blame^] | 1 | #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 | |
| 15 | using ::aos::time::Time; |
| 16 | |
| 17 | namespace frc971 { |
| 18 | namespace control_loops { |
| 19 | namespace testing { |
| 20 | |
| 21 | // TODO(aschuh): Figure out these constants. |
| 22 | const double kTransferStartPosition = 0.0; |
| 23 | const double kIndexStartPosition = 0.5; |
| 24 | const double kIndexStopPosition = 2.5; |
| 25 | const double kGrabberStopPosition = 2.625; |
| 26 | const double kGrabberMovementVelocity = 0.4; |
| 27 | |
| 28 | // Start and stop position of the bottom disc detect sensor in meters. |
| 29 | const double kBottomDiscDetectStart = -0.08; |
| 30 | const double kBottomDiscDetectStop = 0.200025; |
| 31 | |
| 32 | const double kTopDiscDetectStart = 18.0; |
| 33 | const double kTopDiscDetectStop = 19.0; |
| 34 | |
| 35 | // Disc radius in meters. |
| 36 | const double kDiscRadius = 11.875 * 0.0254 / 2; |
| 37 | // Roller radius in meters. |
| 38 | const double kRollerRadius = 2.0 * 0.0254 / 2; |
| 39 | |
| 40 | class 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. |
| 140 | class 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 | |
| 236 | class 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. |
| 287 | TEST_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 |