blob: e85657cade581ba6d7eae5e038fe25fd648caa01 [file] [log] [blame]
Comran Morshed25f81a02016-01-23 13:40:10 +00001#include "y2016/control_loops/superstructure/superstructure.h"
Austin Schuh10c2d112016-02-14 13:42:28 -08002#include "y2016/control_loops/superstructure/superstructure_controls.h"
Comran Morshed25f81a02016-01-23 13:40:10 +00003
Austin Schuh2c717862016-03-13 15:32:53 -07004#include "aos/common/commonmath.h"
Comran Morshed25f81a02016-01-23 13:40:10 +00005#include "aos/common/controls/control_loops.q.h"
6#include "aos/common/logging/logging.h"
7
Austin Schuh2fc10fa2016-02-08 00:44:34 -08008#include "y2016/control_loops/superstructure/integral_intake_plant.h"
9#include "y2016/control_loops/superstructure/integral_arm_plant.h"
10
11#include "y2016/constants.h"
12
Comran Morshed25f81a02016-01-23 13:40:10 +000013namespace y2016 {
14namespace control_loops {
Austin Schuh2fc10fa2016-02-08 00:44:34 -080015namespace superstructure {
Comran Morshed25f81a02016-01-23 13:40:10 +000016
Austin Schuh2fc10fa2016-02-08 00:44:34 -080017namespace {
Austin Schuh2d7820b2016-02-16 13:47:42 -080018// The maximum voltage the intake roller will be allowed to use.
Austin Schuhbe041152016-02-28 22:01:52 -080019constexpr float kMaxIntakeTopVoltage = 12.0;
20constexpr float kMaxIntakeBottomVoltage = 12.0;
Comran Morshedf4cd7642016-02-15 20:40:49 +000021
Austin Schuh2d7820b2016-02-16 13:47:42 -080022// Aliases to reduce typing.
Adam Snaider06779722016-02-14 15:26:22 -080023constexpr double kIntakeEncoderIndexDifference =
24 constants::Values::kIntakeEncoderIndexDifference;
25constexpr double kWristEncoderIndexDifference =
26 constants::Values::kWristEncoderIndexDifference;
27constexpr double kShoulderEncoderIndexDifference =
28 constants::Values::kShoulderEncoderIndexDifference;
Austin Schuh2fc10fa2016-02-08 00:44:34 -080029} // namespace
30
Philipp Schrader0119eb12016-02-15 18:16:21 +000031// ///// CollisionAvoidance /////
32
33void CollisionAvoidance::UpdateGoal(double shoulder_angle_goal,
34 double wrist_angle_goal,
35 double intake_angle_goal) {
Austin Schuh6ca0f792016-03-12 14:06:14 -080036 const double original_shoulder_angle_goal = shoulder_angle_goal;
37 const double original_intake_angle_goal = intake_angle_goal;
Austin Schuh2c717862016-03-13 15:32:53 -070038 const double original_wrist_angle_goal = wrist_angle_goal;
Austin Schuh6ca0f792016-03-12 14:06:14 -080039
Philipp Schrader0119eb12016-02-15 18:16:21 +000040 double shoulder_angle = arm_->shoulder_angle();
41 double wrist_angle = arm_->wrist_angle();
42 double intake_angle = intake_->angle();
43
44 // TODO(phil): This may need tuning to account for bounciness in the limbs or
45 // some other thing that I haven't thought of. At the very least,
46 // incorporating a small safety margin makes writing test cases much easier
47 // since you can directly compare statuses against the constants in the
48 // CollisionAvoidance class.
49 constexpr double kSafetyMargin = 0.01; // radians
50
51 // Avoid colliding the shooter with the frame.
52 // If the shoulder is below a certain angle or we want to move it below
53 // that angle, then the shooter has to stay level to the ground. Otherwise,
54 // it will crash into the frame.
Austin Schuh2c717862016-03-13 15:32:53 -070055 if (intake_angle < kMaxIntakeAngleBeforeArmInterference + kSafetyMargin) {
56 if (shoulder_angle < kMinShoulderAngleForHorizontalShooter ||
57 original_shoulder_angle_goal < kMinShoulderAngleForHorizontalShooter) {
58 wrist_angle_goal = 0.0;
59 } else if (shoulder_angle < kMinShoulderAngleForIntakeInterference ||
60 original_shoulder_angle_goal <
61 kMinShoulderAngleForIntakeInterference) {
62 wrist_angle_goal =
63 aos::Clip(original_wrist_angle_goal,
64 kMinWristAngleForMovingByIntake + kSafetyMargin,
65 kMaxWristAngleForMovingByIntake - kSafetyMargin);
66 }
67 } else {
68 if (shoulder_angle < kMinShoulderAngleForIntakeUpInterference ||
69 original_shoulder_angle_goal <
70 kMinShoulderAngleForIntakeUpInterference) {
71 wrist_angle_goal = 0.0;
72 }
73 }
74
Austin Schuh6ca0f792016-03-12 14:06:14 -080075 if (shoulder_angle < kMinShoulderAngleForIntakeUpInterference ||
76 original_shoulder_angle_goal < kMinShoulderAngleForIntakeUpInterference) {
Philipp Schrader0119eb12016-02-15 18:16:21 +000077 // Make sure that we don't move the shoulder below a certain angle until
78 // the wrist is level with the ground.
Austin Schuh6802a9d2016-03-12 21:34:53 -080079 if (intake_angle < kMaxIntakeAngleBeforeArmInterference + kSafetyMargin) {
Austin Schuh2c717862016-03-13 15:32:53 -070080 if (wrist_angle > kMaxWristAngleForMovingByIntake ||
81 wrist_angle < kMinWristAngleForMovingByIntake) {
Austin Schuh6802a9d2016-03-12 21:34:53 -080082 shoulder_angle_goal =
Austin Schuh6ca0f792016-03-12 14:06:14 -080083 ::std::max(original_shoulder_angle_goal,
Austin Schuh6802a9d2016-03-12 21:34:53 -080084 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
85 }
86 } else {
Austin Schuh2c717862016-03-13 15:32:53 -070087 if (wrist_angle > kMaxWristAngleForMovingByIntake ||
88 wrist_angle < kMinWristAngleForMovingByIntake) {
Austin Schuh6802a9d2016-03-12 21:34:53 -080089 shoulder_angle_goal =
90 ::std::max(original_shoulder_angle_goal,
91 kMinShoulderAngleForIntakeUpInterference + kSafetyMargin);
92 }
Austin Schuh6ca0f792016-03-12 14:06:14 -080093 }
Philipp Schrader0119eb12016-02-15 18:16:21 +000094 if (::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
95 shoulder_angle_goal =
Austin Schuh6802a9d2016-03-12 21:34:53 -080096 ::std::max(shoulder_angle_goal,
Philipp Schrader0119eb12016-02-15 18:16:21 +000097 kMinShoulderAngleForHorizontalShooter + kSafetyMargin);
98 }
99 }
100
101 // Is the arm where it could interfere with the intake right now?
102 bool shoulder_is_in_danger =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800103 (shoulder_angle < kMinShoulderAngleForIntakeUpInterference &&
Philipp Schrader0119eb12016-02-15 18:16:21 +0000104 shoulder_angle > kMaxShoulderAngleUntilSafeIntakeStowing);
105
106 // Is the arm moving into collision zone from above?
107 bool shoulder_moving_into_danger_from_above =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800108 (shoulder_angle >= kMinShoulderAngleForIntakeUpInterference &&
109 original_shoulder_angle_goal <=
110 kMinShoulderAngleForIntakeUpInterference);
Philipp Schrader0119eb12016-02-15 18:16:21 +0000111
112 // Is the arm moving into collision zone from below?
113 bool shoulder_moving_into_danger_from_below =
114 (shoulder_angle <= kMaxShoulderAngleUntilSafeIntakeStowing &&
Austin Schuh6ca0f792016-03-12 14:06:14 -0800115 original_shoulder_angle_goal >= kMaxShoulderAngleUntilSafeIntakeStowing);
Philipp Schrader0119eb12016-02-15 18:16:21 +0000116
117 // Avoid colliding the arm with the intake.
118 if (shoulder_is_in_danger || shoulder_moving_into_danger_from_above ||
119 shoulder_moving_into_danger_from_below) {
120 // If the arm could collide with the intake, we make sure to move the
121 // intake out of the way. The arm has priority.
122 intake_angle_goal =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800123 ::std::min(original_intake_angle_goal,
Philipp Schrader0119eb12016-02-15 18:16:21 +0000124 kMaxIntakeAngleBeforeArmInterference - kSafetyMargin);
125
126 // Don't let the shoulder move into the collision area until the intake is
127 // out of the way.
128 if (intake_angle > kMaxIntakeAngleBeforeArmInterference) {
129 const double kHalfwayPointBetweenSafeZones =
130 (kMinShoulderAngleForIntakeInterference +
131 kMaxShoulderAngleUntilSafeIntakeStowing) /
132 2.0;
133
134 if (shoulder_angle >= kHalfwayPointBetweenSafeZones) {
135 // The shoulder is closer to being above the collision area. Move it up
136 // there.
Austin Schuh2c717862016-03-13 15:32:53 -0700137 if (intake_angle <
138 kMaxIntakeAngleBeforeArmInterference + kSafetyMargin) {
139 shoulder_angle_goal = ::std::max(
140 original_shoulder_angle_goal,
141 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
142 } else {
143 shoulder_angle_goal = ::std::max(
144 original_shoulder_angle_goal,
145 kMinShoulderAngleForIntakeUpInterference + kSafetyMargin);
146 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000147 } else {
148 // The shoulder is closer to being below the collision zone (i.e. in
149 // stowing/intake position), keep it there for now.
150 shoulder_angle_goal =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800151 ::std::min(original_shoulder_angle_goal,
Philipp Schrader0119eb12016-02-15 18:16:21 +0000152 kMaxShoulderAngleUntilSafeIntakeStowing - kSafetyMargin);
153 }
154 }
155 }
156
157 // Send the possibly adjusted goals to the components.
158 arm_->set_unprofiled_goal(shoulder_angle_goal, wrist_angle_goal);
159 intake_->set_unprofiled_goal(intake_angle_goal);
160}
161
Philipp Schrader07147532016-02-16 01:23:07 +0000162bool CollisionAvoidance::collided() const {
163 return collided_with_given_angles(arm_->shoulder_angle(), arm_->wrist_angle(),
164 intake_->angle());
165}
166
167bool CollisionAvoidance::collided_with_given_angles(double shoulder_angle,
168 double wrist_angle,
169 double intake_angle) {
170 // The arm and the intake must not hit.
171 if (shoulder_angle >=
172 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing &&
173 shoulder_angle <=
Austin Schuh6ca0f792016-03-12 14:06:14 -0800174 CollisionAvoidance::kMinShoulderAngleForIntakeUpInterference &&
175 intake_angle >
176 CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference) {
177 LOG(DEBUG, "Collided: Intake %f > %f, and shoulder %f < %f < %f.\n",
178 intake_angle, CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference,
179 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing,
Austin Schuhc85cdd52016-02-16 13:05:35 -0800180 shoulder_angle,
Austin Schuh6ca0f792016-03-12 14:06:14 -0800181 CollisionAvoidance::kMinShoulderAngleForIntakeUpInterference);
182 return true;
183 }
184
185 if (shoulder_angle >=
186 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing &&
187 shoulder_angle <=
188 CollisionAvoidance::kMinShoulderAngleForIntakeInterference &&
189 intake_angle < CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference &&
190 intake_angle > Superstructure::kIntakeLowerClear &&
Austin Schuh2c717862016-03-13 15:32:53 -0700191 (wrist_angle > CollisionAvoidance::kMaxWristAngleForMovingByIntake ||
192 wrist_angle < CollisionAvoidance::kMinWristAngleForMovingByIntake)) {
193 LOG(DEBUG,
194 "Collided: Intake %f < %f < %f, shoulder %f < %f < %f, and %f < %f < "
195 "%f.\n",
Austin Schuh6ca0f792016-03-12 14:06:14 -0800196 Superstructure::kIntakeLowerClear, intake_angle,
197 CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference,
198 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing,
199 shoulder_angle,
Austin Schuh2c717862016-03-13 15:32:53 -0700200 CollisionAvoidance::kMinShoulderAngleForIntakeInterference,
201 CollisionAvoidance::kMinWristAngleForMovingByIntake, wrist_angle,
202 CollisionAvoidance::kMaxWristAngleForMovingByIntake);
Philipp Schrader07147532016-02-16 01:23:07 +0000203 return true;
204 }
205
206 // The wrist must go back to zero when the shoulder is moving the arm into
207 // a stowed/intaking position.
208 if (shoulder_angle <
209 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter &&
210 ::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
Austin Schuhc85cdd52016-02-16 13:05:35 -0800211 LOG(DEBUG, "Collided: Shoulder %f < %f and wrist |%f| < %f.\n",
212 shoulder_angle,
213 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter, wrist_angle,
214 kMaxWristAngleForSafeArmStowing);
Philipp Schrader07147532016-02-16 01:23:07 +0000215 return true;
216 }
217
218 return false;
219}
220
Philipp Schrader0119eb12016-02-15 18:16:21 +0000221constexpr double CollisionAvoidance::kMinShoulderAngleForHorizontalShooter;
222constexpr double CollisionAvoidance::kMinShoulderAngleForIntakeInterference;
223constexpr double CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference;
224constexpr double CollisionAvoidance::kMaxWristAngleForSafeArmStowing;
225constexpr double CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing;
226
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800227Superstructure::Superstructure(
228 control_loops::SuperstructureQueue *superstructure_queue)
229 : aos::controls::ControlLoop<control_loops::SuperstructureQueue>(
Philipp Schrader0119eb12016-02-15 18:16:21 +0000230 superstructure_queue),
231 collision_avoidance_(&intake_, &arm_) {}
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800232
Adam Snaider06779722016-02-14 15:26:22 -0800233bool Superstructure::IsArmNear(double shoulder_tolerance,
234 double wrist_tolerance) {
235 return ((arm_.unprofiled_goal() - arm_.X_hat())
236 .block<2, 1>(0, 0)
237 .lpNorm<Eigen::Infinity>() < shoulder_tolerance) &&
238 ((arm_.unprofiled_goal() - arm_.X_hat())
239 .block<4, 1>(0, 0)
240 .lpNorm<Eigen::Infinity>() < wrist_tolerance) &&
241 ((arm_.unprofiled_goal() - arm_.goal())
242 .block<4, 1>(0, 0)
243 .lpNorm<Eigen::Infinity>() < 1e-6);
244}
245
246bool Superstructure::IsArmNear(double tolerance) {
247 return ((arm_.unprofiled_goal() - arm_.X_hat())
248 .block<4, 1>(0, 0)
249 .lpNorm<Eigen::Infinity>() < tolerance) &&
250 ((arm_.unprofiled_goal() - arm_.goal())
251 .block<4, 1>(0, 0)
252 .lpNorm<Eigen::Infinity>() < 1e-6);
253}
254
255bool Superstructure::IsIntakeNear(double tolerance) {
256 return ((intake_.unprofiled_goal() - intake_.X_hat())
257 .block<2, 1>(0, 0)
258 .lpNorm<Eigen::Infinity>() < tolerance);
259}
260
261double Superstructure::MoveButKeepAbove(double reference_angle,
262 double current_angle,
263 double move_distance) {
264 return -MoveButKeepBelow(-reference_angle, -current_angle, -move_distance);
265}
266
267double Superstructure::MoveButKeepBelow(double reference_angle,
268 double current_angle,
269 double move_distance) {
270 // There are 3 interesting places to move to.
271 const double small_negative_move = current_angle - move_distance;
272 const double small_positive_move = current_angle + move_distance;
273 // And the reference angle.
274
275 // Move the the highest one that is below reference_angle.
276 if (small_negative_move > reference_angle) {
277 return reference_angle;
278 } else if (small_positive_move > reference_angle) {
279 return small_negative_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800280 } else {
Adam Snaider06779722016-02-14 15:26:22 -0800281 return small_positive_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800282 }
283}
284
285void Superstructure::RunIteration(
286 const control_loops::SuperstructureQueue::Goal *unsafe_goal,
287 const control_loops::SuperstructureQueue::Position *position,
288 control_loops::SuperstructureQueue::Output *output,
289 control_loops::SuperstructureQueue::Status *status) {
Adam Snaider06779722016-02-14 15:26:22 -0800290 const State state_before_switch = state_;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800291 if (WasReset()) {
292 LOG(ERROR, "WPILib reset, restarting\n");
293 arm_.Reset();
294 intake_.Reset();
295 state_ = UNINITIALIZED;
296 }
297
298 // Bool to track if we should turn the motors on or not.
299 bool disable = output == nullptr;
300
301 arm_.Correct(position->shoulder, position->wrist);
302 intake_.Correct(position->intake);
303
Adam Snaider06779722016-02-14 15:26:22 -0800304 // There are 2 main zeroing paths, HIGH_ARM_ZERO and LOW_ARM_ZERO.
305 //
306 // HIGH_ARM_ZERO works by lifting the arm all the way up so it is clear,
307 // moving the shooter to be horizontal, moving the intake out, and then moving
308 // the arm back down.
309 //
310 // LOW_ARM_ZERO works by moving the intake out of the way, lifting the arm up,
311 // leveling the shooter, and then moving back down.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800312
Diana Vandenberge2843c62016-02-13 17:44:20 -0800313 if (arm_.error() || intake_.error()) {
314 state_ = ESTOP;
315 }
316
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800317 const bool is_collided = collided();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800318 switch (state_) {
319 case UNINITIALIZED:
Adam Snaider06779722016-02-14 15:26:22 -0800320 // Wait in the uninitialized state until both the arm and intake are
321 // initialized.
322 LOG(DEBUG, "Uninitialized, waiting for intake and arm\n");
323 if (arm_.initialized() && intake_.initialized()) {
324 state_ = DISABLED_INITIALIZED;
325 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800326 disable = true;
327 break;
328
Adam Snaider06779722016-02-14 15:26:22 -0800329 case DISABLED_INITIALIZED:
330 // Wait here until we are either fully zeroed while disabled, or we become
331 // enabled. At that point, figure out if we should HIGH_ARM_ZERO or
332 // LOW_ARM_ZERO.
333 if (disable) {
334 if (arm_.zeroed() && intake_.zeroed()) {
335 state_ = SLOW_RUNNING;
336 }
337 } else {
338 if (arm_.shoulder_angle() >= kShoulderMiddleAngle) {
339 state_ = HIGH_ARM_ZERO_LIFT_ARM;
340 } else {
341 state_ = LOW_ARM_ZERO_LOWER_INTAKE;
342 }
343 }
344
345 // Set the goals to where we are now so when we start back up, we don't
346 // jump.
347 intake_.ForceGoal(intake_.angle());
348 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
349 // Set up the profile to be the zeroing profile.
350 intake_.AdjustProfile(0.5, 10);
351 arm_.AdjustProfile(0.5, 10, 0.5, 10);
352
353 // We are not ready to start doing anything yet.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800354 disable = true;
Adam Snaider06779722016-02-14 15:26:22 -0800355 break;
356
357 case HIGH_ARM_ZERO_LIFT_ARM:
358 if (disable) {
359 state_ = DISABLED_INITIALIZED;
360 } else {
361 // Raise the shoulder up out of the way.
362 arm_.set_unprofiled_goal(kShoulderUpAngle, arm_.wrist_angle());
363 if (IsArmNear(kLooseTolerance)) {
364 // Close enough, start the next move.
365 state_ = HIGH_ARM_ZERO_LEVEL_SHOOTER;
366 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800367 }
368 break;
369
Adam Snaider06779722016-02-14 15:26:22 -0800370 case HIGH_ARM_ZERO_LEVEL_SHOOTER:
371 if (disable) {
372 state_ = DISABLED_INITIALIZED;
373 } else {
374 // Move the shooter to be level.
375 arm_.set_unprofiled_goal(kShoulderUpAngle, 0.0);
376
377 if (IsArmNear(kLooseTolerance)) {
378 // Close enough, start the next move.
379 state_ = HIGH_ARM_ZERO_MOVE_INTAKE_OUT;
380 }
381 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800382 break;
383
Adam Snaider06779722016-02-14 15:26:22 -0800384 case HIGH_ARM_ZERO_MOVE_INTAKE_OUT:
385 if (disable) {
386 state_ = DISABLED_INITIALIZED;
387 } else {
388 // If we were just asked to move the intake, make sure it moves far
389 // enough.
390 if (last_state_ != HIGH_ARM_ZERO_MOVE_INTAKE_OUT) {
391 intake_.set_unprofiled_goal(
392 MoveButKeepBelow(kIntakeUpperClear, intake_.angle(),
393 kIntakeEncoderIndexDifference * 2.5));
394 }
395
396 if (IsIntakeNear(kLooseTolerance)) {
397 // Close enough, start the next move.
398 state_ = HIGH_ARM_ZERO_LOWER_ARM;
399 }
400 }
401 break;
402
403 case HIGH_ARM_ZERO_LOWER_ARM:
404 if (disable) {
405 state_ = DISABLED_INITIALIZED;
406 } else {
407 // Land the shooter in the belly-pan. It should be zeroed by the time
408 // it gets there. If not, just estop.
409 arm_.set_unprofiled_goal(kShoulderLanded, 0.0);
410 if (arm_.zeroed() && intake_.zeroed()) {
411 state_ = RUNNING;
412 } else if (IsArmNear(kLooseTolerance)) {
413 LOG(ERROR,
414 "Failed to zero while executing the HIGH_ARM_ZERO sequence. "
415 "Arm: %d Intake %d\n",
416 arm_.zeroed(), intake_.zeroed());
417 state_ = ESTOP;
418 }
419 }
420 break;
421
422 case LOW_ARM_ZERO_LOWER_INTAKE:
423 if (disable) {
424 state_ = DISABLED_INITIALIZED;
425 } else {
426 // Move the intake down out of the way of the arm. Make sure to move it
427 // far enough to zero.
428 if (last_state_ != LOW_ARM_ZERO_LOWER_INTAKE) {
429 intake_.set_unprofiled_goal(
430 MoveButKeepBelow(kIntakeLowerClear, intake_.angle(),
431 kIntakeEncoderIndexDifference * 2.5));
432 }
433 if (IsIntakeNear(kLooseTolerance)) {
434 if (::std::abs(arm_.wrist_angle()) < kWristAlmostLevel) {
435 state_ = LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER;
436 } else {
437 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
438 }
439 }
440 }
441 break;
442
443 case LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER:
444 if (disable) {
445 state_ = DISABLED_INITIALIZED;
446 } else {
447 // If we are supposed to level the shooter, set it to level, and wait
448 // until it is very close to level.
449 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
450 if (IsArmNear(kLooseTolerance, kTightTolerance)) {
451 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
452 }
453 }
454 break;
455
456 case LOW_ARM_ZERO_LIFT_SHOULDER:
457 if (disable) {
458 state_ = DISABLED_INITIALIZED;
459 } else {
460 // Decide where to move to. We need to move far enough to see an index
461 // pulse, but must also get high enough that we can safely level the
462 // shooter.
463 if (last_state_ != LOW_ARM_ZERO_LIFT_SHOULDER) {
464 arm_.set_unprofiled_goal(
465 MoveButKeepAbove(kShoulderWristClearAngle, arm_.shoulder_angle(),
466 ::std::max(kWristEncoderIndexDifference,
467 kShoulderEncoderIndexDifference) *
468 2.5),
469 arm_.unprofiled_goal(2, 0));
470 }
471
472 // Wait until we are level and then go for it.
473 if (IsArmNear(kLooseTolerance)) {
474 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
475 }
476 }
477 break;
478
479 case LOW_ARM_ZERO_LEVEL_SHOOTER:
480 if (disable) {
481 state_ = DISABLED_INITIALIZED;
482 } else {
483 // Move the shooter level (and keep the same height). We don't want to
484 // got to RUNNING until we are completely level so that we don't
485 // give control back in a weird case where we might crash.
486 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
487 if (IsArmNear(kLooseTolerance)) {
488 if (arm_.zeroed() && intake_.zeroed()) {
489 state_ = RUNNING;
490 } else {
491 LOG(ERROR,
492 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
493 "Arm: %d Intake %d\n",
494 arm_.zeroed(), intake_.zeroed());
495 state_ = ESTOP;
496 }
497 }
498 }
499 break;
500
Austin Schuhb1d682b2016-02-16 13:07:44 -0800501 // These 4 cases are very similar.
Adam Snaider06779722016-02-14 15:26:22 -0800502 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800503 case RUNNING:
Austin Schuhb1d682b2016-02-16 13:07:44 -0800504 case LANDING_SLOW_RUNNING:
505 case LANDING_RUNNING: {
Austin Schuh829fe572016-02-14 21:41:16 -0800506 if (disable) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800507 // If we are disabled, go to slow running (or landing slow running) if
508 // we are collided.
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800509 if (is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800510 if (state_ == RUNNING) {
511 state_ = SLOW_RUNNING;
512 } else if (state_ == LANDING_RUNNING) {
513 state_ = LANDING_SLOW_RUNNING;
514 }
515 }
Austin Schuh829fe572016-02-14 21:41:16 -0800516
Austin Schuhb1d682b2016-02-16 13:07:44 -0800517 // Reset the profile to the current position so it moves well from here.
Austin Schuh829fe572016-02-14 21:41:16 -0800518 intake_.ForceGoal(intake_.angle());
519 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
520 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800521 // If we are in slow_running and are no longer collided, let 'er rip.
Austin Schuh829fe572016-02-14 21:41:16 -0800522 if (state_ == SLOW_RUNNING) {
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800523 if (!is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800524 state_ = RUNNING;
525 }
526 } else if (state_ == LANDING_SLOW_RUNNING) {
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800527 if (!is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800528 state_ = LANDING_RUNNING;
529 }
Austin Schuh829fe572016-02-14 21:41:16 -0800530 }
531 }
532
Austin Schuhb1d682b2016-02-16 13:07:44 -0800533 double requested_shoulder = constants::Values::kShoulderRange.lower;
534 double requested_wrist = 0.0;
535 double requested_intake = M_PI / 2.0;
536
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800537 if (unsafe_goal) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800538 // If we are in one of the landing states, limit the accelerations and
539 // velocities to land cleanly.
540 if (state_ == LANDING_SLOW_RUNNING || state_ == LANDING_RUNNING) {
541 arm_.AdjustProfile(0.5, // Shoulder Velocity
542 4.0, // Shoulder acceleration,
543 4.0, // Wrist velocity
544 10.0); // Wrist acceleration.
545 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
546 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800547
Austin Schuhb1d682b2016-02-16 13:07:44 -0800548 requested_shoulder =
549 ::std::max(unsafe_goal->angle_shoulder,
550 constants::Values::kShoulderRange.lower);
551 requested_wrist = 0.0;
552 requested_intake = unsafe_goal->angle_intake;
Austin Schuha1dd5272016-02-16 15:39:38 -0800553 // Transition to landing once the profile is close to finished for the
554 // shoulder.
555 if (arm_.goal(0, 0) > kShoulderTransitionToLanded + 1e-4 ||
556 arm_.unprofiled_goal(0, 0) > kShoulderTransitionToLanded + 1e-4) {
557 if (state_ == LANDING_RUNNING) {
558 state_ = RUNNING;
559 } else {
560 state_ = SLOW_RUNNING;
561 }
562 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000563 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800564 // Otherwise, give the user what he asked for.
565 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
566 unsafe_goal->max_angular_acceleration_shoulder,
567 unsafe_goal->max_angular_velocity_wrist,
568 unsafe_goal->max_angular_acceleration_wrist);
569 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
570 unsafe_goal->max_angular_acceleration_intake);
571
572 // Except, don't let the shoulder go all the way down.
573 requested_shoulder = ::std::max(unsafe_goal->angle_shoulder,
574 kShoulderTransitionToLanded);
575 requested_wrist = unsafe_goal->angle_wrist;
576 requested_intake = unsafe_goal->angle_intake;
577
578 // Transition to landing once the profile is close to finished for the
579 // shoulder.
Austin Schuha1dd5272016-02-16 15:39:38 -0800580 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 &&
581 arm_.unprofiled_goal(0, 0) <=
582 kShoulderTransitionToLanded + 1e-4) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800583 if (state_ == RUNNING) {
584 state_ = LANDING_RUNNING;
585 } else {
586 state_ = LANDING_SLOW_RUNNING;
587 }
588 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000589 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800590 }
591
Austin Schuhb1d682b2016-02-16 13:07:44 -0800592 // Push the request out to hardware!
593 collision_avoidance_.UpdateGoal(requested_shoulder, requested_wrist,
594 requested_intake);
595
596 // ESTOP if we hit the hard limits.
597 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
598 state_ = ESTOP;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800599 }
Austin Schuhb1d682b2016-02-16 13:07:44 -0800600 } break;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800601
602 case ESTOP:
603 LOG(ERROR, "Estop\n");
604 disable = true;
605 break;
606 }
607
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800608 // Set the voltage limits.
Comran Morshed011f7d92016-02-16 23:03:06 +0000609 const double max_voltage = (state_ == RUNNING || state_ == LANDING_RUNNING)
610 ? kOperatingVoltage
611 : kZeroingVoltage;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800612 arm_.set_max_voltage(max_voltage, max_voltage);
613 intake_.set_max_voltage(max_voltage);
614
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800615 if (IsRunning()) {
616 // We don't want lots of negative voltage when we are near the bellypan on
617 // the shoulder...
618 // TODO(austin): Do I want to push negative power into the belly pan at this
619 // point? Maybe just put the goal slightly below the bellypan and call that
620 // good enough.
Austin Schuh6ca0f792016-03-12 14:06:14 -0800621 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 ||
622 arm_.X_hat(0, 0) <= kShoulderTransitionToLanded + 1e-4) {
Comran Morshed011f7d92016-02-16 23:03:06 +0000623 arm_.set_shoulder_asymetric_limits(kLandingShoulderDownVoltage,
624 max_voltage);
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800625 }
626 }
627
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800628 // Calculate the loops for a cycle.
Austin Schuhbe041152016-02-28 22:01:52 -0800629 {
630 Eigen::Matrix<double, 3, 1> error = intake_.controller().error();
631 status->intake.position_power = intake_.controller().K(0, 0) * error(0, 0);
632 status->intake.velocity_power = intake_.controller().K(0, 1) * error(1, 0);
633 }
634
635 {
636 Eigen::Matrix<double, 6, 1> error = arm_.controller().error();
637 status->shoulder.position_power = arm_.controller().K(0, 0) * error(0, 0);
638 status->shoulder.velocity_power = arm_.controller().K(0, 1) * error(1, 0);
639 status->wrist.position_power = arm_.controller().K(0, 2) * error(2, 0);
640 status->wrist.velocity_power = arm_.controller().K(0, 3) * error(3, 0);
641 }
642
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800643 arm_.Update(disable);
644 intake_.Update(disable);
645
646 // Write out all the voltages.
647 if (output) {
648 output->voltage_intake = intake_.intake_voltage();
649 output->voltage_shoulder = arm_.shoulder_voltage();
650 output->voltage_wrist = arm_.wrist_voltage();
Comran Morshedf4cd7642016-02-15 20:40:49 +0000651
652 // Logic to run our rollers on the intake.
Campbell Crowleyd4fd6552016-02-21 17:53:46 -0800653 output->voltage_top_rollers = 0.0;
654 output->voltage_bottom_rollers = 0.0;
Comran Morshedf4cd7642016-02-15 20:40:49 +0000655 if (unsafe_goal) {
Campbell Crowleyd4fd6552016-02-21 17:53:46 -0800656 output->voltage_top_rollers = ::std::max(
657 -kMaxIntakeTopVoltage,
658 ::std::min(unsafe_goal->voltage_top_rollers, kMaxIntakeTopVoltage));
659 output->voltage_bottom_rollers = ::std::max(
660 -kMaxIntakeBottomVoltage,
661 ::std::min(unsafe_goal->voltage_bottom_rollers, kMaxIntakeBottomVoltage));
Comran Morshedf4cd7642016-02-15 20:40:49 +0000662 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800663 }
664
665 // Save debug/internal state.
Austin Schuha987f8b2016-02-28 22:02:20 -0800666 status->zeroed = arm_.zeroed() && intake_.zeroed();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800667
668 status->shoulder.angle = arm_.X_hat(0, 0);
669 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
670 status->shoulder.goal_angle = arm_.goal(0, 0);
671 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800672 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
673 status->shoulder.unprofiled_goal_angular_velocity =
674 arm_.unprofiled_goal(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800675 status->shoulder.voltage_error = arm_.X_hat(4, 0);
676 status->shoulder.calculated_velocity =
677 (arm_.shoulder_angle() - last_shoulder_angle_) / 0.005;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800678 status->shoulder.estimator_state = arm_.ShoulderEstimatorState();
679
680 status->wrist.angle = arm_.X_hat(2, 0);
681 status->wrist.angular_velocity = arm_.X_hat(3, 0);
682 status->wrist.goal_angle = arm_.goal(2, 0);
683 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800684 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
685 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800686 status->wrist.voltage_error = arm_.X_hat(5, 0);
687 status->wrist.calculated_velocity =
688 (arm_.wrist_angle() - last_wrist_angle_) / 0.005;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800689 status->wrist.estimator_state = arm_.WristEstimatorState();
690
691 status->intake.angle = intake_.X_hat(0, 0);
692 status->intake.angular_velocity = intake_.X_hat(1, 0);
693 status->intake.goal_angle = intake_.goal(0, 0);
694 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800695 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
696 status->intake.unprofiled_goal_angular_velocity =
697 intake_.unprofiled_goal(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800698 status->intake.calculated_velocity = (intake_.angle() - last_intake_angle_) / 0.005;
699 status->intake.voltage_error = intake_.X_hat(2, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800700 status->intake.estimator_state = intake_.IntakeEstimatorState();
Austin Schuhbe041152016-02-28 22:01:52 -0800701 status->intake.feedforwards_power = intake_.controller().ff_U(0, 0);
702
Austin Schuhf59b6bc2016-03-11 21:26:19 -0800703 status->shoulder_controller_index = arm_.controller_index();
704
Austin Schuhbe041152016-02-28 22:01:52 -0800705 last_shoulder_angle_ = arm_.shoulder_angle();
706 last_wrist_angle_ = arm_.wrist_angle();
707 last_intake_angle_ = intake_.angle();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800708
709 status->estopped = (state_ == ESTOP);
710
711 status->state = state_;
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800712 status->is_collided = is_collided;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800713
Adam Snaider06779722016-02-14 15:26:22 -0800714 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800715}
716
Philipp Schrader3da48ac2016-03-06 23:03:44 +0000717constexpr double Superstructure::kZeroingVoltage;
718constexpr double Superstructure::kOperatingVoltage;
Austin Schuh6ca0f792016-03-12 14:06:14 -0800719constexpr double Superstructure::kLandingShoulderDownVoltage;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000720constexpr double Superstructure::kShoulderMiddleAngle;
721constexpr double Superstructure::kLooseTolerance;
722constexpr double Superstructure::kIntakeUpperClear;
723constexpr double Superstructure::kIntakeLowerClear;
724constexpr double Superstructure::kShoulderUpAngle;
725constexpr double Superstructure::kShoulderLanded;
726constexpr double Superstructure::kTightTolerance;
727constexpr double Superstructure::kWristAlmostLevel;
728constexpr double Superstructure::kShoulderWristClearAngle;
Austin Schuhb1d682b2016-02-16 13:07:44 -0800729constexpr double Superstructure::kShoulderTransitionToLanded;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000730
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800731} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000732} // namespace control_loops
733} // namespace y2016