blob: 9b87ff9ca64520b27c42fdb98a1cf26ea1db60ca [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;
Austin Schuh2d7820b2016-02-16 13:47:42 -080018// The maximum voltage the intake roller will be allowed to use.
19constexpr float kMaxIntakeVoltage = 8.0;
Comran Morshedf4cd7642016-02-15 20:40:49 +000020
Austin Schuh2d7820b2016-02-16 13:47:42 -080021// Aliases to reduce typing.
Adam Snaider06779722016-02-14 15:26:22 -080022constexpr double kIntakeEncoderIndexDifference =
23 constants::Values::kIntakeEncoderIndexDifference;
24constexpr double kWristEncoderIndexDifference =
25 constants::Values::kWristEncoderIndexDifference;
26constexpr double kShoulderEncoderIndexDifference =
27 constants::Values::kShoulderEncoderIndexDifference;
Austin Schuh2fc10fa2016-02-08 00:44:34 -080028} // namespace
29
Philipp Schrader0119eb12016-02-15 18:16:21 +000030// ///// CollisionAvoidance /////
31
32void CollisionAvoidance::UpdateGoal(double shoulder_angle_goal,
33 double wrist_angle_goal,
34 double intake_angle_goal) {
35 double shoulder_angle = arm_->shoulder_angle();
36 double wrist_angle = arm_->wrist_angle();
37 double intake_angle = intake_->angle();
38
39 // TODO(phil): This may need tuning to account for bounciness in the limbs or
40 // some other thing that I haven't thought of. At the very least,
41 // incorporating a small safety margin makes writing test cases much easier
42 // since you can directly compare statuses against the constants in the
43 // CollisionAvoidance class.
44 constexpr double kSafetyMargin = 0.01; // radians
45
46 // Avoid colliding the shooter with the frame.
47 // If the shoulder is below a certain angle or we want to move it below
48 // that angle, then the shooter has to stay level to the ground. Otherwise,
49 // it will crash into the frame.
50 if (shoulder_angle < kMinShoulderAngleForHorizontalShooter ||
51 shoulder_angle_goal < kMinShoulderAngleForHorizontalShooter) {
52 wrist_angle_goal = 0.0;
53
54 // Make sure that we don't move the shoulder below a certain angle until
55 // the wrist is level with the ground.
56 if (::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
57 shoulder_angle_goal =
58 ::std::max(shoulder_angle_goal,
59 kMinShoulderAngleForHorizontalShooter + kSafetyMargin);
60 }
61 }
62
63 // Is the arm where it could interfere with the intake right now?
64 bool shoulder_is_in_danger =
65 (shoulder_angle < kMinShoulderAngleForIntakeInterference &&
66 shoulder_angle > kMaxShoulderAngleUntilSafeIntakeStowing);
67
68 // Is the arm moving into collision zone from above?
69 bool shoulder_moving_into_danger_from_above =
70 (shoulder_angle >= kMinShoulderAngleForIntakeInterference &&
71 shoulder_angle_goal <= kMinShoulderAngleForIntakeInterference);
72
73 // Is the arm moving into collision zone from below?
74 bool shoulder_moving_into_danger_from_below =
75 (shoulder_angle <= kMaxShoulderAngleUntilSafeIntakeStowing &&
76 shoulder_angle_goal >= kMaxShoulderAngleUntilSafeIntakeStowing);
77
78 // Avoid colliding the arm with the intake.
79 if (shoulder_is_in_danger || shoulder_moving_into_danger_from_above ||
80 shoulder_moving_into_danger_from_below) {
81 // If the arm could collide with the intake, we make sure to move the
82 // intake out of the way. The arm has priority.
83 intake_angle_goal =
84 ::std::min(intake_angle_goal,
85 kMaxIntakeAngleBeforeArmInterference - kSafetyMargin);
86
87 // Don't let the shoulder move into the collision area until the intake is
88 // out of the way.
89 if (intake_angle > kMaxIntakeAngleBeforeArmInterference) {
90 const double kHalfwayPointBetweenSafeZones =
91 (kMinShoulderAngleForIntakeInterference +
92 kMaxShoulderAngleUntilSafeIntakeStowing) /
93 2.0;
94
95 if (shoulder_angle >= kHalfwayPointBetweenSafeZones) {
96 // The shoulder is closer to being above the collision area. Move it up
97 // there.
98 shoulder_angle_goal =
99 ::std::max(shoulder_angle_goal,
100 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
101 } else {
102 // The shoulder is closer to being below the collision zone (i.e. in
103 // stowing/intake position), keep it there for now.
104 shoulder_angle_goal =
105 ::std::min(shoulder_angle_goal,
106 kMaxShoulderAngleUntilSafeIntakeStowing - kSafetyMargin);
107 }
108 }
109 }
110
111 // Send the possibly adjusted goals to the components.
112 arm_->set_unprofiled_goal(shoulder_angle_goal, wrist_angle_goal);
113 intake_->set_unprofiled_goal(intake_angle_goal);
114}
115
Philipp Schrader07147532016-02-16 01:23:07 +0000116bool CollisionAvoidance::collided() const {
117 return collided_with_given_angles(arm_->shoulder_angle(), arm_->wrist_angle(),
118 intake_->angle());
119}
120
121bool CollisionAvoidance::collided_with_given_angles(double shoulder_angle,
122 double wrist_angle,
123 double intake_angle) {
124 // The arm and the intake must not hit.
125 if (shoulder_angle >=
126 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing &&
127 shoulder_angle <=
128 CollisionAvoidance::kMinShoulderAngleForIntakeInterference &&
129 intake_angle > CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference) {
Austin Schuhc85cdd52016-02-16 13:05:35 -0800130 LOG(DEBUG, "Collided: Intake %f > %f, and shoulder %f < %f < %f.\n", intake_angle,
131 CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference,
132 CollisionAvoidance::kMinShoulderAngleForIntakeInterference,
133 shoulder_angle,
134 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing);
Philipp Schrader07147532016-02-16 01:23:07 +0000135 return true;
136 }
137
138 // The wrist must go back to zero when the shoulder is moving the arm into
139 // a stowed/intaking position.
140 if (shoulder_angle <
141 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter &&
142 ::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
Austin Schuhc85cdd52016-02-16 13:05:35 -0800143 LOG(DEBUG, "Collided: Shoulder %f < %f and wrist |%f| < %f.\n",
144 shoulder_angle,
145 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter, wrist_angle,
146 kMaxWristAngleForSafeArmStowing);
Philipp Schrader07147532016-02-16 01:23:07 +0000147 return true;
148 }
149
150 return false;
151}
152
Philipp Schrader0119eb12016-02-15 18:16:21 +0000153constexpr double CollisionAvoidance::kMinShoulderAngleForHorizontalShooter;
154constexpr double CollisionAvoidance::kMinShoulderAngleForIntakeInterference;
155constexpr double CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference;
156constexpr double CollisionAvoidance::kMaxWristAngleForSafeArmStowing;
157constexpr double CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing;
158
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800159Superstructure::Superstructure(
160 control_loops::SuperstructureQueue *superstructure_queue)
161 : aos::controls::ControlLoop<control_loops::SuperstructureQueue>(
Philipp Schrader0119eb12016-02-15 18:16:21 +0000162 superstructure_queue),
163 collision_avoidance_(&intake_, &arm_) {}
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800164
Adam Snaider06779722016-02-14 15:26:22 -0800165bool Superstructure::IsArmNear(double shoulder_tolerance,
166 double wrist_tolerance) {
167 return ((arm_.unprofiled_goal() - arm_.X_hat())
168 .block<2, 1>(0, 0)
169 .lpNorm<Eigen::Infinity>() < shoulder_tolerance) &&
170 ((arm_.unprofiled_goal() - arm_.X_hat())
171 .block<4, 1>(0, 0)
172 .lpNorm<Eigen::Infinity>() < wrist_tolerance) &&
173 ((arm_.unprofiled_goal() - arm_.goal())
174 .block<4, 1>(0, 0)
175 .lpNorm<Eigen::Infinity>() < 1e-6);
176}
177
178bool Superstructure::IsArmNear(double tolerance) {
179 return ((arm_.unprofiled_goal() - arm_.X_hat())
180 .block<4, 1>(0, 0)
181 .lpNorm<Eigen::Infinity>() < tolerance) &&
182 ((arm_.unprofiled_goal() - arm_.goal())
183 .block<4, 1>(0, 0)
184 .lpNorm<Eigen::Infinity>() < 1e-6);
185}
186
187bool Superstructure::IsIntakeNear(double tolerance) {
188 return ((intake_.unprofiled_goal() - intake_.X_hat())
189 .block<2, 1>(0, 0)
190 .lpNorm<Eigen::Infinity>() < tolerance);
191}
192
193double Superstructure::MoveButKeepAbove(double reference_angle,
194 double current_angle,
195 double move_distance) {
196 return -MoveButKeepBelow(-reference_angle, -current_angle, -move_distance);
197}
198
199double Superstructure::MoveButKeepBelow(double reference_angle,
200 double current_angle,
201 double move_distance) {
202 // There are 3 interesting places to move to.
203 const double small_negative_move = current_angle - move_distance;
204 const double small_positive_move = current_angle + move_distance;
205 // And the reference angle.
206
207 // Move the the highest one that is below reference_angle.
208 if (small_negative_move > reference_angle) {
209 return reference_angle;
210 } else if (small_positive_move > reference_angle) {
211 return small_negative_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800212 } else {
Adam Snaider06779722016-02-14 15:26:22 -0800213 return small_positive_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800214 }
215}
216
217void Superstructure::RunIteration(
218 const control_loops::SuperstructureQueue::Goal *unsafe_goal,
219 const control_loops::SuperstructureQueue::Position *position,
220 control_loops::SuperstructureQueue::Output *output,
221 control_loops::SuperstructureQueue::Status *status) {
Adam Snaider06779722016-02-14 15:26:22 -0800222 const State state_before_switch = state_;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800223 if (WasReset()) {
224 LOG(ERROR, "WPILib reset, restarting\n");
225 arm_.Reset();
226 intake_.Reset();
227 state_ = UNINITIALIZED;
228 }
229
230 // Bool to track if we should turn the motors on or not.
231 bool disable = output == nullptr;
232
233 arm_.Correct(position->shoulder, position->wrist);
234 intake_.Correct(position->intake);
235
Adam Snaider06779722016-02-14 15:26:22 -0800236 // There are 2 main zeroing paths, HIGH_ARM_ZERO and LOW_ARM_ZERO.
237 //
238 // HIGH_ARM_ZERO works by lifting the arm all the way up so it is clear,
239 // moving the shooter to be horizontal, moving the intake out, and then moving
240 // the arm back down.
241 //
242 // LOW_ARM_ZERO works by moving the intake out of the way, lifting the arm up,
243 // leveling the shooter, and then moving back down.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800244
Diana Vandenberge2843c62016-02-13 17:44:20 -0800245 if (arm_.error() || intake_.error()) {
246 state_ = ESTOP;
247 }
248
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800249 switch (state_) {
250 case UNINITIALIZED:
Adam Snaider06779722016-02-14 15:26:22 -0800251 // Wait in the uninitialized state until both the arm and intake are
252 // initialized.
253 LOG(DEBUG, "Uninitialized, waiting for intake and arm\n");
254 if (arm_.initialized() && intake_.initialized()) {
255 state_ = DISABLED_INITIALIZED;
256 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800257 disable = true;
258 break;
259
Adam Snaider06779722016-02-14 15:26:22 -0800260 case DISABLED_INITIALIZED:
261 // Wait here until we are either fully zeroed while disabled, or we become
262 // enabled. At that point, figure out if we should HIGH_ARM_ZERO or
263 // LOW_ARM_ZERO.
264 if (disable) {
265 if (arm_.zeroed() && intake_.zeroed()) {
266 state_ = SLOW_RUNNING;
267 }
268 } else {
269 if (arm_.shoulder_angle() >= kShoulderMiddleAngle) {
270 state_ = HIGH_ARM_ZERO_LIFT_ARM;
271 } else {
272 state_ = LOW_ARM_ZERO_LOWER_INTAKE;
273 }
274 }
275
276 // Set the goals to where we are now so when we start back up, we don't
277 // jump.
278 intake_.ForceGoal(intake_.angle());
279 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
280 // Set up the profile to be the zeroing profile.
281 intake_.AdjustProfile(0.5, 10);
282 arm_.AdjustProfile(0.5, 10, 0.5, 10);
283
284 // We are not ready to start doing anything yet.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800285 disable = true;
Adam Snaider06779722016-02-14 15:26:22 -0800286 break;
287
288 case HIGH_ARM_ZERO_LIFT_ARM:
289 if (disable) {
290 state_ = DISABLED_INITIALIZED;
291 } else {
292 // Raise the shoulder up out of the way.
293 arm_.set_unprofiled_goal(kShoulderUpAngle, arm_.wrist_angle());
294 if (IsArmNear(kLooseTolerance)) {
295 // Close enough, start the next move.
296 state_ = HIGH_ARM_ZERO_LEVEL_SHOOTER;
297 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800298 }
299 break;
300
Adam Snaider06779722016-02-14 15:26:22 -0800301 case HIGH_ARM_ZERO_LEVEL_SHOOTER:
302 if (disable) {
303 state_ = DISABLED_INITIALIZED;
304 } else {
305 // Move the shooter to be level.
306 arm_.set_unprofiled_goal(kShoulderUpAngle, 0.0);
307
308 if (IsArmNear(kLooseTolerance)) {
309 // Close enough, start the next move.
310 state_ = HIGH_ARM_ZERO_MOVE_INTAKE_OUT;
311 }
312 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800313 break;
314
Adam Snaider06779722016-02-14 15:26:22 -0800315 case HIGH_ARM_ZERO_MOVE_INTAKE_OUT:
316 if (disable) {
317 state_ = DISABLED_INITIALIZED;
318 } else {
319 // If we were just asked to move the intake, make sure it moves far
320 // enough.
321 if (last_state_ != HIGH_ARM_ZERO_MOVE_INTAKE_OUT) {
322 intake_.set_unprofiled_goal(
323 MoveButKeepBelow(kIntakeUpperClear, intake_.angle(),
324 kIntakeEncoderIndexDifference * 2.5));
325 }
326
327 if (IsIntakeNear(kLooseTolerance)) {
328 // Close enough, start the next move.
329 state_ = HIGH_ARM_ZERO_LOWER_ARM;
330 }
331 }
332 break;
333
334 case HIGH_ARM_ZERO_LOWER_ARM:
335 if (disable) {
336 state_ = DISABLED_INITIALIZED;
337 } else {
338 // Land the shooter in the belly-pan. It should be zeroed by the time
339 // it gets there. If not, just estop.
340 arm_.set_unprofiled_goal(kShoulderLanded, 0.0);
341 if (arm_.zeroed() && intake_.zeroed()) {
342 state_ = RUNNING;
343 } else if (IsArmNear(kLooseTolerance)) {
344 LOG(ERROR,
345 "Failed to zero while executing the HIGH_ARM_ZERO sequence. "
346 "Arm: %d Intake %d\n",
347 arm_.zeroed(), intake_.zeroed());
348 state_ = ESTOP;
349 }
350 }
351 break;
352
353 case LOW_ARM_ZERO_LOWER_INTAKE:
354 if (disable) {
355 state_ = DISABLED_INITIALIZED;
356 } else {
357 // Move the intake down out of the way of the arm. Make sure to move it
358 // far enough to zero.
359 if (last_state_ != LOW_ARM_ZERO_LOWER_INTAKE) {
360 intake_.set_unprofiled_goal(
361 MoveButKeepBelow(kIntakeLowerClear, intake_.angle(),
362 kIntakeEncoderIndexDifference * 2.5));
363 }
364 if (IsIntakeNear(kLooseTolerance)) {
365 if (::std::abs(arm_.wrist_angle()) < kWristAlmostLevel) {
366 state_ = LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER;
367 } else {
368 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
369 }
370 }
371 }
372 break;
373
374 case LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER:
375 if (disable) {
376 state_ = DISABLED_INITIALIZED;
377 } else {
378 // If we are supposed to level the shooter, set it to level, and wait
379 // until it is very close to level.
380 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
381 if (IsArmNear(kLooseTolerance, kTightTolerance)) {
382 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
383 }
384 }
385 break;
386
387 case LOW_ARM_ZERO_LIFT_SHOULDER:
388 if (disable) {
389 state_ = DISABLED_INITIALIZED;
390 } else {
391 // Decide where to move to. We need to move far enough to see an index
392 // pulse, but must also get high enough that we can safely level the
393 // shooter.
394 if (last_state_ != LOW_ARM_ZERO_LIFT_SHOULDER) {
395 arm_.set_unprofiled_goal(
396 MoveButKeepAbove(kShoulderWristClearAngle, arm_.shoulder_angle(),
397 ::std::max(kWristEncoderIndexDifference,
398 kShoulderEncoderIndexDifference) *
399 2.5),
400 arm_.unprofiled_goal(2, 0));
401 }
402
403 // Wait until we are level and then go for it.
404 if (IsArmNear(kLooseTolerance)) {
405 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
406 }
407 }
408 break;
409
410 case LOW_ARM_ZERO_LEVEL_SHOOTER:
411 if (disable) {
412 state_ = DISABLED_INITIALIZED;
413 } else {
414 // Move the shooter level (and keep the same height). We don't want to
415 // got to RUNNING until we are completely level so that we don't
416 // give control back in a weird case where we might crash.
417 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
418 if (IsArmNear(kLooseTolerance)) {
419 if (arm_.zeroed() && intake_.zeroed()) {
420 state_ = RUNNING;
421 } else {
422 LOG(ERROR,
423 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
424 "Arm: %d Intake %d\n",
425 arm_.zeroed(), intake_.zeroed());
426 state_ = ESTOP;
427 }
428 }
429 }
430 break;
431
Austin Schuhb1d682b2016-02-16 13:07:44 -0800432 // These 4 cases are very similar.
Adam Snaider06779722016-02-14 15:26:22 -0800433 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800434 case RUNNING:
Austin Schuhb1d682b2016-02-16 13:07:44 -0800435 case LANDING_SLOW_RUNNING:
436 case LANDING_RUNNING: {
Austin Schuh829fe572016-02-14 21:41:16 -0800437 if (disable) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800438 // If we are disabled, go to slow running (or landing slow running) if
439 // we are collided.
440 if (collided()) {
441 if (state_ == RUNNING) {
442 state_ = SLOW_RUNNING;
443 } else if (state_ == LANDING_RUNNING) {
444 state_ = LANDING_SLOW_RUNNING;
445 }
446 }
Austin Schuh829fe572016-02-14 21:41:16 -0800447
Austin Schuhb1d682b2016-02-16 13:07:44 -0800448 // Reset the profile to the current position so it moves well from here.
Austin Schuh829fe572016-02-14 21:41:16 -0800449 intake_.ForceGoal(intake_.angle());
450 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
451 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800452 // If we are in slow_running and are no longer collided, let 'er rip.
Austin Schuh829fe572016-02-14 21:41:16 -0800453 if (state_ == SLOW_RUNNING) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800454 if (!collided()) {
455 state_ = RUNNING;
456 }
457 } else if (state_ == LANDING_SLOW_RUNNING) {
458 if (!collided()) {
459 state_ = LANDING_RUNNING;
460 }
Austin Schuh829fe572016-02-14 21:41:16 -0800461 }
462 }
463
Austin Schuhb1d682b2016-02-16 13:07:44 -0800464 double requested_shoulder = constants::Values::kShoulderRange.lower;
465 double requested_wrist = 0.0;
466 double requested_intake = M_PI / 2.0;
467
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800468 if (unsafe_goal) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800469 // If we are in one of the landing states, limit the accelerations and
470 // velocities to land cleanly.
471 if (state_ == LANDING_SLOW_RUNNING || state_ == LANDING_RUNNING) {
472 arm_.AdjustProfile(0.5, // Shoulder Velocity
473 4.0, // Shoulder acceleration,
474 4.0, // Wrist velocity
475 10.0); // Wrist acceleration.
476 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
477 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800478
Austin Schuhb1d682b2016-02-16 13:07:44 -0800479 requested_shoulder =
480 ::std::max(unsafe_goal->angle_shoulder,
481 constants::Values::kShoulderRange.lower);
482 requested_wrist = 0.0;
483 requested_intake = unsafe_goal->angle_intake;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000484 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800485 // Otherwise, give the user what he asked for.
486 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
487 unsafe_goal->max_angular_acceleration_shoulder,
488 unsafe_goal->max_angular_velocity_wrist,
489 unsafe_goal->max_angular_acceleration_wrist);
490 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
491 unsafe_goal->max_angular_acceleration_intake);
492
493 // Except, don't let the shoulder go all the way down.
494 requested_shoulder = ::std::max(unsafe_goal->angle_shoulder,
495 kShoulderTransitionToLanded);
496 requested_wrist = unsafe_goal->angle_wrist;
497 requested_intake = unsafe_goal->angle_intake;
498
499 // Transition to landing once the profile is close to finished for the
500 // shoulder.
501 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4) {
502 if (state_ == RUNNING) {
503 state_ = LANDING_RUNNING;
504 } else {
505 state_ = LANDING_SLOW_RUNNING;
506 }
507 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000508 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800509 }
510
Austin Schuhb1d682b2016-02-16 13:07:44 -0800511 // Push the request out to hardware!
512 collision_avoidance_.UpdateGoal(requested_shoulder, requested_wrist,
513 requested_intake);
514
515 // ESTOP if we hit the hard limits.
516 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
517 state_ = ESTOP;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800518 }
Austin Schuhb1d682b2016-02-16 13:07:44 -0800519 } break;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800520
521 case ESTOP:
522 LOG(ERROR, "Estop\n");
523 disable = true;
524 break;
525 }
526
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800527 // Set the voltage limits.
Austin Schuhb1d682b2016-02-16 13:07:44 -0800528 const double max_voltage =
529 (state_ == RUNNING || state_ == LANDING_RUNNING) ? 12.0 : kZeroingVoltage;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800530 arm_.set_max_voltage(max_voltage, max_voltage);
531 intake_.set_max_voltage(max_voltage);
532
533 // Calculate the loops for a cycle.
534 arm_.Update(disable);
535 intake_.Update(disable);
536
537 // Write out all the voltages.
538 if (output) {
539 output->voltage_intake = intake_.intake_voltage();
540 output->voltage_shoulder = arm_.shoulder_voltage();
541 output->voltage_wrist = arm_.wrist_voltage();
Comran Morshedf4cd7642016-02-15 20:40:49 +0000542
543 // Logic to run our rollers on the intake.
544 output->voltage_rollers = 0.0;
545 if (unsafe_goal) {
Austin Schuh2d7820b2016-02-16 13:47:42 -0800546 output->voltage_rollers = ::std::max(
547 -kMaxIntakeVoltage,
548 ::std::min(unsafe_goal->voltage_rollers, kMaxIntakeVoltage));
Comran Morshedf4cd7642016-02-15 20:40:49 +0000549 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800550 }
551
552 // Save debug/internal state.
553 // TODO(austin): Save the voltage errors.
Austin Schuhb1d682b2016-02-16 13:07:44 -0800554 status->zeroed = (state_ == RUNNING || state_ == LANDING_RUNNING ||
555 state_ == SLOW_RUNNING || state_ == LANDING_SLOW_RUNNING);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800556
557 status->shoulder.angle = arm_.X_hat(0, 0);
558 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
559 status->shoulder.goal_angle = arm_.goal(0, 0);
560 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800561 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
562 status->shoulder.unprofiled_goal_angular_velocity =
563 arm_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800564 status->shoulder.estimator_state = arm_.ShoulderEstimatorState();
565
566 status->wrist.angle = arm_.X_hat(2, 0);
567 status->wrist.angular_velocity = arm_.X_hat(3, 0);
568 status->wrist.goal_angle = arm_.goal(2, 0);
569 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800570 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
571 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800572 status->wrist.estimator_state = arm_.WristEstimatorState();
573
574 status->intake.angle = intake_.X_hat(0, 0);
575 status->intake.angular_velocity = intake_.X_hat(1, 0);
576 status->intake.goal_angle = intake_.goal(0, 0);
577 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800578 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
579 status->intake.unprofiled_goal_angular_velocity =
580 intake_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800581 status->intake.estimator_state = intake_.IntakeEstimatorState();
582
583 status->estopped = (state_ == ESTOP);
584
585 status->state = state_;
586
Adam Snaider06779722016-02-14 15:26:22 -0800587 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800588}
589
Philipp Schrader0119eb12016-02-15 18:16:21 +0000590constexpr double Superstructure::kShoulderMiddleAngle;
591constexpr double Superstructure::kLooseTolerance;
592constexpr double Superstructure::kIntakeUpperClear;
593constexpr double Superstructure::kIntakeLowerClear;
594constexpr double Superstructure::kShoulderUpAngle;
595constexpr double Superstructure::kShoulderLanded;
596constexpr double Superstructure::kTightTolerance;
597constexpr double Superstructure::kWristAlmostLevel;
598constexpr double Superstructure::kShoulderWristClearAngle;
Austin Schuhb1d682b2016-02-16 13:07:44 -0800599constexpr double Superstructure::kShoulderTransitionToLanded;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000600
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800601} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000602} // namespace control_loops
603} // namespace y2016