blob: a2b7a9e396a26c7ee824794fdc4abd946d5718dc [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 {
Adam Snaider06779722016-02-14 15:26:22 -080017constexpr double kIntakeEncoderIndexDifference =
18 constants::Values::kIntakeEncoderIndexDifference;
19constexpr double kWristEncoderIndexDifference =
20 constants::Values::kWristEncoderIndexDifference;
21constexpr double kShoulderEncoderIndexDifference =
22 constants::Values::kShoulderEncoderIndexDifference;
23
Austin Schuh2fc10fa2016-02-08 00:44:34 -080024constexpr double kZeroingVoltage = 4.0;
Austin Schuh2fc10fa2016-02-08 00:44:34 -080025} // namespace
26
Philipp Schrader0119eb12016-02-15 18:16:21 +000027// ///// CollisionAvoidance /////
28
29void CollisionAvoidance::UpdateGoal(double shoulder_angle_goal,
30 double wrist_angle_goal,
31 double intake_angle_goal) {
32 double shoulder_angle = arm_->shoulder_angle();
33 double wrist_angle = arm_->wrist_angle();
34 double intake_angle = intake_->angle();
35
36 // TODO(phil): This may need tuning to account for bounciness in the limbs or
37 // some other thing that I haven't thought of. At the very least,
38 // incorporating a small safety margin makes writing test cases much easier
39 // since you can directly compare statuses against the constants in the
40 // CollisionAvoidance class.
41 constexpr double kSafetyMargin = 0.01; // radians
42
43 // Avoid colliding the shooter with the frame.
44 // If the shoulder is below a certain angle or we want to move it below
45 // that angle, then the shooter has to stay level to the ground. Otherwise,
46 // it will crash into the frame.
47 if (shoulder_angle < kMinShoulderAngleForHorizontalShooter ||
48 shoulder_angle_goal < kMinShoulderAngleForHorizontalShooter) {
49 wrist_angle_goal = 0.0;
50
51 // Make sure that we don't move the shoulder below a certain angle until
52 // the wrist is level with the ground.
53 if (::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
54 shoulder_angle_goal =
55 ::std::max(shoulder_angle_goal,
56 kMinShoulderAngleForHorizontalShooter + kSafetyMargin);
57 }
58 }
59
60 // Is the arm where it could interfere with the intake right now?
61 bool shoulder_is_in_danger =
62 (shoulder_angle < kMinShoulderAngleForIntakeInterference &&
63 shoulder_angle > kMaxShoulderAngleUntilSafeIntakeStowing);
64
65 // Is the arm moving into collision zone from above?
66 bool shoulder_moving_into_danger_from_above =
67 (shoulder_angle >= kMinShoulderAngleForIntakeInterference &&
68 shoulder_angle_goal <= kMinShoulderAngleForIntakeInterference);
69
70 // Is the arm moving into collision zone from below?
71 bool shoulder_moving_into_danger_from_below =
72 (shoulder_angle <= kMaxShoulderAngleUntilSafeIntakeStowing &&
73 shoulder_angle_goal >= kMaxShoulderAngleUntilSafeIntakeStowing);
74
75 // Avoid colliding the arm with the intake.
76 if (shoulder_is_in_danger || shoulder_moving_into_danger_from_above ||
77 shoulder_moving_into_danger_from_below) {
78 // If the arm could collide with the intake, we make sure to move the
79 // intake out of the way. The arm has priority.
80 intake_angle_goal =
81 ::std::min(intake_angle_goal,
82 kMaxIntakeAngleBeforeArmInterference - kSafetyMargin);
83
84 // Don't let the shoulder move into the collision area until the intake is
85 // out of the way.
86 if (intake_angle > kMaxIntakeAngleBeforeArmInterference) {
87 const double kHalfwayPointBetweenSafeZones =
88 (kMinShoulderAngleForIntakeInterference +
89 kMaxShoulderAngleUntilSafeIntakeStowing) /
90 2.0;
91
92 if (shoulder_angle >= kHalfwayPointBetweenSafeZones) {
93 // The shoulder is closer to being above the collision area. Move it up
94 // there.
95 shoulder_angle_goal =
96 ::std::max(shoulder_angle_goal,
97 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
98 } else {
99 // The shoulder is closer to being below the collision zone (i.e. in
100 // stowing/intake position), keep it there for now.
101 shoulder_angle_goal =
102 ::std::min(shoulder_angle_goal,
103 kMaxShoulderAngleUntilSafeIntakeStowing - kSafetyMargin);
104 }
105 }
106 }
107
108 // Send the possibly adjusted goals to the components.
109 arm_->set_unprofiled_goal(shoulder_angle_goal, wrist_angle_goal);
110 intake_->set_unprofiled_goal(intake_angle_goal);
111}
112
113constexpr double CollisionAvoidance::kMinShoulderAngleForHorizontalShooter;
114constexpr double CollisionAvoidance::kMinShoulderAngleForIntakeInterference;
115constexpr double CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference;
116constexpr double CollisionAvoidance::kMaxWristAngleForSafeArmStowing;
117constexpr double CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing;
118
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800119Superstructure::Superstructure(
120 control_loops::SuperstructureQueue *superstructure_queue)
121 : aos::controls::ControlLoop<control_loops::SuperstructureQueue>(
Philipp Schrader0119eb12016-02-15 18:16:21 +0000122 superstructure_queue),
123 collision_avoidance_(&intake_, &arm_) {}
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800124
Adam Snaider06779722016-02-14 15:26:22 -0800125bool Superstructure::IsArmNear(double shoulder_tolerance,
126 double wrist_tolerance) {
127 return ((arm_.unprofiled_goal() - arm_.X_hat())
128 .block<2, 1>(0, 0)
129 .lpNorm<Eigen::Infinity>() < shoulder_tolerance) &&
130 ((arm_.unprofiled_goal() - arm_.X_hat())
131 .block<4, 1>(0, 0)
132 .lpNorm<Eigen::Infinity>() < wrist_tolerance) &&
133 ((arm_.unprofiled_goal() - arm_.goal())
134 .block<4, 1>(0, 0)
135 .lpNorm<Eigen::Infinity>() < 1e-6);
136}
137
138bool Superstructure::IsArmNear(double tolerance) {
139 return ((arm_.unprofiled_goal() - arm_.X_hat())
140 .block<4, 1>(0, 0)
141 .lpNorm<Eigen::Infinity>() < tolerance) &&
142 ((arm_.unprofiled_goal() - arm_.goal())
143 .block<4, 1>(0, 0)
144 .lpNorm<Eigen::Infinity>() < 1e-6);
145}
146
147bool Superstructure::IsIntakeNear(double tolerance) {
148 return ((intake_.unprofiled_goal() - intake_.X_hat())
149 .block<2, 1>(0, 0)
150 .lpNorm<Eigen::Infinity>() < tolerance);
151}
152
153double Superstructure::MoveButKeepAbove(double reference_angle,
154 double current_angle,
155 double move_distance) {
156 return -MoveButKeepBelow(-reference_angle, -current_angle, -move_distance);
157}
158
159double Superstructure::MoveButKeepBelow(double reference_angle,
160 double current_angle,
161 double move_distance) {
162 // There are 3 interesting places to move to.
163 const double small_negative_move = current_angle - move_distance;
164 const double small_positive_move = current_angle + move_distance;
165 // And the reference angle.
166
167 // Move the the highest one that is below reference_angle.
168 if (small_negative_move > reference_angle) {
169 return reference_angle;
170 } else if (small_positive_move > reference_angle) {
171 return small_negative_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800172 } else {
Adam Snaider06779722016-02-14 15:26:22 -0800173 return small_positive_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800174 }
175}
176
177void Superstructure::RunIteration(
178 const control_loops::SuperstructureQueue::Goal *unsafe_goal,
179 const control_loops::SuperstructureQueue::Position *position,
180 control_loops::SuperstructureQueue::Output *output,
181 control_loops::SuperstructureQueue::Status *status) {
Adam Snaider06779722016-02-14 15:26:22 -0800182 const State state_before_switch = state_;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800183 if (WasReset()) {
184 LOG(ERROR, "WPILib reset, restarting\n");
185 arm_.Reset();
186 intake_.Reset();
187 state_ = UNINITIALIZED;
188 }
189
190 // Bool to track if we should turn the motors on or not.
191 bool disable = output == nullptr;
192
193 arm_.Correct(position->shoulder, position->wrist);
194 intake_.Correct(position->intake);
195
Adam Snaider06779722016-02-14 15:26:22 -0800196 // There are 2 main zeroing paths, HIGH_ARM_ZERO and LOW_ARM_ZERO.
197 //
198 // HIGH_ARM_ZERO works by lifting the arm all the way up so it is clear,
199 // moving the shooter to be horizontal, moving the intake out, and then moving
200 // the arm back down.
201 //
202 // LOW_ARM_ZERO works by moving the intake out of the way, lifting the arm up,
203 // leveling the shooter, and then moving back down.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800204
Diana Vandenberge2843c62016-02-13 17:44:20 -0800205 if (arm_.error() || intake_.error()) {
206 state_ = ESTOP;
207 }
208
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800209 switch (state_) {
210 case UNINITIALIZED:
Adam Snaider06779722016-02-14 15:26:22 -0800211 // Wait in the uninitialized state until both the arm and intake are
212 // initialized.
213 LOG(DEBUG, "Uninitialized, waiting for intake and arm\n");
214 if (arm_.initialized() && intake_.initialized()) {
215 state_ = DISABLED_INITIALIZED;
216 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800217 disable = true;
218 break;
219
Adam Snaider06779722016-02-14 15:26:22 -0800220 case DISABLED_INITIALIZED:
221 // Wait here until we are either fully zeroed while disabled, or we become
222 // enabled. At that point, figure out if we should HIGH_ARM_ZERO or
223 // LOW_ARM_ZERO.
224 if (disable) {
225 if (arm_.zeroed() && intake_.zeroed()) {
226 state_ = SLOW_RUNNING;
227 }
228 } else {
229 if (arm_.shoulder_angle() >= kShoulderMiddleAngle) {
230 state_ = HIGH_ARM_ZERO_LIFT_ARM;
231 } else {
232 state_ = LOW_ARM_ZERO_LOWER_INTAKE;
233 }
234 }
235
236 // Set the goals to where we are now so when we start back up, we don't
237 // jump.
238 intake_.ForceGoal(intake_.angle());
239 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
240 // Set up the profile to be the zeroing profile.
241 intake_.AdjustProfile(0.5, 10);
242 arm_.AdjustProfile(0.5, 10, 0.5, 10);
243
244 // We are not ready to start doing anything yet.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800245 disable = true;
Adam Snaider06779722016-02-14 15:26:22 -0800246 break;
247
248 case HIGH_ARM_ZERO_LIFT_ARM:
249 if (disable) {
250 state_ = DISABLED_INITIALIZED;
251 } else {
252 // Raise the shoulder up out of the way.
253 arm_.set_unprofiled_goal(kShoulderUpAngle, arm_.wrist_angle());
254 if (IsArmNear(kLooseTolerance)) {
255 // Close enough, start the next move.
256 state_ = HIGH_ARM_ZERO_LEVEL_SHOOTER;
257 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800258 }
259 break;
260
Adam Snaider06779722016-02-14 15:26:22 -0800261 case HIGH_ARM_ZERO_LEVEL_SHOOTER:
262 if (disable) {
263 state_ = DISABLED_INITIALIZED;
264 } else {
265 // Move the shooter to be level.
266 arm_.set_unprofiled_goal(kShoulderUpAngle, 0.0);
267
268 if (IsArmNear(kLooseTolerance)) {
269 // Close enough, start the next move.
270 state_ = HIGH_ARM_ZERO_MOVE_INTAKE_OUT;
271 }
272 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800273 break;
274
Adam Snaider06779722016-02-14 15:26:22 -0800275 case HIGH_ARM_ZERO_MOVE_INTAKE_OUT:
276 if (disable) {
277 state_ = DISABLED_INITIALIZED;
278 } else {
279 // If we were just asked to move the intake, make sure it moves far
280 // enough.
281 if (last_state_ != HIGH_ARM_ZERO_MOVE_INTAKE_OUT) {
282 intake_.set_unprofiled_goal(
283 MoveButKeepBelow(kIntakeUpperClear, intake_.angle(),
284 kIntakeEncoderIndexDifference * 2.5));
285 }
286
287 if (IsIntakeNear(kLooseTolerance)) {
288 // Close enough, start the next move.
289 state_ = HIGH_ARM_ZERO_LOWER_ARM;
290 }
291 }
292 break;
293
294 case HIGH_ARM_ZERO_LOWER_ARM:
295 if (disable) {
296 state_ = DISABLED_INITIALIZED;
297 } else {
298 // Land the shooter in the belly-pan. It should be zeroed by the time
299 // it gets there. If not, just estop.
300 arm_.set_unprofiled_goal(kShoulderLanded, 0.0);
301 if (arm_.zeroed() && intake_.zeroed()) {
302 state_ = RUNNING;
303 } else if (IsArmNear(kLooseTolerance)) {
304 LOG(ERROR,
305 "Failed to zero while executing the HIGH_ARM_ZERO sequence. "
306 "Arm: %d Intake %d\n",
307 arm_.zeroed(), intake_.zeroed());
308 state_ = ESTOP;
309 }
310 }
311 break;
312
313 case LOW_ARM_ZERO_LOWER_INTAKE:
314 if (disable) {
315 state_ = DISABLED_INITIALIZED;
316 } else {
317 // Move the intake down out of the way of the arm. Make sure to move it
318 // far enough to zero.
319 if (last_state_ != LOW_ARM_ZERO_LOWER_INTAKE) {
320 intake_.set_unprofiled_goal(
321 MoveButKeepBelow(kIntakeLowerClear, intake_.angle(),
322 kIntakeEncoderIndexDifference * 2.5));
323 }
324 if (IsIntakeNear(kLooseTolerance)) {
325 if (::std::abs(arm_.wrist_angle()) < kWristAlmostLevel) {
326 state_ = LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER;
327 } else {
328 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
329 }
330 }
331 }
332 break;
333
334 case LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER:
335 if (disable) {
336 state_ = DISABLED_INITIALIZED;
337 } else {
338 // If we are supposed to level the shooter, set it to level, and wait
339 // until it is very close to level.
340 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
341 if (IsArmNear(kLooseTolerance, kTightTolerance)) {
342 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
343 }
344 }
345 break;
346
347 case LOW_ARM_ZERO_LIFT_SHOULDER:
348 if (disable) {
349 state_ = DISABLED_INITIALIZED;
350 } else {
351 // Decide where to move to. We need to move far enough to see an index
352 // pulse, but must also get high enough that we can safely level the
353 // shooter.
354 if (last_state_ != LOW_ARM_ZERO_LIFT_SHOULDER) {
355 arm_.set_unprofiled_goal(
356 MoveButKeepAbove(kShoulderWristClearAngle, arm_.shoulder_angle(),
357 ::std::max(kWristEncoderIndexDifference,
358 kShoulderEncoderIndexDifference) *
359 2.5),
360 arm_.unprofiled_goal(2, 0));
361 }
362
363 // Wait until we are level and then go for it.
364 if (IsArmNear(kLooseTolerance)) {
365 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
366 }
367 }
368 break;
369
370 case LOW_ARM_ZERO_LEVEL_SHOOTER:
371 if (disable) {
372 state_ = DISABLED_INITIALIZED;
373 } else {
374 // Move the shooter level (and keep the same height). We don't want to
375 // got to RUNNING until we are completely level so that we don't
376 // give control back in a weird case where we might crash.
377 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
378 if (IsArmNear(kLooseTolerance)) {
379 if (arm_.zeroed() && intake_.zeroed()) {
380 state_ = RUNNING;
381 } else {
382 LOG(ERROR,
383 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
384 "Arm: %d Intake %d\n",
385 arm_.zeroed(), intake_.zeroed());
386 state_ = ESTOP;
387 }
388 }
389 }
390 break;
391
392 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800393 case RUNNING:
Austin Schuh829fe572016-02-14 21:41:16 -0800394 if (disable) {
395 // TODO(austin): Enter SLOW_RUNNING if we are collided.
396
397 // If we are disabled, reset the profile to the current position.
398 intake_.ForceGoal(intake_.angle());
399 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
400 } else {
401 if (state_ == SLOW_RUNNING) {
402 // TODO(austin): Exit SLOW_RUNNING if we are not collided.
403 LOG(ERROR, "Need to transition on non-collided, not all the time.\n");
404 state_ = RUNNING;
405 }
406 }
407
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800408 if (unsafe_goal) {
409 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
Austin Schuh829fe572016-02-14 21:41:16 -0800410 unsafe_goal->max_angular_acceleration_shoulder,
411 unsafe_goal->max_angular_velocity_wrist,
412 unsafe_goal->max_angular_acceleration_wrist);
Austin Schuhe7e40892016-02-15 18:21:04 -0800413 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
Austin Schuh829fe572016-02-14 21:41:16 -0800414 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800415
Philipp Schrader0119eb12016-02-15 18:16:21 +0000416 if (collision_avoidance_enabled()) {
417 collision_avoidance_.UpdateGoal(unsafe_goal->angle_shoulder,
418 unsafe_goal->angle_wrist,
419 unsafe_goal->angle_intake);
420 } else {
421 arm_.set_unprofiled_goal(unsafe_goal->angle_shoulder,
422 unsafe_goal->angle_wrist);
423 intake_.set_unprofiled_goal(unsafe_goal->angle_intake);
424 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800425 }
426
Adam Snaider06779722016-02-14 15:26:22 -0800427 // ESTOP if we hit any of the limits. It is safe(ish) to hit the limits
428 // while zeroing since we use such low power.
429 if (state_ == RUNNING || state_ == SLOW_RUNNING) {
430 // ESTOP if we hit the hard limits.
431 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
432 state_ = ESTOP;
433 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800434 }
435 break;
436
437 case ESTOP:
438 LOG(ERROR, "Estop\n");
439 disable = true;
440 break;
441 }
442
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800443 // Set the voltage limits.
444 const double max_voltage = state_ == RUNNING ? 12.0 : kZeroingVoltage;
445 arm_.set_max_voltage(max_voltage, max_voltage);
446 intake_.set_max_voltage(max_voltage);
447
448 // Calculate the loops for a cycle.
449 arm_.Update(disable);
450 intake_.Update(disable);
451
452 // Write out all the voltages.
453 if (output) {
454 output->voltage_intake = intake_.intake_voltage();
455 output->voltage_shoulder = arm_.shoulder_voltage();
456 output->voltage_wrist = arm_.wrist_voltage();
457 }
458
459 // Save debug/internal state.
460 // TODO(austin): Save the voltage errors.
461 status->zeroed = state_ == RUNNING;
462
463 status->shoulder.angle = arm_.X_hat(0, 0);
464 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
465 status->shoulder.goal_angle = arm_.goal(0, 0);
466 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800467 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
468 status->shoulder.unprofiled_goal_angular_velocity =
469 arm_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800470 status->shoulder.estimator_state = arm_.ShoulderEstimatorState();
471
472 status->wrist.angle = arm_.X_hat(2, 0);
473 status->wrist.angular_velocity = arm_.X_hat(3, 0);
474 status->wrist.goal_angle = arm_.goal(2, 0);
475 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800476 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
477 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800478 status->wrist.estimator_state = arm_.WristEstimatorState();
479
480 status->intake.angle = intake_.X_hat(0, 0);
481 status->intake.angular_velocity = intake_.X_hat(1, 0);
482 status->intake.goal_angle = intake_.goal(0, 0);
483 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800484 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
485 status->intake.unprofiled_goal_angular_velocity =
486 intake_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800487 status->intake.estimator_state = intake_.IntakeEstimatorState();
488
489 status->estopped = (state_ == ESTOP);
490
491 status->state = state_;
492
Adam Snaider06779722016-02-14 15:26:22 -0800493 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800494}
495
Philipp Schrader0119eb12016-02-15 18:16:21 +0000496constexpr double Superstructure::kShoulderMiddleAngle;
497constexpr double Superstructure::kLooseTolerance;
498constexpr double Superstructure::kIntakeUpperClear;
499constexpr double Superstructure::kIntakeLowerClear;
500constexpr double Superstructure::kShoulderUpAngle;
501constexpr double Superstructure::kShoulderLanded;
502constexpr double Superstructure::kTightTolerance;
503constexpr double Superstructure::kWristAlmostLevel;
504constexpr double Superstructure::kShoulderWristClearAngle;
505
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800506} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000507} // namespace control_loops
508} // namespace y2016