blob: 99a130c48657eba7ae7698af0a3d17bd2cf195cf [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
John Park33858a32018-09-28 23:05:48 -07004#include "aos/commonmath.h"
5#include "aos/controls/control_loops.q.h"
6#include "aos/logging/logging.h"
Comran Morshed25f81a02016-01-23 13:40:10 +00007
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"
Austin Schuh1defd262016-04-03 16:13:32 -070010#include "y2016/queues/ball_detector.q.h"
Austin Schuh2fc10fa2016-02-08 00:44:34 -080011
12#include "y2016/constants.h"
13
Comran Morshed25f81a02016-01-23 13:40:10 +000014namespace y2016 {
15namespace control_loops {
Austin Schuh2fc10fa2016-02-08 00:44:34 -080016namespace superstructure {
Comran Morshed25f81a02016-01-23 13:40:10 +000017
Austin Schuh2fc10fa2016-02-08 00:44:34 -080018namespace {
Austin Schuh2d7820b2016-02-16 13:47:42 -080019// The maximum voltage the intake roller will be allowed to use.
Austin Schuhbe041152016-02-28 22:01:52 -080020constexpr float kMaxIntakeTopVoltage = 12.0;
21constexpr float kMaxIntakeBottomVoltage = 12.0;
Comran Morshed71466fe2016-04-21 20:21:14 -070022constexpr float kMaxClimberVoltage = 12.0;
Comran Morshedf4cd7642016-02-15 20:40:49 +000023
Austin Schuh2d7820b2016-02-16 13:47:42 -080024// Aliases to reduce typing.
Adam Snaider06779722016-02-14 15:26:22 -080025constexpr double kIntakeEncoderIndexDifference =
26 constants::Values::kIntakeEncoderIndexDifference;
27constexpr double kWristEncoderIndexDifference =
28 constants::Values::kWristEncoderIndexDifference;
29constexpr double kShoulderEncoderIndexDifference =
30 constants::Values::kShoulderEncoderIndexDifference;
Austin Schuh2fc10fa2016-02-08 00:44:34 -080031} // namespace
32
Philipp Schrader0119eb12016-02-15 18:16:21 +000033// ///// CollisionAvoidance /////
34
35void CollisionAvoidance::UpdateGoal(double shoulder_angle_goal,
36 double wrist_angle_goal,
37 double intake_angle_goal) {
Austin Schuh6ca0f792016-03-12 14:06:14 -080038 const double original_shoulder_angle_goal = shoulder_angle_goal;
39 const double original_intake_angle_goal = intake_angle_goal;
Austin Schuh2c717862016-03-13 15:32:53 -070040 const double original_wrist_angle_goal = wrist_angle_goal;
Austin Schuh6ca0f792016-03-12 14:06:14 -080041
Philipp Schrader0119eb12016-02-15 18:16:21 +000042 double shoulder_angle = arm_->shoulder_angle();
43 double wrist_angle = arm_->wrist_angle();
Austin Schuh3634ed32017-02-05 16:28:49 -080044 double intake_angle = intake_->position();
Philipp Schrader0119eb12016-02-15 18:16:21 +000045
46 // TODO(phil): This may need tuning to account for bounciness in the limbs or
47 // some other thing that I haven't thought of. At the very least,
48 // incorporating a small safety margin makes writing test cases much easier
49 // since you can directly compare statuses against the constants in the
50 // CollisionAvoidance class.
Austin Schuhfef64ac2016-04-24 19:08:01 -070051 constexpr double kSafetyMargin = 0.03; // radians
Philipp Schrader0119eb12016-02-15 18:16:21 +000052
53 // Avoid colliding the shooter with the frame.
54 // If the shoulder is below a certain angle or we want to move it below
55 // that angle, then the shooter has to stay level to the ground. Otherwise,
56 // it will crash into the frame.
Austin Schuh2c717862016-03-13 15:32:53 -070057 if (intake_angle < kMaxIntakeAngleBeforeArmInterference + kSafetyMargin) {
58 if (shoulder_angle < kMinShoulderAngleForHorizontalShooter ||
59 original_shoulder_angle_goal < kMinShoulderAngleForHorizontalShooter) {
60 wrist_angle_goal = 0.0;
61 } else if (shoulder_angle < kMinShoulderAngleForIntakeInterference ||
62 original_shoulder_angle_goal <
63 kMinShoulderAngleForIntakeInterference) {
64 wrist_angle_goal =
65 aos::Clip(original_wrist_angle_goal,
66 kMinWristAngleForMovingByIntake + kSafetyMargin,
67 kMaxWristAngleForMovingByIntake - kSafetyMargin);
68 }
69 } else {
70 if (shoulder_angle < kMinShoulderAngleForIntakeUpInterference ||
71 original_shoulder_angle_goal <
72 kMinShoulderAngleForIntakeUpInterference) {
73 wrist_angle_goal = 0.0;
74 }
75 }
76
Austin Schuh6ca0f792016-03-12 14:06:14 -080077 if (shoulder_angle < kMinShoulderAngleForIntakeUpInterference ||
78 original_shoulder_angle_goal < kMinShoulderAngleForIntakeUpInterference) {
Philipp Schrader0119eb12016-02-15 18:16:21 +000079 // Make sure that we don't move the shoulder below a certain angle until
80 // the wrist is level with the ground.
Austin Schuh6802a9d2016-03-12 21:34:53 -080081 if (intake_angle < kMaxIntakeAngleBeforeArmInterference + kSafetyMargin) {
Austin Schuh2c717862016-03-13 15:32:53 -070082 if (wrist_angle > kMaxWristAngleForMovingByIntake ||
83 wrist_angle < kMinWristAngleForMovingByIntake) {
Austin Schuh6802a9d2016-03-12 21:34:53 -080084 shoulder_angle_goal =
Comran Morshed71466fe2016-04-21 20:21:14 -070085 ::std::max(original_shoulder_angle_goal,
86 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
Austin Schuh6802a9d2016-03-12 21:34:53 -080087 }
88 } else {
Austin Schuh2c717862016-03-13 15:32:53 -070089 if (wrist_angle > kMaxWristAngleForMovingByIntake ||
90 wrist_angle < kMinWristAngleForMovingByIntake) {
Comran Morshed71466fe2016-04-21 20:21:14 -070091 shoulder_angle_goal = ::std::max(
92 original_shoulder_angle_goal,
93 kMinShoulderAngleForIntakeUpInterference + kSafetyMargin);
Austin Schuh6802a9d2016-03-12 21:34:53 -080094 }
Austin Schuh6ca0f792016-03-12 14:06:14 -080095 }
Philipp Schrader0119eb12016-02-15 18:16:21 +000096 if (::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
97 shoulder_angle_goal =
Austin Schuh6802a9d2016-03-12 21:34:53 -080098 ::std::max(shoulder_angle_goal,
Philipp Schrader0119eb12016-02-15 18:16:21 +000099 kMinShoulderAngleForHorizontalShooter + kSafetyMargin);
100 }
101 }
102
103 // Is the arm where it could interfere with the intake right now?
104 bool shoulder_is_in_danger =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800105 (shoulder_angle < kMinShoulderAngleForIntakeUpInterference &&
Philipp Schrader0119eb12016-02-15 18:16:21 +0000106 shoulder_angle > kMaxShoulderAngleUntilSafeIntakeStowing);
107
108 // Is the arm moving into collision zone from above?
109 bool shoulder_moving_into_danger_from_above =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800110 (shoulder_angle >= kMinShoulderAngleForIntakeUpInterference &&
111 original_shoulder_angle_goal <=
112 kMinShoulderAngleForIntakeUpInterference);
Philipp Schrader0119eb12016-02-15 18:16:21 +0000113
114 // Is the arm moving into collision zone from below?
115 bool shoulder_moving_into_danger_from_below =
116 (shoulder_angle <= kMaxShoulderAngleUntilSafeIntakeStowing &&
Austin Schuh6ca0f792016-03-12 14:06:14 -0800117 original_shoulder_angle_goal >= kMaxShoulderAngleUntilSafeIntakeStowing);
Philipp Schrader0119eb12016-02-15 18:16:21 +0000118
119 // Avoid colliding the arm with the intake.
120 if (shoulder_is_in_danger || shoulder_moving_into_danger_from_above ||
121 shoulder_moving_into_danger_from_below) {
122 // If the arm could collide with the intake, we make sure to move the
123 // intake out of the way. The arm has priority.
124 intake_angle_goal =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800125 ::std::min(original_intake_angle_goal,
Philipp Schrader0119eb12016-02-15 18:16:21 +0000126 kMaxIntakeAngleBeforeArmInterference - kSafetyMargin);
127
128 // Don't let the shoulder move into the collision area until the intake is
129 // out of the way.
130 if (intake_angle > kMaxIntakeAngleBeforeArmInterference) {
131 const double kHalfwayPointBetweenSafeZones =
132 (kMinShoulderAngleForIntakeInterference +
133 kMaxShoulderAngleUntilSafeIntakeStowing) /
134 2.0;
135
136 if (shoulder_angle >= kHalfwayPointBetweenSafeZones) {
137 // The shoulder is closer to being above the collision area. Move it up
138 // there.
Austin Schuh2c717862016-03-13 15:32:53 -0700139 if (intake_angle <
140 kMaxIntakeAngleBeforeArmInterference + kSafetyMargin) {
141 shoulder_angle_goal = ::std::max(
142 original_shoulder_angle_goal,
143 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
144 } else {
145 shoulder_angle_goal = ::std::max(
146 original_shoulder_angle_goal,
147 kMinShoulderAngleForIntakeUpInterference + kSafetyMargin);
148 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000149 } else {
150 // The shoulder is closer to being below the collision zone (i.e. in
151 // stowing/intake position), keep it there for now.
152 shoulder_angle_goal =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800153 ::std::min(original_shoulder_angle_goal,
Philipp Schrader0119eb12016-02-15 18:16:21 +0000154 kMaxShoulderAngleUntilSafeIntakeStowing - kSafetyMargin);
155 }
156 }
157 }
158
159 // Send the possibly adjusted goals to the components.
160 arm_->set_unprofiled_goal(shoulder_angle_goal, wrist_angle_goal);
161 intake_->set_unprofiled_goal(intake_angle_goal);
162}
163
Philipp Schrader07147532016-02-16 01:23:07 +0000164bool CollisionAvoidance::collided() const {
165 return collided_with_given_angles(arm_->shoulder_angle(), arm_->wrist_angle(),
Austin Schuh3634ed32017-02-05 16:28:49 -0800166 intake_->position());
Philipp Schrader07147532016-02-16 01:23:07 +0000167}
168
169bool CollisionAvoidance::collided_with_given_angles(double shoulder_angle,
170 double wrist_angle,
171 double intake_angle) {
172 // The arm and the intake must not hit.
173 if (shoulder_angle >=
174 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing &&
175 shoulder_angle <=
Austin Schuh6ca0f792016-03-12 14:06:14 -0800176 CollisionAvoidance::kMinShoulderAngleForIntakeUpInterference &&
Comran Morshed71466fe2016-04-21 20:21:14 -0700177 intake_angle > CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700178 AOS_LOG(DEBUG, "Collided: Intake %f > %f, and shoulder %f < %f < %f.\n",
179 intake_angle,
180 CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference,
181 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing,
182 shoulder_angle,
183 CollisionAvoidance::kMinShoulderAngleForIntakeUpInterference);
Austin Schuh6ca0f792016-03-12 14:06:14 -0800184 return true;
185 }
186
187 if (shoulder_angle >=
188 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing &&
189 shoulder_angle <=
190 CollisionAvoidance::kMinShoulderAngleForIntakeInterference &&
191 intake_angle < CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference &&
192 intake_angle > Superstructure::kIntakeLowerClear &&
Austin Schuh2c717862016-03-13 15:32:53 -0700193 (wrist_angle > CollisionAvoidance::kMaxWristAngleForMovingByIntake ||
194 wrist_angle < CollisionAvoidance::kMinWristAngleForMovingByIntake)) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700195 AOS_LOG(
196 DEBUG,
Austin Schuh2c717862016-03-13 15:32:53 -0700197 "Collided: Intake %f < %f < %f, shoulder %f < %f < %f, and %f < %f < "
198 "%f.\n",
Austin Schuh6ca0f792016-03-12 14:06:14 -0800199 Superstructure::kIntakeLowerClear, intake_angle,
200 CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference,
201 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing,
202 shoulder_angle,
Austin Schuh2c717862016-03-13 15:32:53 -0700203 CollisionAvoidance::kMinShoulderAngleForIntakeInterference,
204 CollisionAvoidance::kMinWristAngleForMovingByIntake, wrist_angle,
205 CollisionAvoidance::kMaxWristAngleForMovingByIntake);
Philipp Schrader07147532016-02-16 01:23:07 +0000206 return true;
207 }
208
209 // The wrist must go back to zero when the shoulder is moving the arm into
210 // a stowed/intaking position.
211 if (shoulder_angle <
212 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter &&
213 ::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700214 AOS_LOG(DEBUG, "Collided: Shoulder %f < %f and wrist |%f| > %f.\n",
215 shoulder_angle,
216 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter,
217 wrist_angle, kMaxWristAngleForSafeArmStowing);
Philipp Schrader07147532016-02-16 01:23:07 +0000218 return true;
219 }
220
221 return false;
222}
223
Philipp Schrader0119eb12016-02-15 18:16:21 +0000224constexpr double CollisionAvoidance::kMinShoulderAngleForHorizontalShooter;
225constexpr double CollisionAvoidance::kMinShoulderAngleForIntakeInterference;
226constexpr double CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference;
227constexpr double CollisionAvoidance::kMaxWristAngleForSafeArmStowing;
228constexpr double CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing;
229
Austin Schuh55a13dc2019-01-27 22:39:03 -0800230Superstructure::Superstructure(::aos::EventLoop *event_loop,
231 const ::std::string &name)
232 : aos::controls::ControlLoop<control_loops::SuperstructureQueue>(event_loop,
233 name),
Austin Schuh4b652c92019-05-27 13:22:27 -0700234 ball_detector_fetcher_(
235 event_loop->MakeFetcher<::y2016::sensors::BallDetector>(
236 ".y2016.sensors.ball_detector")),
Philipp Schrader0119eb12016-02-15 18:16:21 +0000237 collision_avoidance_(&intake_, &arm_) {}
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800238
Adam Snaider06779722016-02-14 15:26:22 -0800239bool Superstructure::IsArmNear(double shoulder_tolerance,
240 double wrist_tolerance) {
241 return ((arm_.unprofiled_goal() - arm_.X_hat())
242 .block<2, 1>(0, 0)
243 .lpNorm<Eigen::Infinity>() < shoulder_tolerance) &&
244 ((arm_.unprofiled_goal() - arm_.X_hat())
245 .block<4, 1>(0, 0)
246 .lpNorm<Eigen::Infinity>() < wrist_tolerance) &&
247 ((arm_.unprofiled_goal() - arm_.goal())
248 .block<4, 1>(0, 0)
249 .lpNorm<Eigen::Infinity>() < 1e-6);
250}
251
252bool Superstructure::IsArmNear(double tolerance) {
253 return ((arm_.unprofiled_goal() - arm_.X_hat())
254 .block<4, 1>(0, 0)
255 .lpNorm<Eigen::Infinity>() < tolerance) &&
256 ((arm_.unprofiled_goal() - arm_.goal())
257 .block<4, 1>(0, 0)
258 .lpNorm<Eigen::Infinity>() < 1e-6);
259}
260
261bool Superstructure::IsIntakeNear(double tolerance) {
262 return ((intake_.unprofiled_goal() - intake_.X_hat())
263 .block<2, 1>(0, 0)
264 .lpNorm<Eigen::Infinity>() < tolerance);
265}
266
267double Superstructure::MoveButKeepAbove(double reference_angle,
268 double current_angle,
269 double move_distance) {
270 return -MoveButKeepBelow(-reference_angle, -current_angle, -move_distance);
271}
272
273double Superstructure::MoveButKeepBelow(double reference_angle,
274 double current_angle,
275 double move_distance) {
276 // There are 3 interesting places to move to.
277 const double small_negative_move = current_angle - move_distance;
278 const double small_positive_move = current_angle + move_distance;
279 // And the reference angle.
280
281 // Move the the highest one that is below reference_angle.
282 if (small_negative_move > reference_angle) {
283 return reference_angle;
284 } else if (small_positive_move > reference_angle) {
285 return small_negative_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800286 } else {
Adam Snaider06779722016-02-14 15:26:22 -0800287 return small_positive_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800288 }
289}
290
291void Superstructure::RunIteration(
292 const control_loops::SuperstructureQueue::Goal *unsafe_goal,
293 const control_loops::SuperstructureQueue::Position *position,
294 control_loops::SuperstructureQueue::Output *output,
295 control_loops::SuperstructureQueue::Status *status) {
Adam Snaider06779722016-02-14 15:26:22 -0800296 const State state_before_switch = state_;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800297 if (WasReset()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700298 AOS_LOG(ERROR, "WPILib reset, restarting\n");
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800299 arm_.Reset();
300 intake_.Reset();
301 state_ = UNINITIALIZED;
302 }
303
304 // Bool to track if we should turn the motors on or not.
305 bool disable = output == nullptr;
306
307 arm_.Correct(position->shoulder, position->wrist);
308 intake_.Correct(position->intake);
309
Adam Snaider06779722016-02-14 15:26:22 -0800310 // There are 2 main zeroing paths, HIGH_ARM_ZERO and LOW_ARM_ZERO.
311 //
312 // HIGH_ARM_ZERO works by lifting the arm all the way up so it is clear,
313 // moving the shooter to be horizontal, moving the intake out, and then moving
314 // the arm back down.
315 //
316 // LOW_ARM_ZERO works by moving the intake out of the way, lifting the arm up,
317 // leveling the shooter, and then moving back down.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800318
Diana Vandenberge2843c62016-02-13 17:44:20 -0800319 if (arm_.error() || intake_.error()) {
320 state_ = ESTOP;
321 }
322
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800323 const bool is_collided = collided();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800324 switch (state_) {
325 case UNINITIALIZED:
Adam Snaider06779722016-02-14 15:26:22 -0800326 // Wait in the uninitialized state until both the arm and intake are
327 // initialized.
Austin Schuhf257f3c2019-10-27 21:00:43 -0700328 AOS_LOG(DEBUG, "Uninitialized, waiting for intake and arm\n");
Adam Snaider06779722016-02-14 15:26:22 -0800329 if (arm_.initialized() && intake_.initialized()) {
330 state_ = DISABLED_INITIALIZED;
331 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800332 disable = true;
333 break;
334
Adam Snaider06779722016-02-14 15:26:22 -0800335 case DISABLED_INITIALIZED:
336 // Wait here until we are either fully zeroed while disabled, or we become
337 // enabled. At that point, figure out if we should HIGH_ARM_ZERO or
338 // LOW_ARM_ZERO.
339 if (disable) {
340 if (arm_.zeroed() && intake_.zeroed()) {
341 state_ = SLOW_RUNNING;
342 }
343 } else {
344 if (arm_.shoulder_angle() >= kShoulderMiddleAngle) {
345 state_ = HIGH_ARM_ZERO_LIFT_ARM;
346 } else {
347 state_ = LOW_ARM_ZERO_LOWER_INTAKE;
348 }
349 }
350
351 // Set the goals to where we are now so when we start back up, we don't
352 // jump.
Austin Schuh3634ed32017-02-05 16:28:49 -0800353 intake_.ForceGoal(intake_.position());
Adam Snaider06779722016-02-14 15:26:22 -0800354 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
355 // Set up the profile to be the zeroing profile.
356 intake_.AdjustProfile(0.5, 10);
357 arm_.AdjustProfile(0.5, 10, 0.5, 10);
358
359 // We are not ready to start doing anything yet.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800360 disable = true;
Adam Snaider06779722016-02-14 15:26:22 -0800361 break;
362
363 case HIGH_ARM_ZERO_LIFT_ARM:
364 if (disable) {
365 state_ = DISABLED_INITIALIZED;
366 } else {
367 // Raise the shoulder up out of the way.
368 arm_.set_unprofiled_goal(kShoulderUpAngle, arm_.wrist_angle());
369 if (IsArmNear(kLooseTolerance)) {
370 // Close enough, start the next move.
371 state_ = HIGH_ARM_ZERO_LEVEL_SHOOTER;
372 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800373 }
374 break;
375
Adam Snaider06779722016-02-14 15:26:22 -0800376 case HIGH_ARM_ZERO_LEVEL_SHOOTER:
377 if (disable) {
378 state_ = DISABLED_INITIALIZED;
379 } else {
380 // Move the shooter to be level.
381 arm_.set_unprofiled_goal(kShoulderUpAngle, 0.0);
382
383 if (IsArmNear(kLooseTolerance)) {
384 // Close enough, start the next move.
385 state_ = HIGH_ARM_ZERO_MOVE_INTAKE_OUT;
386 }
387 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800388 break;
389
Adam Snaider06779722016-02-14 15:26:22 -0800390 case HIGH_ARM_ZERO_MOVE_INTAKE_OUT:
391 if (disable) {
392 state_ = DISABLED_INITIALIZED;
393 } else {
394 // If we were just asked to move the intake, make sure it moves far
395 // enough.
396 if (last_state_ != HIGH_ARM_ZERO_MOVE_INTAKE_OUT) {
397 intake_.set_unprofiled_goal(
Austin Schuh3634ed32017-02-05 16:28:49 -0800398 MoveButKeepBelow(kIntakeUpperClear, intake_.position(),
Adam Snaider06779722016-02-14 15:26:22 -0800399 kIntakeEncoderIndexDifference * 2.5));
400 }
401
402 if (IsIntakeNear(kLooseTolerance)) {
403 // Close enough, start the next move.
404 state_ = HIGH_ARM_ZERO_LOWER_ARM;
405 }
406 }
407 break;
408
409 case HIGH_ARM_ZERO_LOWER_ARM:
410 if (disable) {
411 state_ = DISABLED_INITIALIZED;
412 } else {
413 // Land the shooter in the belly-pan. It should be zeroed by the time
414 // it gets there. If not, just estop.
415 arm_.set_unprofiled_goal(kShoulderLanded, 0.0);
416 if (arm_.zeroed() && intake_.zeroed()) {
417 state_ = RUNNING;
418 } else if (IsArmNear(kLooseTolerance)) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700419 AOS_LOG(ERROR,
420 "Failed to zero while executing the HIGH_ARM_ZERO sequence. "
421 "Arm: %d Intake %d\n",
422 arm_.zeroed(), intake_.zeroed());
Adam Snaider06779722016-02-14 15:26:22 -0800423 state_ = ESTOP;
424 }
425 }
426 break;
427
428 case LOW_ARM_ZERO_LOWER_INTAKE:
429 if (disable) {
430 state_ = DISABLED_INITIALIZED;
431 } else {
432 // Move the intake down out of the way of the arm. Make sure to move it
433 // far enough to zero.
434 if (last_state_ != LOW_ARM_ZERO_LOWER_INTAKE) {
435 intake_.set_unprofiled_goal(
Austin Schuh3634ed32017-02-05 16:28:49 -0800436 MoveButKeepBelow(kIntakeLowerClear, intake_.position(),
Adam Snaider06779722016-02-14 15:26:22 -0800437 kIntakeEncoderIndexDifference * 2.5));
438 }
439 if (IsIntakeNear(kLooseTolerance)) {
440 if (::std::abs(arm_.wrist_angle()) < kWristAlmostLevel) {
441 state_ = LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER;
442 } else {
443 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
444 }
445 }
446 }
447 break;
448
449 case LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER:
450 if (disable) {
451 state_ = DISABLED_INITIALIZED;
452 } else {
453 // If we are supposed to level the shooter, set it to level, and wait
454 // until it is very close to level.
455 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
456 if (IsArmNear(kLooseTolerance, kTightTolerance)) {
457 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
458 }
459 }
460 break;
461
462 case LOW_ARM_ZERO_LIFT_SHOULDER:
463 if (disable) {
464 state_ = DISABLED_INITIALIZED;
465 } else {
466 // Decide where to move to. We need to move far enough to see an index
467 // pulse, but must also get high enough that we can safely level the
468 // shooter.
469 if (last_state_ != LOW_ARM_ZERO_LIFT_SHOULDER) {
470 arm_.set_unprofiled_goal(
471 MoveButKeepAbove(kShoulderWristClearAngle, arm_.shoulder_angle(),
472 ::std::max(kWristEncoderIndexDifference,
473 kShoulderEncoderIndexDifference) *
474 2.5),
475 arm_.unprofiled_goal(2, 0));
476 }
477
Brian Silverman741b27a2016-05-16 00:09:26 -0700478 // If we're about to ask the wrist to go past one of its limits, then
479 // move the goal so it will be just at the limit when we finish lifting
480 // the shoulder. If it wasn't intersecting something before, this can't
481 // cause it to crash into anything.
482 const double ungrounded_wrist = arm_.goal(2, 0) - arm_.goal(0, 0);
483 const double unprofiled_ungrounded_wrist =
484 arm_.unprofiled_goal(2, 0) - arm_.unprofiled_goal(0, 0);
485 if (unprofiled_ungrounded_wrist >
486 constants::Values::kWristRange.upper &&
487 ungrounded_wrist >
488 constants::Values::kWristRange.upper - kWristAlmostLevel) {
489 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0),
490 constants::Values::kWristRange.upper +
491 arm_.unprofiled_goal(0, 0));
492 } else if (unprofiled_ungrounded_wrist <
493 constants::Values::kWristRange.lower &&
494 ungrounded_wrist < constants::Values::kWristRange.lower +
495 kWristAlmostLevel) {
496 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0),
497 constants::Values::kWristRange.lower +
498 arm_.unprofiled_goal(0, 0));
499 }
500
Adam Snaider06779722016-02-14 15:26:22 -0800501 // Wait until we are level and then go for it.
502 if (IsArmNear(kLooseTolerance)) {
503 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
504 }
505 }
506 break;
507
508 case LOW_ARM_ZERO_LEVEL_SHOOTER:
509 if (disable) {
510 state_ = DISABLED_INITIALIZED;
511 } else {
512 // Move the shooter level (and keep the same height). We don't want to
513 // got to RUNNING until we are completely level so that we don't
514 // give control back in a weird case where we might crash.
515 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
516 if (IsArmNear(kLooseTolerance)) {
517 if (arm_.zeroed() && intake_.zeroed()) {
518 state_ = RUNNING;
519 } else {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700520 AOS_LOG(ERROR,
521 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
522 "Arm: %d Intake %d\n",
523 arm_.zeroed(), intake_.zeroed());
Adam Snaider06779722016-02-14 15:26:22 -0800524 state_ = ESTOP;
525 }
526 }
527 }
528 break;
529
Austin Schuhb1d682b2016-02-16 13:07:44 -0800530 // These 4 cases are very similar.
Adam Snaider06779722016-02-14 15:26:22 -0800531 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800532 case RUNNING:
Austin Schuhb1d682b2016-02-16 13:07:44 -0800533 case LANDING_SLOW_RUNNING:
534 case LANDING_RUNNING: {
Austin Schuh829fe572016-02-14 21:41:16 -0800535 if (disable) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800536 // If we are disabled, go to slow running (or landing slow running) if
537 // we are collided.
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800538 if (is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800539 if (state_ == RUNNING) {
540 state_ = SLOW_RUNNING;
541 } else if (state_ == LANDING_RUNNING) {
542 state_ = LANDING_SLOW_RUNNING;
543 }
544 }
Austin Schuh829fe572016-02-14 21:41:16 -0800545
Austin Schuhb1d682b2016-02-16 13:07:44 -0800546 // Reset the profile to the current position so it moves well from here.
Austin Schuh3634ed32017-02-05 16:28:49 -0800547 intake_.ForceGoal(intake_.position());
Austin Schuh829fe572016-02-14 21:41:16 -0800548 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
549 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800550 // If we are in slow_running and are no longer collided, let 'er rip.
Austin Schuh829fe572016-02-14 21:41:16 -0800551 if (state_ == SLOW_RUNNING) {
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800552 if (!is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800553 state_ = RUNNING;
554 }
555 } else if (state_ == LANDING_SLOW_RUNNING) {
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800556 if (!is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800557 state_ = LANDING_RUNNING;
558 }
Austin Schuh829fe572016-02-14 21:41:16 -0800559 }
560 }
561
Austin Schuhb1d682b2016-02-16 13:07:44 -0800562 double requested_shoulder = constants::Values::kShoulderRange.lower;
563 double requested_wrist = 0.0;
564 double requested_intake = M_PI / 2.0;
565
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800566 if (unsafe_goal) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800567 // If we are in one of the landing states, limit the accelerations and
568 // velocities to land cleanly.
569 if (state_ == LANDING_SLOW_RUNNING || state_ == LANDING_RUNNING) {
570 arm_.AdjustProfile(0.5, // Shoulder Velocity
571 4.0, // Shoulder acceleration,
572 4.0, // Wrist velocity
573 10.0); // Wrist acceleration.
574 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
575 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800576
Austin Schuhb1d682b2016-02-16 13:07:44 -0800577 requested_shoulder =
578 ::std::max(unsafe_goal->angle_shoulder,
579 constants::Values::kShoulderRange.lower);
580 requested_wrist = 0.0;
581 requested_intake = unsafe_goal->angle_intake;
Austin Schuha1dd5272016-02-16 15:39:38 -0800582 // Transition to landing once the profile is close to finished for the
583 // shoulder.
584 if (arm_.goal(0, 0) > kShoulderTransitionToLanded + 1e-4 ||
585 arm_.unprofiled_goal(0, 0) > kShoulderTransitionToLanded + 1e-4) {
586 if (state_ == LANDING_RUNNING) {
587 state_ = RUNNING;
588 } else {
589 state_ = SLOW_RUNNING;
590 }
591 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000592 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800593 // Otherwise, give the user what he asked for.
594 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
595 unsafe_goal->max_angular_acceleration_shoulder,
596 unsafe_goal->max_angular_velocity_wrist,
597 unsafe_goal->max_angular_acceleration_wrist);
598 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
599 unsafe_goal->max_angular_acceleration_intake);
600
601 // Except, don't let the shoulder go all the way down.
602 requested_shoulder = ::std::max(unsafe_goal->angle_shoulder,
603 kShoulderTransitionToLanded);
604 requested_wrist = unsafe_goal->angle_wrist;
605 requested_intake = unsafe_goal->angle_intake;
606
607 // Transition to landing once the profile is close to finished for the
608 // shoulder.
Austin Schuha1dd5272016-02-16 15:39:38 -0800609 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 &&
610 arm_.unprofiled_goal(0, 0) <=
611 kShoulderTransitionToLanded + 1e-4) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800612 if (state_ == RUNNING) {
613 state_ = LANDING_RUNNING;
614 } else {
615 state_ = LANDING_SLOW_RUNNING;
616 }
617 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000618 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800619 }
620
Austin Schuhb1d682b2016-02-16 13:07:44 -0800621 // Push the request out to hardware!
622 collision_avoidance_.UpdateGoal(requested_shoulder, requested_wrist,
623 requested_intake);
624
625 // ESTOP if we hit the hard limits.
626 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
627 state_ = ESTOP;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800628 }
Austin Schuhb1d682b2016-02-16 13:07:44 -0800629 } break;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800630
631 case ESTOP:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700632 AOS_LOG(ERROR, "Estop\n");
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800633 disable = true;
634 break;
635 }
636
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800637 // Set the voltage limits.
Comran Morshed011f7d92016-02-16 23:03:06 +0000638 const double max_voltage = (state_ == RUNNING || state_ == LANDING_RUNNING)
639 ? kOperatingVoltage
640 : kZeroingVoltage;
Comran Morshed71466fe2016-04-21 20:21:14 -0700641 if (unsafe_goal) {
Austin Schuh0c001a82016-05-01 12:30:09 -0700642 constexpr float kTriggerThreshold = 12.0 * 0.90 / 0.005;
Comran Morshed71466fe2016-04-21 20:21:14 -0700643
644 if (unsafe_goal->voltage_climber > 1.0) {
645 kill_shoulder_accumulator_ +=
646 ::std::min(kMaxClimberVoltage, unsafe_goal->voltage_climber);
647 } else {
648 kill_shoulder_accumulator_ = 0.0;
649 }
650
651 if (kill_shoulder_accumulator_ > kTriggerThreshold) {
652 kill_shoulder_ = true;
653 }
654 }
Austin Schuh0c001a82016-05-01 12:30:09 -0700655 arm_.set_max_voltage(
Austin Schuh473a5652017-02-05 01:30:42 -0800656 {{kill_shoulder_ ? 0.0 : max_voltage,
657 kill_shoulder_ ? (arm_.X_hat(0, 0) < 0.05 ? kShooterHangingLowVoltage
658 : kShooterHangingVoltage)
659 : max_voltage}});
660 intake_.set_max_voltage({{max_voltage}});
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800661
Diana Vandenberg9cc9ab62016-04-20 21:27:47 -0700662 if (IsRunning() && !kill_shoulder_) {
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800663 // We don't want lots of negative voltage when we are near the bellypan on
664 // the shoulder...
665 // TODO(austin): Do I want to push negative power into the belly pan at this
666 // point? Maybe just put the goal slightly below the bellypan and call that
667 // good enough.
Austin Schuh6ca0f792016-03-12 14:06:14 -0800668 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 ||
669 arm_.X_hat(0, 0) <= kShoulderTransitionToLanded + 1e-4) {
Comran Morshed011f7d92016-02-16 23:03:06 +0000670 arm_.set_shoulder_asymetric_limits(kLandingShoulderDownVoltage,
671 max_voltage);
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800672 }
673 }
674
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800675 // Calculate the loops for a cycle.
Austin Schuhbe041152016-02-28 22:01:52 -0800676 {
677 Eigen::Matrix<double, 3, 1> error = intake_.controller().error();
Austin Schuh32501832017-02-25 18:32:56 -0800678 status->intake.position_power =
679 intake_.controller().controller().K(0, 0) * error(0, 0);
680 status->intake.velocity_power =
681 intake_.controller().controller().K(0, 1) * error(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800682 }
683
684 {
685 Eigen::Matrix<double, 6, 1> error = arm_.controller().error();
Austin Schuh32501832017-02-25 18:32:56 -0800686 status->shoulder.position_power =
687 arm_.controller().controller().K(0, 0) * error(0, 0);
688 status->shoulder.velocity_power =
689 arm_.controller().controller().K(0, 1) * error(1, 0);
690 status->wrist.position_power =
691 arm_.controller().controller().K(0, 2) * error(2, 0);
692 status->wrist.velocity_power =
693 arm_.controller().controller().K(0, 3) * error(3, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800694 }
695
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800696 arm_.Update(disable);
697 intake_.Update(disable);
698
699 // Write out all the voltages.
700 if (output) {
Austin Schuh3634ed32017-02-05 16:28:49 -0800701 output->voltage_intake = intake_.voltage();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800702 output->voltage_shoulder = arm_.shoulder_voltage();
703 output->voltage_wrist = arm_.wrist_voltage();
Comran Morshedf4cd7642016-02-15 20:40:49 +0000704
Campbell Crowleyd4fd6552016-02-21 17:53:46 -0800705 output->voltage_top_rollers = 0.0;
706 output->voltage_bottom_rollers = 0.0;
Comran Morshed71466fe2016-04-21 20:21:14 -0700707
708 output->voltage_climber = 0.0;
709 output->unfold_climber = false;
Comran Morshedf4cd7642016-02-15 20:40:49 +0000710 if (unsafe_goal) {
Comran Morshed71466fe2016-04-21 20:21:14 -0700711 // Ball detector lights.
Austin Schuh4b652c92019-05-27 13:22:27 -0700712 ball_detector_fetcher_.Fetch();
Austin Schuh1defd262016-04-03 16:13:32 -0700713 bool ball_detected = false;
Austin Schuh4b652c92019-05-27 13:22:27 -0700714 if (ball_detector_fetcher_.get()) {
715 ball_detected = ball_detector_fetcher_->voltage > 2.5;
Austin Schuh1defd262016-04-03 16:13:32 -0700716 }
Comran Morshed71466fe2016-04-21 20:21:14 -0700717
718 // Climber.
719 output->voltage_climber = ::std::max(
720 static_cast<float>(0.0),
721 ::std::min(unsafe_goal->voltage_climber, kMaxClimberVoltage));
722 output->unfold_climber = unsafe_goal->unfold_climber;
723
724 // Intake.
Austin Schuh1defd262016-04-03 16:13:32 -0700725 if (unsafe_goal->force_intake || !ball_detected) {
726 output->voltage_top_rollers = ::std::max(
727 -kMaxIntakeTopVoltage,
728 ::std::min(unsafe_goal->voltage_top_rollers, kMaxIntakeTopVoltage));
729 output->voltage_bottom_rollers =
730 ::std::max(-kMaxIntakeBottomVoltage,
731 ::std::min(unsafe_goal->voltage_bottom_rollers,
732 kMaxIntakeBottomVoltage));
733 } else {
734 output->voltage_top_rollers = 0.0;
735 output->voltage_bottom_rollers = 0.0;
736 }
Comran Morshed71466fe2016-04-21 20:21:14 -0700737
738 // Traverse.
Austin Schuh843412b2016-03-20 16:48:46 -0700739 output->traverse_unlatched = unsafe_goal->traverse_unlatched;
740 output->traverse_down = unsafe_goal->traverse_down;
Comran Morshedf4cd7642016-02-15 20:40:49 +0000741 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800742 }
743
744 // Save debug/internal state.
Austin Schuha987f8b2016-02-28 22:02:20 -0800745 status->zeroed = arm_.zeroed() && intake_.zeroed();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800746
747 status->shoulder.angle = arm_.X_hat(0, 0);
748 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
749 status->shoulder.goal_angle = arm_.goal(0, 0);
750 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800751 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
752 status->shoulder.unprofiled_goal_angular_velocity =
753 arm_.unprofiled_goal(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800754 status->shoulder.voltage_error = arm_.X_hat(4, 0);
755 status->shoulder.calculated_velocity =
756 (arm_.shoulder_angle() - last_shoulder_angle_) / 0.005;
Austin Schuh473a5652017-02-05 01:30:42 -0800757 status->shoulder.estimator_state = arm_.EstimatorState(0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800758
759 status->wrist.angle = arm_.X_hat(2, 0);
760 status->wrist.angular_velocity = arm_.X_hat(3, 0);
761 status->wrist.goal_angle = arm_.goal(2, 0);
762 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800763 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
764 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800765 status->wrist.voltage_error = arm_.X_hat(5, 0);
766 status->wrist.calculated_velocity =
767 (arm_.wrist_angle() - last_wrist_angle_) / 0.005;
Austin Schuh473a5652017-02-05 01:30:42 -0800768 status->wrist.estimator_state = arm_.EstimatorState(1);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800769
770 status->intake.angle = intake_.X_hat(0, 0);
771 status->intake.angular_velocity = intake_.X_hat(1, 0);
772 status->intake.goal_angle = intake_.goal(0, 0);
773 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800774 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
775 status->intake.unprofiled_goal_angular_velocity =
776 intake_.unprofiled_goal(1, 0);
Comran Morshed71466fe2016-04-21 20:21:14 -0700777 status->intake.calculated_velocity =
Austin Schuh3634ed32017-02-05 16:28:49 -0800778 (intake_.position() - last_intake_angle_) / 0.005;
Austin Schuhbe041152016-02-28 22:01:52 -0800779 status->intake.voltage_error = intake_.X_hat(2, 0);
Austin Schuh473a5652017-02-05 01:30:42 -0800780 status->intake.estimator_state = intake_.EstimatorState(0);
Austin Schuhbe041152016-02-28 22:01:52 -0800781 status->intake.feedforwards_power = intake_.controller().ff_U(0, 0);
782
Austin Schuhf59b6bc2016-03-11 21:26:19 -0800783 status->shoulder_controller_index = arm_.controller_index();
784
Austin Schuhbe041152016-02-28 22:01:52 -0800785 last_shoulder_angle_ = arm_.shoulder_angle();
786 last_wrist_angle_ = arm_.wrist_angle();
Austin Schuh3634ed32017-02-05 16:28:49 -0800787 last_intake_angle_ = intake_.position();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800788
789 status->estopped = (state_ == ESTOP);
790
791 status->state = state_;
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800792 status->is_collided = is_collided;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800793
Adam Snaider06779722016-02-14 15:26:22 -0800794 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800795}
796
Philipp Schrader3da48ac2016-03-06 23:03:44 +0000797constexpr double Superstructure::kZeroingVoltage;
798constexpr double Superstructure::kOperatingVoltage;
Austin Schuh6ca0f792016-03-12 14:06:14 -0800799constexpr double Superstructure::kLandingShoulderDownVoltage;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000800constexpr double Superstructure::kShoulderMiddleAngle;
801constexpr double Superstructure::kLooseTolerance;
802constexpr double Superstructure::kIntakeUpperClear;
803constexpr double Superstructure::kIntakeLowerClear;
804constexpr double Superstructure::kShoulderUpAngle;
805constexpr double Superstructure::kShoulderLanded;
806constexpr double Superstructure::kTightTolerance;
807constexpr double Superstructure::kWristAlmostLevel;
808constexpr double Superstructure::kShoulderWristClearAngle;
Austin Schuhb1d682b2016-02-16 13:07:44 -0800809constexpr double Superstructure::kShoulderTransitionToLanded;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000810
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800811} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000812} // namespace control_loops
813} // namespace y2016