blob: 759c5cae1bfe505cba3520e9a0f94ddbec0dde49 [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
4#include "aos/common/controls/control_loops.q.h"
5#include "aos/common/logging/logging.h"
6
Austin Schuh2fc10fa2016-02-08 00:44:34 -08007#include "y2016/control_loops/superstructure/integral_intake_plant.h"
8#include "y2016/control_loops/superstructure/integral_arm_plant.h"
9
10#include "y2016/constants.h"
11
Comran Morshed25f81a02016-01-23 13:40:10 +000012namespace y2016 {
13namespace control_loops {
Austin Schuh2fc10fa2016-02-08 00:44:34 -080014namespace superstructure {
Comran Morshed25f81a02016-01-23 13:40:10 +000015
Austin Schuh2fc10fa2016-02-08 00:44:34 -080016namespace {
Comran Morshedf4cd7642016-02-15 20:40:49 +000017constexpr double kZeroingVoltage = 4.0;
Comran Morshed011f7d92016-02-16 23:03:06 +000018constexpr double kOperatingVoltage = 12.0;
19constexpr double kLandingShoulderDownVoltage = -2.0;
Austin Schuh2d7820b2016-02-16 13:47:42 -080020// The maximum voltage the intake roller will be allowed to use.
21constexpr float kMaxIntakeVoltage = 8.0;
Comran Morshedf4cd7642016-02-15 20:40:49 +000022
Austin Schuh2d7820b2016-02-16 13:47:42 -080023// Aliases to reduce typing.
Adam Snaider06779722016-02-14 15:26:22 -080024constexpr double kIntakeEncoderIndexDifference =
25 constants::Values::kIntakeEncoderIndexDifference;
26constexpr double kWristEncoderIndexDifference =
27 constants::Values::kWristEncoderIndexDifference;
28constexpr double kShoulderEncoderIndexDifference =
29 constants::Values::kShoulderEncoderIndexDifference;
Austin Schuh2fc10fa2016-02-08 00:44:34 -080030} // namespace
31
Philipp Schrader0119eb12016-02-15 18:16:21 +000032// ///// CollisionAvoidance /////
33
34void CollisionAvoidance::UpdateGoal(double shoulder_angle_goal,
35 double wrist_angle_goal,
36 double intake_angle_goal) {
37 double shoulder_angle = arm_->shoulder_angle();
38 double wrist_angle = arm_->wrist_angle();
39 double intake_angle = intake_->angle();
40
41 // TODO(phil): This may need tuning to account for bounciness in the limbs or
42 // some other thing that I haven't thought of. At the very least,
43 // incorporating a small safety margin makes writing test cases much easier
44 // since you can directly compare statuses against the constants in the
45 // CollisionAvoidance class.
46 constexpr double kSafetyMargin = 0.01; // radians
47
48 // Avoid colliding the shooter with the frame.
49 // If the shoulder is below a certain angle or we want to move it below
50 // that angle, then the shooter has to stay level to the ground. Otherwise,
51 // it will crash into the frame.
52 if (shoulder_angle < kMinShoulderAngleForHorizontalShooter ||
53 shoulder_angle_goal < kMinShoulderAngleForHorizontalShooter) {
54 wrist_angle_goal = 0.0;
55
56 // Make sure that we don't move the shoulder below a certain angle until
57 // the wrist is level with the ground.
58 if (::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
59 shoulder_angle_goal =
60 ::std::max(shoulder_angle_goal,
61 kMinShoulderAngleForHorizontalShooter + kSafetyMargin);
62 }
63 }
64
65 // Is the arm where it could interfere with the intake right now?
66 bool shoulder_is_in_danger =
67 (shoulder_angle < kMinShoulderAngleForIntakeInterference &&
68 shoulder_angle > kMaxShoulderAngleUntilSafeIntakeStowing);
69
70 // Is the arm moving into collision zone from above?
71 bool shoulder_moving_into_danger_from_above =
72 (shoulder_angle >= kMinShoulderAngleForIntakeInterference &&
73 shoulder_angle_goal <= kMinShoulderAngleForIntakeInterference);
74
75 // Is the arm moving into collision zone from below?
76 bool shoulder_moving_into_danger_from_below =
77 (shoulder_angle <= kMaxShoulderAngleUntilSafeIntakeStowing &&
78 shoulder_angle_goal >= kMaxShoulderAngleUntilSafeIntakeStowing);
79
80 // Avoid colliding the arm with the intake.
81 if (shoulder_is_in_danger || shoulder_moving_into_danger_from_above ||
82 shoulder_moving_into_danger_from_below) {
83 // If the arm could collide with the intake, we make sure to move the
84 // intake out of the way. The arm has priority.
85 intake_angle_goal =
86 ::std::min(intake_angle_goal,
87 kMaxIntakeAngleBeforeArmInterference - kSafetyMargin);
88
89 // Don't let the shoulder move into the collision area until the intake is
90 // out of the way.
91 if (intake_angle > kMaxIntakeAngleBeforeArmInterference) {
92 const double kHalfwayPointBetweenSafeZones =
93 (kMinShoulderAngleForIntakeInterference +
94 kMaxShoulderAngleUntilSafeIntakeStowing) /
95 2.0;
96
97 if (shoulder_angle >= kHalfwayPointBetweenSafeZones) {
98 // The shoulder is closer to being above the collision area. Move it up
99 // there.
100 shoulder_angle_goal =
101 ::std::max(shoulder_angle_goal,
102 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
103 } else {
104 // The shoulder is closer to being below the collision zone (i.e. in
105 // stowing/intake position), keep it there for now.
106 shoulder_angle_goal =
107 ::std::min(shoulder_angle_goal,
108 kMaxShoulderAngleUntilSafeIntakeStowing - kSafetyMargin);
109 }
110 }
111 }
112
113 // Send the possibly adjusted goals to the components.
114 arm_->set_unprofiled_goal(shoulder_angle_goal, wrist_angle_goal);
115 intake_->set_unprofiled_goal(intake_angle_goal);
116}
117
Philipp Schrader07147532016-02-16 01:23:07 +0000118bool CollisionAvoidance::collided() const {
119 return collided_with_given_angles(arm_->shoulder_angle(), arm_->wrist_angle(),
120 intake_->angle());
121}
122
123bool CollisionAvoidance::collided_with_given_angles(double shoulder_angle,
124 double wrist_angle,
125 double intake_angle) {
126 // The arm and the intake must not hit.
127 if (shoulder_angle >=
128 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing &&
129 shoulder_angle <=
130 CollisionAvoidance::kMinShoulderAngleForIntakeInterference &&
131 intake_angle > CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference) {
Austin Schuhc85cdd52016-02-16 13:05:35 -0800132 LOG(DEBUG, "Collided: Intake %f > %f, and shoulder %f < %f < %f.\n", intake_angle,
133 CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference,
134 CollisionAvoidance::kMinShoulderAngleForIntakeInterference,
135 shoulder_angle,
136 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing);
Philipp Schrader07147532016-02-16 01:23:07 +0000137 return true;
138 }
139
140 // The wrist must go back to zero when the shoulder is moving the arm into
141 // a stowed/intaking position.
142 if (shoulder_angle <
143 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter &&
144 ::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
Austin Schuhc85cdd52016-02-16 13:05:35 -0800145 LOG(DEBUG, "Collided: Shoulder %f < %f and wrist |%f| < %f.\n",
146 shoulder_angle,
147 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter, wrist_angle,
148 kMaxWristAngleForSafeArmStowing);
Philipp Schrader07147532016-02-16 01:23:07 +0000149 return true;
150 }
151
152 return false;
153}
154
Philipp Schrader0119eb12016-02-15 18:16:21 +0000155constexpr double CollisionAvoidance::kMinShoulderAngleForHorizontalShooter;
156constexpr double CollisionAvoidance::kMinShoulderAngleForIntakeInterference;
157constexpr double CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference;
158constexpr double CollisionAvoidance::kMaxWristAngleForSafeArmStowing;
159constexpr double CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing;
160
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800161Superstructure::Superstructure(
162 control_loops::SuperstructureQueue *superstructure_queue)
163 : aos::controls::ControlLoop<control_loops::SuperstructureQueue>(
Philipp Schrader0119eb12016-02-15 18:16:21 +0000164 superstructure_queue),
165 collision_avoidance_(&intake_, &arm_) {}
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800166
Adam Snaider06779722016-02-14 15:26:22 -0800167bool Superstructure::IsArmNear(double shoulder_tolerance,
168 double wrist_tolerance) {
169 return ((arm_.unprofiled_goal() - arm_.X_hat())
170 .block<2, 1>(0, 0)
171 .lpNorm<Eigen::Infinity>() < shoulder_tolerance) &&
172 ((arm_.unprofiled_goal() - arm_.X_hat())
173 .block<4, 1>(0, 0)
174 .lpNorm<Eigen::Infinity>() < wrist_tolerance) &&
175 ((arm_.unprofiled_goal() - arm_.goal())
176 .block<4, 1>(0, 0)
177 .lpNorm<Eigen::Infinity>() < 1e-6);
178}
179
180bool Superstructure::IsArmNear(double tolerance) {
181 return ((arm_.unprofiled_goal() - arm_.X_hat())
182 .block<4, 1>(0, 0)
183 .lpNorm<Eigen::Infinity>() < tolerance) &&
184 ((arm_.unprofiled_goal() - arm_.goal())
185 .block<4, 1>(0, 0)
186 .lpNorm<Eigen::Infinity>() < 1e-6);
187}
188
189bool Superstructure::IsIntakeNear(double tolerance) {
190 return ((intake_.unprofiled_goal() - intake_.X_hat())
191 .block<2, 1>(0, 0)
192 .lpNorm<Eigen::Infinity>() < tolerance);
193}
194
195double Superstructure::MoveButKeepAbove(double reference_angle,
196 double current_angle,
197 double move_distance) {
198 return -MoveButKeepBelow(-reference_angle, -current_angle, -move_distance);
199}
200
201double Superstructure::MoveButKeepBelow(double reference_angle,
202 double current_angle,
203 double move_distance) {
204 // There are 3 interesting places to move to.
205 const double small_negative_move = current_angle - move_distance;
206 const double small_positive_move = current_angle + move_distance;
207 // And the reference angle.
208
209 // Move the the highest one that is below reference_angle.
210 if (small_negative_move > reference_angle) {
211 return reference_angle;
212 } else if (small_positive_move > reference_angle) {
213 return small_negative_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800214 } else {
Adam Snaider06779722016-02-14 15:26:22 -0800215 return small_positive_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800216 }
217}
218
219void Superstructure::RunIteration(
220 const control_loops::SuperstructureQueue::Goal *unsafe_goal,
221 const control_loops::SuperstructureQueue::Position *position,
222 control_loops::SuperstructureQueue::Output *output,
223 control_loops::SuperstructureQueue::Status *status) {
Adam Snaider06779722016-02-14 15:26:22 -0800224 const State state_before_switch = state_;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800225 if (WasReset()) {
226 LOG(ERROR, "WPILib reset, restarting\n");
227 arm_.Reset();
228 intake_.Reset();
229 state_ = UNINITIALIZED;
230 }
231
232 // Bool to track if we should turn the motors on or not.
233 bool disable = output == nullptr;
234
235 arm_.Correct(position->shoulder, position->wrist);
236 intake_.Correct(position->intake);
237
Adam Snaider06779722016-02-14 15:26:22 -0800238 // There are 2 main zeroing paths, HIGH_ARM_ZERO and LOW_ARM_ZERO.
239 //
240 // HIGH_ARM_ZERO works by lifting the arm all the way up so it is clear,
241 // moving the shooter to be horizontal, moving the intake out, and then moving
242 // the arm back down.
243 //
244 // LOW_ARM_ZERO works by moving the intake out of the way, lifting the arm up,
245 // leveling the shooter, and then moving back down.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800246
Diana Vandenberge2843c62016-02-13 17:44:20 -0800247 if (arm_.error() || intake_.error()) {
248 state_ = ESTOP;
249 }
250
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800251 switch (state_) {
252 case UNINITIALIZED:
Adam Snaider06779722016-02-14 15:26:22 -0800253 // Wait in the uninitialized state until both the arm and intake are
254 // initialized.
255 LOG(DEBUG, "Uninitialized, waiting for intake and arm\n");
256 if (arm_.initialized() && intake_.initialized()) {
257 state_ = DISABLED_INITIALIZED;
258 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800259 disable = true;
260 break;
261
Adam Snaider06779722016-02-14 15:26:22 -0800262 case DISABLED_INITIALIZED:
263 // Wait here until we are either fully zeroed while disabled, or we become
264 // enabled. At that point, figure out if we should HIGH_ARM_ZERO or
265 // LOW_ARM_ZERO.
266 if (disable) {
267 if (arm_.zeroed() && intake_.zeroed()) {
268 state_ = SLOW_RUNNING;
269 }
270 } else {
271 if (arm_.shoulder_angle() >= kShoulderMiddleAngle) {
272 state_ = HIGH_ARM_ZERO_LIFT_ARM;
273 } else {
274 state_ = LOW_ARM_ZERO_LOWER_INTAKE;
275 }
276 }
277
278 // Set the goals to where we are now so when we start back up, we don't
279 // jump.
280 intake_.ForceGoal(intake_.angle());
281 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
282 // Set up the profile to be the zeroing profile.
283 intake_.AdjustProfile(0.5, 10);
284 arm_.AdjustProfile(0.5, 10, 0.5, 10);
285
286 // We are not ready to start doing anything yet.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800287 disable = true;
Adam Snaider06779722016-02-14 15:26:22 -0800288 break;
289
290 case HIGH_ARM_ZERO_LIFT_ARM:
291 if (disable) {
292 state_ = DISABLED_INITIALIZED;
293 } else {
294 // Raise the shoulder up out of the way.
295 arm_.set_unprofiled_goal(kShoulderUpAngle, arm_.wrist_angle());
296 if (IsArmNear(kLooseTolerance)) {
297 // Close enough, start the next move.
298 state_ = HIGH_ARM_ZERO_LEVEL_SHOOTER;
299 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800300 }
301 break;
302
Adam Snaider06779722016-02-14 15:26:22 -0800303 case HIGH_ARM_ZERO_LEVEL_SHOOTER:
304 if (disable) {
305 state_ = DISABLED_INITIALIZED;
306 } else {
307 // Move the shooter to be level.
308 arm_.set_unprofiled_goal(kShoulderUpAngle, 0.0);
309
310 if (IsArmNear(kLooseTolerance)) {
311 // Close enough, start the next move.
312 state_ = HIGH_ARM_ZERO_MOVE_INTAKE_OUT;
313 }
314 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800315 break;
316
Adam Snaider06779722016-02-14 15:26:22 -0800317 case HIGH_ARM_ZERO_MOVE_INTAKE_OUT:
318 if (disable) {
319 state_ = DISABLED_INITIALIZED;
320 } else {
321 // If we were just asked to move the intake, make sure it moves far
322 // enough.
323 if (last_state_ != HIGH_ARM_ZERO_MOVE_INTAKE_OUT) {
324 intake_.set_unprofiled_goal(
325 MoveButKeepBelow(kIntakeUpperClear, intake_.angle(),
326 kIntakeEncoderIndexDifference * 2.5));
327 }
328
329 if (IsIntakeNear(kLooseTolerance)) {
330 // Close enough, start the next move.
331 state_ = HIGH_ARM_ZERO_LOWER_ARM;
332 }
333 }
334 break;
335
336 case HIGH_ARM_ZERO_LOWER_ARM:
337 if (disable) {
338 state_ = DISABLED_INITIALIZED;
339 } else {
340 // Land the shooter in the belly-pan. It should be zeroed by the time
341 // it gets there. If not, just estop.
342 arm_.set_unprofiled_goal(kShoulderLanded, 0.0);
343 if (arm_.zeroed() && intake_.zeroed()) {
344 state_ = RUNNING;
345 } else if (IsArmNear(kLooseTolerance)) {
346 LOG(ERROR,
347 "Failed to zero while executing the HIGH_ARM_ZERO sequence. "
348 "Arm: %d Intake %d\n",
349 arm_.zeroed(), intake_.zeroed());
350 state_ = ESTOP;
351 }
352 }
353 break;
354
355 case LOW_ARM_ZERO_LOWER_INTAKE:
356 if (disable) {
357 state_ = DISABLED_INITIALIZED;
358 } else {
359 // Move the intake down out of the way of the arm. Make sure to move it
360 // far enough to zero.
361 if (last_state_ != LOW_ARM_ZERO_LOWER_INTAKE) {
362 intake_.set_unprofiled_goal(
363 MoveButKeepBelow(kIntakeLowerClear, intake_.angle(),
364 kIntakeEncoderIndexDifference * 2.5));
365 }
366 if (IsIntakeNear(kLooseTolerance)) {
367 if (::std::abs(arm_.wrist_angle()) < kWristAlmostLevel) {
368 state_ = LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER;
369 } else {
370 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
371 }
372 }
373 }
374 break;
375
376 case LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER:
377 if (disable) {
378 state_ = DISABLED_INITIALIZED;
379 } else {
380 // If we are supposed to level the shooter, set it to level, and wait
381 // until it is very close to level.
382 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
383 if (IsArmNear(kLooseTolerance, kTightTolerance)) {
384 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
385 }
386 }
387 break;
388
389 case LOW_ARM_ZERO_LIFT_SHOULDER:
390 if (disable) {
391 state_ = DISABLED_INITIALIZED;
392 } else {
393 // Decide where to move to. We need to move far enough to see an index
394 // pulse, but must also get high enough that we can safely level the
395 // shooter.
396 if (last_state_ != LOW_ARM_ZERO_LIFT_SHOULDER) {
397 arm_.set_unprofiled_goal(
398 MoveButKeepAbove(kShoulderWristClearAngle, arm_.shoulder_angle(),
399 ::std::max(kWristEncoderIndexDifference,
400 kShoulderEncoderIndexDifference) *
401 2.5),
402 arm_.unprofiled_goal(2, 0));
403 }
404
405 // Wait until we are level and then go for it.
406 if (IsArmNear(kLooseTolerance)) {
407 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
408 }
409 }
410 break;
411
412 case LOW_ARM_ZERO_LEVEL_SHOOTER:
413 if (disable) {
414 state_ = DISABLED_INITIALIZED;
415 } else {
416 // Move the shooter level (and keep the same height). We don't want to
417 // got to RUNNING until we are completely level so that we don't
418 // give control back in a weird case where we might crash.
419 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
420 if (IsArmNear(kLooseTolerance)) {
421 if (arm_.zeroed() && intake_.zeroed()) {
422 state_ = RUNNING;
423 } else {
424 LOG(ERROR,
425 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
426 "Arm: %d Intake %d\n",
427 arm_.zeroed(), intake_.zeroed());
428 state_ = ESTOP;
429 }
430 }
431 }
432 break;
433
Austin Schuhb1d682b2016-02-16 13:07:44 -0800434 // These 4 cases are very similar.
Adam Snaider06779722016-02-14 15:26:22 -0800435 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800436 case RUNNING:
Austin Schuhb1d682b2016-02-16 13:07:44 -0800437 case LANDING_SLOW_RUNNING:
438 case LANDING_RUNNING: {
Austin Schuh829fe572016-02-14 21:41:16 -0800439 if (disable) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800440 // If we are disabled, go to slow running (or landing slow running) if
441 // we are collided.
442 if (collided()) {
443 if (state_ == RUNNING) {
444 state_ = SLOW_RUNNING;
445 } else if (state_ == LANDING_RUNNING) {
446 state_ = LANDING_SLOW_RUNNING;
447 }
448 }
Austin Schuh829fe572016-02-14 21:41:16 -0800449
Austin Schuhb1d682b2016-02-16 13:07:44 -0800450 // Reset the profile to the current position so it moves well from here.
Austin Schuh829fe572016-02-14 21:41:16 -0800451 intake_.ForceGoal(intake_.angle());
452 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
453 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800454 // If we are in slow_running and are no longer collided, let 'er rip.
Austin Schuh829fe572016-02-14 21:41:16 -0800455 if (state_ == SLOW_RUNNING) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800456 if (!collided()) {
457 state_ = RUNNING;
458 }
459 } else if (state_ == LANDING_SLOW_RUNNING) {
460 if (!collided()) {
461 state_ = LANDING_RUNNING;
462 }
Austin Schuh829fe572016-02-14 21:41:16 -0800463 }
464 }
465
Austin Schuhb1d682b2016-02-16 13:07:44 -0800466 double requested_shoulder = constants::Values::kShoulderRange.lower;
467 double requested_wrist = 0.0;
468 double requested_intake = M_PI / 2.0;
469
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800470 if (unsafe_goal) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800471 // If we are in one of the landing states, limit the accelerations and
472 // velocities to land cleanly.
473 if (state_ == LANDING_SLOW_RUNNING || state_ == LANDING_RUNNING) {
474 arm_.AdjustProfile(0.5, // Shoulder Velocity
475 4.0, // Shoulder acceleration,
476 4.0, // Wrist velocity
477 10.0); // Wrist acceleration.
478 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
479 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800480
Austin Schuhb1d682b2016-02-16 13:07:44 -0800481 requested_shoulder =
482 ::std::max(unsafe_goal->angle_shoulder,
483 constants::Values::kShoulderRange.lower);
484 requested_wrist = 0.0;
485 requested_intake = unsafe_goal->angle_intake;
Austin Schuha1dd5272016-02-16 15:39:38 -0800486 // Transition to landing once the profile is close to finished for the
487 // shoulder.
488 if (arm_.goal(0, 0) > kShoulderTransitionToLanded + 1e-4 ||
489 arm_.unprofiled_goal(0, 0) > kShoulderTransitionToLanded + 1e-4) {
490 if (state_ == LANDING_RUNNING) {
491 state_ = RUNNING;
492 } else {
493 state_ = SLOW_RUNNING;
494 }
495 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000496 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800497 // Otherwise, give the user what he asked for.
498 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
499 unsafe_goal->max_angular_acceleration_shoulder,
500 unsafe_goal->max_angular_velocity_wrist,
501 unsafe_goal->max_angular_acceleration_wrist);
502 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
503 unsafe_goal->max_angular_acceleration_intake);
504
505 // Except, don't let the shoulder go all the way down.
506 requested_shoulder = ::std::max(unsafe_goal->angle_shoulder,
507 kShoulderTransitionToLanded);
508 requested_wrist = unsafe_goal->angle_wrist;
509 requested_intake = unsafe_goal->angle_intake;
510
511 // Transition to landing once the profile is close to finished for the
512 // shoulder.
Austin Schuha1dd5272016-02-16 15:39:38 -0800513 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 &&
514 arm_.unprofiled_goal(0, 0) <=
515 kShoulderTransitionToLanded + 1e-4) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800516 if (state_ == RUNNING) {
517 state_ = LANDING_RUNNING;
518 } else {
519 state_ = LANDING_SLOW_RUNNING;
520 }
521 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000522 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800523 }
524
Austin Schuhb1d682b2016-02-16 13:07:44 -0800525 // Push the request out to hardware!
526 collision_avoidance_.UpdateGoal(requested_shoulder, requested_wrist,
527 requested_intake);
528
529 // ESTOP if we hit the hard limits.
530 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
531 state_ = ESTOP;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800532 }
Austin Schuhb1d682b2016-02-16 13:07:44 -0800533 } break;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800534
535 case ESTOP:
536 LOG(ERROR, "Estop\n");
537 disable = true;
538 break;
539 }
540
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800541 // Set the voltage limits.
Comran Morshed011f7d92016-02-16 23:03:06 +0000542 const double max_voltage = (state_ == RUNNING || state_ == LANDING_RUNNING)
543 ? kOperatingVoltage
544 : kZeroingVoltage;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800545 arm_.set_max_voltage(max_voltage, max_voltage);
546 intake_.set_max_voltage(max_voltage);
547
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800548 if (IsRunning()) {
549 // We don't want lots of negative voltage when we are near the bellypan on
550 // the shoulder...
551 // TODO(austin): Do I want to push negative power into the belly pan at this
552 // point? Maybe just put the goal slightly below the bellypan and call that
553 // good enough.
554 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4) {
Comran Morshed011f7d92016-02-16 23:03:06 +0000555 arm_.set_shoulder_asymetric_limits(kLandingShoulderDownVoltage,
556 max_voltage);
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800557 }
558 }
559
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800560 // Calculate the loops for a cycle.
561 arm_.Update(disable);
562 intake_.Update(disable);
563
564 // Write out all the voltages.
565 if (output) {
566 output->voltage_intake = intake_.intake_voltage();
567 output->voltage_shoulder = arm_.shoulder_voltage();
568 output->voltage_wrist = arm_.wrist_voltage();
Comran Morshedf4cd7642016-02-15 20:40:49 +0000569
570 // Logic to run our rollers on the intake.
571 output->voltage_rollers = 0.0;
572 if (unsafe_goal) {
Austin Schuh2d7820b2016-02-16 13:47:42 -0800573 output->voltage_rollers = ::std::max(
574 -kMaxIntakeVoltage,
575 ::std::min(unsafe_goal->voltage_rollers, kMaxIntakeVoltage));
Comran Morshedf4cd7642016-02-15 20:40:49 +0000576 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800577 }
578
579 // Save debug/internal state.
580 // TODO(austin): Save the voltage errors.
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800581 status->zeroed = IsRunning();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800582
583 status->shoulder.angle = arm_.X_hat(0, 0);
584 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
585 status->shoulder.goal_angle = arm_.goal(0, 0);
586 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800587 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
588 status->shoulder.unprofiled_goal_angular_velocity =
589 arm_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800590 status->shoulder.estimator_state = arm_.ShoulderEstimatorState();
591
592 status->wrist.angle = arm_.X_hat(2, 0);
593 status->wrist.angular_velocity = arm_.X_hat(3, 0);
594 status->wrist.goal_angle = arm_.goal(2, 0);
595 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800596 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
597 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800598 status->wrist.estimator_state = arm_.WristEstimatorState();
599
600 status->intake.angle = intake_.X_hat(0, 0);
601 status->intake.angular_velocity = intake_.X_hat(1, 0);
602 status->intake.goal_angle = intake_.goal(0, 0);
603 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800604 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
605 status->intake.unprofiled_goal_angular_velocity =
606 intake_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800607 status->intake.estimator_state = intake_.IntakeEstimatorState();
608
609 status->estopped = (state_ == ESTOP);
610
611 status->state = state_;
612
Adam Snaider06779722016-02-14 15:26:22 -0800613 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800614}
615
Philipp Schrader0119eb12016-02-15 18:16:21 +0000616constexpr double Superstructure::kShoulderMiddleAngle;
617constexpr double Superstructure::kLooseTolerance;
618constexpr double Superstructure::kIntakeUpperClear;
619constexpr double Superstructure::kIntakeLowerClear;
620constexpr double Superstructure::kShoulderUpAngle;
621constexpr double Superstructure::kShoulderLanded;
622constexpr double Superstructure::kTightTolerance;
623constexpr double Superstructure::kWristAlmostLevel;
624constexpr double Superstructure::kShoulderWristClearAngle;
Austin Schuhb1d682b2016-02-16 13:07:44 -0800625constexpr double Superstructure::kShoulderTransitionToLanded;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000626
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800627} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000628} // namespace control_loops
629} // namespace y2016