blob: 7871732875dbf945bb92c244c5353d7046040ddb [file] [log] [blame]
Comran Morshed25f81a02016-01-23 13:40:10 +00001#include "y2016/control_loops/superstructure/superstructure.h"
Austin Schuh10c2d112016-02-14 13:42:28 -08002#include "y2016/control_loops/superstructure/superstructure_controls.h"
Comran Morshed25f81a02016-01-23 13:40:10 +00003
Austin Schuh2c717862016-03-13 15:32:53 -07004#include "aos/common/commonmath.h"
Comran Morshed25f81a02016-01-23 13:40:10 +00005#include "aos/common/controls/control_loops.q.h"
6#include "aos/common/logging/logging.h"
7
Austin Schuh2fc10fa2016-02-08 00:44:34 -08008#include "y2016/control_loops/superstructure/integral_intake_plant.h"
9#include "y2016/control_loops/superstructure/integral_arm_plant.h"
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 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) {
Austin Schuh6ca0f792016-03-12 14:06:14 -080037 const double original_shoulder_angle_goal = shoulder_angle_goal;
38 const double original_intake_angle_goal = intake_angle_goal;
Austin Schuh2c717862016-03-13 15:32:53 -070039 const double original_wrist_angle_goal = wrist_angle_goal;
Austin Schuh6ca0f792016-03-12 14:06:14 -080040
Philipp Schrader0119eb12016-02-15 18:16:21 +000041 double shoulder_angle = arm_->shoulder_angle();
42 double wrist_angle = arm_->wrist_angle();
43 double intake_angle = intake_->angle();
44
45 // TODO(phil): This may need tuning to account for bounciness in the limbs or
46 // some other thing that I haven't thought of. At the very least,
47 // incorporating a small safety margin makes writing test cases much easier
48 // since you can directly compare statuses against the constants in the
49 // CollisionAvoidance class.
Austin Schuhf59b8ee2016-03-19 21:31:36 -070050 constexpr double kSafetyMargin = 0.02; // radians
Philipp Schrader0119eb12016-02-15 18:16:21 +000051
52 // Avoid colliding the shooter with the frame.
53 // If the shoulder is below a certain angle or we want to move it below
54 // that angle, then the shooter has to stay level to the ground. Otherwise,
55 // it will crash into the frame.
Austin Schuh2c717862016-03-13 15:32:53 -070056 if (intake_angle < kMaxIntakeAngleBeforeArmInterference + kSafetyMargin) {
57 if (shoulder_angle < kMinShoulderAngleForHorizontalShooter ||
58 original_shoulder_angle_goal < kMinShoulderAngleForHorizontalShooter) {
59 wrist_angle_goal = 0.0;
60 } else if (shoulder_angle < kMinShoulderAngleForIntakeInterference ||
61 original_shoulder_angle_goal <
62 kMinShoulderAngleForIntakeInterference) {
63 wrist_angle_goal =
64 aos::Clip(original_wrist_angle_goal,
65 kMinWristAngleForMovingByIntake + kSafetyMargin,
66 kMaxWristAngleForMovingByIntake - kSafetyMargin);
67 }
68 } else {
69 if (shoulder_angle < kMinShoulderAngleForIntakeUpInterference ||
70 original_shoulder_angle_goal <
71 kMinShoulderAngleForIntakeUpInterference) {
72 wrist_angle_goal = 0.0;
73 }
74 }
75
Austin Schuh6ca0f792016-03-12 14:06:14 -080076 if (shoulder_angle < kMinShoulderAngleForIntakeUpInterference ||
77 original_shoulder_angle_goal < kMinShoulderAngleForIntakeUpInterference) {
Philipp Schrader0119eb12016-02-15 18:16:21 +000078 // Make sure that we don't move the shoulder below a certain angle until
79 // the wrist is level with the ground.
Austin Schuh6802a9d2016-03-12 21:34:53 -080080 if (intake_angle < kMaxIntakeAngleBeforeArmInterference + kSafetyMargin) {
Austin Schuh2c717862016-03-13 15:32:53 -070081 if (wrist_angle > kMaxWristAngleForMovingByIntake ||
82 wrist_angle < kMinWristAngleForMovingByIntake) {
Austin Schuh6802a9d2016-03-12 21:34:53 -080083 shoulder_angle_goal =
Austin Schuh6ca0f792016-03-12 14:06:14 -080084 ::std::max(original_shoulder_angle_goal,
Austin Schuh6802a9d2016-03-12 21:34:53 -080085 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
86 }
87 } else {
Austin Schuh2c717862016-03-13 15:32:53 -070088 if (wrist_angle > kMaxWristAngleForMovingByIntake ||
89 wrist_angle < kMinWristAngleForMovingByIntake) {
Austin Schuh6802a9d2016-03-12 21:34:53 -080090 shoulder_angle_goal =
91 ::std::max(original_shoulder_angle_goal,
92 kMinShoulderAngleForIntakeUpInterference + kSafetyMargin);
93 }
Austin Schuh6ca0f792016-03-12 14:06:14 -080094 }
Philipp Schrader0119eb12016-02-15 18:16:21 +000095 if (::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
96 shoulder_angle_goal =
Austin Schuh6802a9d2016-03-12 21:34:53 -080097 ::std::max(shoulder_angle_goal,
Philipp Schrader0119eb12016-02-15 18:16:21 +000098 kMinShoulderAngleForHorizontalShooter + kSafetyMargin);
99 }
100 }
101
102 // Is the arm where it could interfere with the intake right now?
103 bool shoulder_is_in_danger =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800104 (shoulder_angle < kMinShoulderAngleForIntakeUpInterference &&
Philipp Schrader0119eb12016-02-15 18:16:21 +0000105 shoulder_angle > kMaxShoulderAngleUntilSafeIntakeStowing);
106
107 // Is the arm moving into collision zone from above?
108 bool shoulder_moving_into_danger_from_above =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800109 (shoulder_angle >= kMinShoulderAngleForIntakeUpInterference &&
110 original_shoulder_angle_goal <=
111 kMinShoulderAngleForIntakeUpInterference);
Philipp Schrader0119eb12016-02-15 18:16:21 +0000112
113 // Is the arm moving into collision zone from below?
114 bool shoulder_moving_into_danger_from_below =
115 (shoulder_angle <= kMaxShoulderAngleUntilSafeIntakeStowing &&
Austin Schuh6ca0f792016-03-12 14:06:14 -0800116 original_shoulder_angle_goal >= kMaxShoulderAngleUntilSafeIntakeStowing);
Philipp Schrader0119eb12016-02-15 18:16:21 +0000117
118 // Avoid colliding the arm with the intake.
119 if (shoulder_is_in_danger || shoulder_moving_into_danger_from_above ||
120 shoulder_moving_into_danger_from_below) {
121 // If the arm could collide with the intake, we make sure to move the
122 // intake out of the way. The arm has priority.
123 intake_angle_goal =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800124 ::std::min(original_intake_angle_goal,
Philipp Schrader0119eb12016-02-15 18:16:21 +0000125 kMaxIntakeAngleBeforeArmInterference - kSafetyMargin);
126
127 // Don't let the shoulder move into the collision area until the intake is
128 // out of the way.
129 if (intake_angle > kMaxIntakeAngleBeforeArmInterference) {
130 const double kHalfwayPointBetweenSafeZones =
131 (kMinShoulderAngleForIntakeInterference +
132 kMaxShoulderAngleUntilSafeIntakeStowing) /
133 2.0;
134
135 if (shoulder_angle >= kHalfwayPointBetweenSafeZones) {
136 // The shoulder is closer to being above the collision area. Move it up
137 // there.
Austin Schuh2c717862016-03-13 15:32:53 -0700138 if (intake_angle <
139 kMaxIntakeAngleBeforeArmInterference + kSafetyMargin) {
140 shoulder_angle_goal = ::std::max(
141 original_shoulder_angle_goal,
142 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
143 } else {
144 shoulder_angle_goal = ::std::max(
145 original_shoulder_angle_goal,
146 kMinShoulderAngleForIntakeUpInterference + kSafetyMargin);
147 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000148 } else {
149 // The shoulder is closer to being below the collision zone (i.e. in
150 // stowing/intake position), keep it there for now.
151 shoulder_angle_goal =
Austin Schuh6ca0f792016-03-12 14:06:14 -0800152 ::std::min(original_shoulder_angle_goal,
Philipp Schrader0119eb12016-02-15 18:16:21 +0000153 kMaxShoulderAngleUntilSafeIntakeStowing - kSafetyMargin);
154 }
155 }
156 }
157
158 // Send the possibly adjusted goals to the components.
159 arm_->set_unprofiled_goal(shoulder_angle_goal, wrist_angle_goal);
160 intake_->set_unprofiled_goal(intake_angle_goal);
161}
162
Philipp Schrader07147532016-02-16 01:23:07 +0000163bool CollisionAvoidance::collided() const {
164 return collided_with_given_angles(arm_->shoulder_angle(), arm_->wrist_angle(),
165 intake_->angle());
166}
167
168bool CollisionAvoidance::collided_with_given_angles(double shoulder_angle,
169 double wrist_angle,
170 double intake_angle) {
171 // The arm and the intake must not hit.
172 if (shoulder_angle >=
173 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing &&
174 shoulder_angle <=
Austin Schuh6ca0f792016-03-12 14:06:14 -0800175 CollisionAvoidance::kMinShoulderAngleForIntakeUpInterference &&
176 intake_angle >
177 CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference) {
178 LOG(DEBUG, "Collided: Intake %f > %f, and shoulder %f < %f < %f.\n",
179 intake_angle, CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference,
180 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing,
Austin Schuhc85cdd52016-02-16 13:05:35 -0800181 shoulder_angle,
Austin Schuh6ca0f792016-03-12 14:06:14 -0800182 CollisionAvoidance::kMinShoulderAngleForIntakeUpInterference);
183 return true;
184 }
185
186 if (shoulder_angle >=
187 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing &&
188 shoulder_angle <=
189 CollisionAvoidance::kMinShoulderAngleForIntakeInterference &&
190 intake_angle < CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference &&
191 intake_angle > Superstructure::kIntakeLowerClear &&
Austin Schuh2c717862016-03-13 15:32:53 -0700192 (wrist_angle > CollisionAvoidance::kMaxWristAngleForMovingByIntake ||
193 wrist_angle < CollisionAvoidance::kMinWristAngleForMovingByIntake)) {
194 LOG(DEBUG,
195 "Collided: Intake %f < %f < %f, shoulder %f < %f < %f, and %f < %f < "
196 "%f.\n",
Austin Schuh6ca0f792016-03-12 14:06:14 -0800197 Superstructure::kIntakeLowerClear, intake_angle,
198 CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference,
199 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing,
200 shoulder_angle,
Austin Schuh2c717862016-03-13 15:32:53 -0700201 CollisionAvoidance::kMinShoulderAngleForIntakeInterference,
202 CollisionAvoidance::kMinWristAngleForMovingByIntake, wrist_angle,
203 CollisionAvoidance::kMaxWristAngleForMovingByIntake);
Philipp Schrader07147532016-02-16 01:23:07 +0000204 return true;
205 }
206
207 // The wrist must go back to zero when the shoulder is moving the arm into
208 // a stowed/intaking position.
209 if (shoulder_angle <
210 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter &&
211 ::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
Austin Schuhc85cdd52016-02-16 13:05:35 -0800212 LOG(DEBUG, "Collided: Shoulder %f < %f and wrist |%f| < %f.\n",
213 shoulder_angle,
214 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter, wrist_angle,
215 kMaxWristAngleForSafeArmStowing);
Philipp Schrader07147532016-02-16 01:23:07 +0000216 return true;
217 }
218
219 return false;
220}
221
Philipp Schrader0119eb12016-02-15 18:16:21 +0000222constexpr double CollisionAvoidance::kMinShoulderAngleForHorizontalShooter;
223constexpr double CollisionAvoidance::kMinShoulderAngleForIntakeInterference;
224constexpr double CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference;
225constexpr double CollisionAvoidance::kMaxWristAngleForSafeArmStowing;
226constexpr double CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing;
227
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800228Superstructure::Superstructure(
229 control_loops::SuperstructureQueue *superstructure_queue)
230 : aos::controls::ControlLoop<control_loops::SuperstructureQueue>(
Philipp Schrader0119eb12016-02-15 18:16:21 +0000231 superstructure_queue),
232 collision_avoidance_(&intake_, &arm_) {}
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800233
Adam Snaider06779722016-02-14 15:26:22 -0800234bool Superstructure::IsArmNear(double shoulder_tolerance,
235 double wrist_tolerance) {
236 return ((arm_.unprofiled_goal() - arm_.X_hat())
237 .block<2, 1>(0, 0)
238 .lpNorm<Eigen::Infinity>() < shoulder_tolerance) &&
239 ((arm_.unprofiled_goal() - arm_.X_hat())
240 .block<4, 1>(0, 0)
241 .lpNorm<Eigen::Infinity>() < wrist_tolerance) &&
242 ((arm_.unprofiled_goal() - arm_.goal())
243 .block<4, 1>(0, 0)
244 .lpNorm<Eigen::Infinity>() < 1e-6);
245}
246
247bool Superstructure::IsArmNear(double tolerance) {
248 return ((arm_.unprofiled_goal() - arm_.X_hat())
249 .block<4, 1>(0, 0)
250 .lpNorm<Eigen::Infinity>() < tolerance) &&
251 ((arm_.unprofiled_goal() - arm_.goal())
252 .block<4, 1>(0, 0)
253 .lpNorm<Eigen::Infinity>() < 1e-6);
254}
255
256bool Superstructure::IsIntakeNear(double tolerance) {
257 return ((intake_.unprofiled_goal() - intake_.X_hat())
258 .block<2, 1>(0, 0)
259 .lpNorm<Eigen::Infinity>() < tolerance);
260}
261
262double Superstructure::MoveButKeepAbove(double reference_angle,
263 double current_angle,
264 double move_distance) {
265 return -MoveButKeepBelow(-reference_angle, -current_angle, -move_distance);
266}
267
268double Superstructure::MoveButKeepBelow(double reference_angle,
269 double current_angle,
270 double move_distance) {
271 // There are 3 interesting places to move to.
272 const double small_negative_move = current_angle - move_distance;
273 const double small_positive_move = current_angle + move_distance;
274 // And the reference angle.
275
276 // Move the the highest one that is below reference_angle.
277 if (small_negative_move > reference_angle) {
278 return reference_angle;
279 } else if (small_positive_move > reference_angle) {
280 return small_negative_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800281 } else {
Adam Snaider06779722016-02-14 15:26:22 -0800282 return small_positive_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800283 }
284}
285
286void Superstructure::RunIteration(
287 const control_loops::SuperstructureQueue::Goal *unsafe_goal,
288 const control_loops::SuperstructureQueue::Position *position,
289 control_loops::SuperstructureQueue::Output *output,
290 control_loops::SuperstructureQueue::Status *status) {
Adam Snaider06779722016-02-14 15:26:22 -0800291 const State state_before_switch = state_;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800292 if (WasReset()) {
293 LOG(ERROR, "WPILib reset, restarting\n");
294 arm_.Reset();
295 intake_.Reset();
296 state_ = UNINITIALIZED;
297 }
298
299 // Bool to track if we should turn the motors on or not.
300 bool disable = output == nullptr;
301
302 arm_.Correct(position->shoulder, position->wrist);
303 intake_.Correct(position->intake);
304
Adam Snaider06779722016-02-14 15:26:22 -0800305 // There are 2 main zeroing paths, HIGH_ARM_ZERO and LOW_ARM_ZERO.
306 //
307 // HIGH_ARM_ZERO works by lifting the arm all the way up so it is clear,
308 // moving the shooter to be horizontal, moving the intake out, and then moving
309 // the arm back down.
310 //
311 // LOW_ARM_ZERO works by moving the intake out of the way, lifting the arm up,
312 // leveling the shooter, and then moving back down.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800313
Diana Vandenberge2843c62016-02-13 17:44:20 -0800314 if (arm_.error() || intake_.error()) {
315 state_ = ESTOP;
316 }
317
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800318 const bool is_collided = collided();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800319 switch (state_) {
320 case UNINITIALIZED:
Adam Snaider06779722016-02-14 15:26:22 -0800321 // Wait in the uninitialized state until both the arm and intake are
322 // initialized.
323 LOG(DEBUG, "Uninitialized, waiting for intake and arm\n");
324 if (arm_.initialized() && intake_.initialized()) {
325 state_ = DISABLED_INITIALIZED;
326 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800327 disable = true;
328 break;
329
Adam Snaider06779722016-02-14 15:26:22 -0800330 case DISABLED_INITIALIZED:
331 // Wait here until we are either fully zeroed while disabled, or we become
332 // enabled. At that point, figure out if we should HIGH_ARM_ZERO or
333 // LOW_ARM_ZERO.
334 if (disable) {
335 if (arm_.zeroed() && intake_.zeroed()) {
336 state_ = SLOW_RUNNING;
337 }
338 } else {
339 if (arm_.shoulder_angle() >= kShoulderMiddleAngle) {
340 state_ = HIGH_ARM_ZERO_LIFT_ARM;
341 } else {
342 state_ = LOW_ARM_ZERO_LOWER_INTAKE;
343 }
344 }
345
346 // Set the goals to where we are now so when we start back up, we don't
347 // jump.
348 intake_.ForceGoal(intake_.angle());
349 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
350 // Set up the profile to be the zeroing profile.
351 intake_.AdjustProfile(0.5, 10);
352 arm_.AdjustProfile(0.5, 10, 0.5, 10);
353
354 // We are not ready to start doing anything yet.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800355 disable = true;
Adam Snaider06779722016-02-14 15:26:22 -0800356 break;
357
358 case HIGH_ARM_ZERO_LIFT_ARM:
359 if (disable) {
360 state_ = DISABLED_INITIALIZED;
361 } else {
362 // Raise the shoulder up out of the way.
363 arm_.set_unprofiled_goal(kShoulderUpAngle, arm_.wrist_angle());
364 if (IsArmNear(kLooseTolerance)) {
365 // Close enough, start the next move.
366 state_ = HIGH_ARM_ZERO_LEVEL_SHOOTER;
367 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800368 }
369 break;
370
Adam Snaider06779722016-02-14 15:26:22 -0800371 case HIGH_ARM_ZERO_LEVEL_SHOOTER:
372 if (disable) {
373 state_ = DISABLED_INITIALIZED;
374 } else {
375 // Move the shooter to be level.
376 arm_.set_unprofiled_goal(kShoulderUpAngle, 0.0);
377
378 if (IsArmNear(kLooseTolerance)) {
379 // Close enough, start the next move.
380 state_ = HIGH_ARM_ZERO_MOVE_INTAKE_OUT;
381 }
382 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800383 break;
384
Adam Snaider06779722016-02-14 15:26:22 -0800385 case HIGH_ARM_ZERO_MOVE_INTAKE_OUT:
386 if (disable) {
387 state_ = DISABLED_INITIALIZED;
388 } else {
389 // If we were just asked to move the intake, make sure it moves far
390 // enough.
391 if (last_state_ != HIGH_ARM_ZERO_MOVE_INTAKE_OUT) {
392 intake_.set_unprofiled_goal(
393 MoveButKeepBelow(kIntakeUpperClear, intake_.angle(),
394 kIntakeEncoderIndexDifference * 2.5));
395 }
396
397 if (IsIntakeNear(kLooseTolerance)) {
398 // Close enough, start the next move.
399 state_ = HIGH_ARM_ZERO_LOWER_ARM;
400 }
401 }
402 break;
403
404 case HIGH_ARM_ZERO_LOWER_ARM:
405 if (disable) {
406 state_ = DISABLED_INITIALIZED;
407 } else {
408 // Land the shooter in the belly-pan. It should be zeroed by the time
409 // it gets there. If not, just estop.
410 arm_.set_unprofiled_goal(kShoulderLanded, 0.0);
411 if (arm_.zeroed() && intake_.zeroed()) {
412 state_ = RUNNING;
413 } else if (IsArmNear(kLooseTolerance)) {
414 LOG(ERROR,
415 "Failed to zero while executing the HIGH_ARM_ZERO sequence. "
416 "Arm: %d Intake %d\n",
417 arm_.zeroed(), intake_.zeroed());
418 state_ = ESTOP;
419 }
420 }
421 break;
422
423 case LOW_ARM_ZERO_LOWER_INTAKE:
424 if (disable) {
425 state_ = DISABLED_INITIALIZED;
426 } else {
427 // Move the intake down out of the way of the arm. Make sure to move it
428 // far enough to zero.
429 if (last_state_ != LOW_ARM_ZERO_LOWER_INTAKE) {
430 intake_.set_unprofiled_goal(
431 MoveButKeepBelow(kIntakeLowerClear, intake_.angle(),
432 kIntakeEncoderIndexDifference * 2.5));
433 }
434 if (IsIntakeNear(kLooseTolerance)) {
435 if (::std::abs(arm_.wrist_angle()) < kWristAlmostLevel) {
436 state_ = LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER;
437 } else {
438 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
439 }
440 }
441 }
442 break;
443
444 case LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER:
445 if (disable) {
446 state_ = DISABLED_INITIALIZED;
447 } else {
448 // If we are supposed to level the shooter, set it to level, and wait
449 // until it is very close to level.
450 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
451 if (IsArmNear(kLooseTolerance, kTightTolerance)) {
452 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
453 }
454 }
455 break;
456
457 case LOW_ARM_ZERO_LIFT_SHOULDER:
458 if (disable) {
459 state_ = DISABLED_INITIALIZED;
460 } else {
461 // Decide where to move to. We need to move far enough to see an index
462 // pulse, but must also get high enough that we can safely level the
463 // shooter.
464 if (last_state_ != LOW_ARM_ZERO_LIFT_SHOULDER) {
465 arm_.set_unprofiled_goal(
466 MoveButKeepAbove(kShoulderWristClearAngle, arm_.shoulder_angle(),
467 ::std::max(kWristEncoderIndexDifference,
468 kShoulderEncoderIndexDifference) *
469 2.5),
470 arm_.unprofiled_goal(2, 0));
471 }
472
473 // Wait until we are level and then go for it.
474 if (IsArmNear(kLooseTolerance)) {
475 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
476 }
477 }
478 break;
479
480 case LOW_ARM_ZERO_LEVEL_SHOOTER:
481 if (disable) {
482 state_ = DISABLED_INITIALIZED;
483 } else {
484 // Move the shooter level (and keep the same height). We don't want to
485 // got to RUNNING until we are completely level so that we don't
486 // give control back in a weird case where we might crash.
487 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
488 if (IsArmNear(kLooseTolerance)) {
489 if (arm_.zeroed() && intake_.zeroed()) {
490 state_ = RUNNING;
491 } else {
492 LOG(ERROR,
493 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
494 "Arm: %d Intake %d\n",
495 arm_.zeroed(), intake_.zeroed());
496 state_ = ESTOP;
497 }
498 }
499 }
500 break;
501
Austin Schuhb1d682b2016-02-16 13:07:44 -0800502 // These 4 cases are very similar.
Adam Snaider06779722016-02-14 15:26:22 -0800503 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800504 case RUNNING:
Austin Schuhb1d682b2016-02-16 13:07:44 -0800505 case LANDING_SLOW_RUNNING:
506 case LANDING_RUNNING: {
Austin Schuh829fe572016-02-14 21:41:16 -0800507 if (disable) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800508 // If we are disabled, go to slow running (or landing slow running) if
509 // we are collided.
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800510 if (is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800511 if (state_ == RUNNING) {
512 state_ = SLOW_RUNNING;
513 } else if (state_ == LANDING_RUNNING) {
514 state_ = LANDING_SLOW_RUNNING;
515 }
516 }
Austin Schuh829fe572016-02-14 21:41:16 -0800517
Austin Schuhb1d682b2016-02-16 13:07:44 -0800518 // Reset the profile to the current position so it moves well from here.
Austin Schuh829fe572016-02-14 21:41:16 -0800519 intake_.ForceGoal(intake_.angle());
520 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
521 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800522 // If we are in slow_running and are no longer collided, let 'er rip.
Austin Schuh829fe572016-02-14 21:41:16 -0800523 if (state_ == SLOW_RUNNING) {
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800524 if (!is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800525 state_ = RUNNING;
526 }
527 } else if (state_ == LANDING_SLOW_RUNNING) {
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800528 if (!is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800529 state_ = LANDING_RUNNING;
530 }
Austin Schuh829fe572016-02-14 21:41:16 -0800531 }
532 }
533
Austin Schuhb1d682b2016-02-16 13:07:44 -0800534 double requested_shoulder = constants::Values::kShoulderRange.lower;
535 double requested_wrist = 0.0;
536 double requested_intake = M_PI / 2.0;
537
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800538 if (unsafe_goal) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800539 // If we are in one of the landing states, limit the accelerations and
540 // velocities to land cleanly.
541 if (state_ == LANDING_SLOW_RUNNING || state_ == LANDING_RUNNING) {
542 arm_.AdjustProfile(0.5, // Shoulder Velocity
543 4.0, // Shoulder acceleration,
544 4.0, // Wrist velocity
545 10.0); // Wrist acceleration.
546 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
547 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800548
Austin Schuhb1d682b2016-02-16 13:07:44 -0800549 requested_shoulder =
550 ::std::max(unsafe_goal->angle_shoulder,
551 constants::Values::kShoulderRange.lower);
552 requested_wrist = 0.0;
553 requested_intake = unsafe_goal->angle_intake;
Austin Schuha1dd5272016-02-16 15:39:38 -0800554 // Transition to landing once the profile is close to finished for the
555 // shoulder.
556 if (arm_.goal(0, 0) > kShoulderTransitionToLanded + 1e-4 ||
557 arm_.unprofiled_goal(0, 0) > kShoulderTransitionToLanded + 1e-4) {
558 if (state_ == LANDING_RUNNING) {
559 state_ = RUNNING;
560 } else {
561 state_ = SLOW_RUNNING;
562 }
563 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000564 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800565 // Otherwise, give the user what he asked for.
566 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
567 unsafe_goal->max_angular_acceleration_shoulder,
568 unsafe_goal->max_angular_velocity_wrist,
569 unsafe_goal->max_angular_acceleration_wrist);
570 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
571 unsafe_goal->max_angular_acceleration_intake);
572
573 // Except, don't let the shoulder go all the way down.
574 requested_shoulder = ::std::max(unsafe_goal->angle_shoulder,
575 kShoulderTransitionToLanded);
576 requested_wrist = unsafe_goal->angle_wrist;
577 requested_intake = unsafe_goal->angle_intake;
578
579 // Transition to landing once the profile is close to finished for the
580 // shoulder.
Austin Schuha1dd5272016-02-16 15:39:38 -0800581 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 &&
582 arm_.unprofiled_goal(0, 0) <=
583 kShoulderTransitionToLanded + 1e-4) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800584 if (state_ == RUNNING) {
585 state_ = LANDING_RUNNING;
586 } else {
587 state_ = LANDING_SLOW_RUNNING;
588 }
589 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000590 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800591 }
592
Austin Schuhb1d682b2016-02-16 13:07:44 -0800593 // Push the request out to hardware!
594 collision_avoidance_.UpdateGoal(requested_shoulder, requested_wrist,
595 requested_intake);
596
597 // ESTOP if we hit the hard limits.
598 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
599 state_ = ESTOP;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800600 }
Austin Schuhb1d682b2016-02-16 13:07:44 -0800601 } break;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800602
603 case ESTOP:
604 LOG(ERROR, "Estop\n");
605 disable = true;
606 break;
607 }
608
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800609 // Set the voltage limits.
Comran Morshed011f7d92016-02-16 23:03:06 +0000610 const double max_voltage = (state_ == RUNNING || state_ == LANDING_RUNNING)
611 ? kOperatingVoltage
612 : kZeroingVoltage;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800613 arm_.set_max_voltage(max_voltage, max_voltage);
614 intake_.set_max_voltage(max_voltage);
615
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800616 if (IsRunning()) {
617 // We don't want lots of negative voltage when we are near the bellypan on
618 // the shoulder...
619 // TODO(austin): Do I want to push negative power into the belly pan at this
620 // point? Maybe just put the goal slightly below the bellypan and call that
621 // good enough.
Austin Schuh6ca0f792016-03-12 14:06:14 -0800622 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 ||
623 arm_.X_hat(0, 0) <= kShoulderTransitionToLanded + 1e-4) {
Comran Morshed011f7d92016-02-16 23:03:06 +0000624 arm_.set_shoulder_asymetric_limits(kLandingShoulderDownVoltage,
625 max_voltage);
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800626 }
627 }
628
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800629 // Calculate the loops for a cycle.
Austin Schuhbe041152016-02-28 22:01:52 -0800630 {
631 Eigen::Matrix<double, 3, 1> error = intake_.controller().error();
632 status->intake.position_power = intake_.controller().K(0, 0) * error(0, 0);
633 status->intake.velocity_power = intake_.controller().K(0, 1) * error(1, 0);
634 }
635
636 {
637 Eigen::Matrix<double, 6, 1> error = arm_.controller().error();
638 status->shoulder.position_power = arm_.controller().K(0, 0) * error(0, 0);
639 status->shoulder.velocity_power = arm_.controller().K(0, 1) * error(1, 0);
640 status->wrist.position_power = arm_.controller().K(0, 2) * error(2, 0);
641 status->wrist.velocity_power = arm_.controller().K(0, 3) * error(3, 0);
642 }
643
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800644 arm_.Update(disable);
645 intake_.Update(disable);
646
647 // Write out all the voltages.
648 if (output) {
649 output->voltage_intake = intake_.intake_voltage();
650 output->voltage_shoulder = arm_.shoulder_voltage();
651 output->voltage_wrist = arm_.wrist_voltage();
Comran Morshedf4cd7642016-02-15 20:40:49 +0000652
653 // Logic to run our rollers on the intake.
Campbell Crowleyd4fd6552016-02-21 17:53:46 -0800654 output->voltage_top_rollers = 0.0;
655 output->voltage_bottom_rollers = 0.0;
Comran Morshedf4cd7642016-02-15 20:40:49 +0000656 if (unsafe_goal) {
Austin Schuh1defd262016-04-03 16:13:32 -0700657 ::y2016::sensors::ball_detector.FetchAnother();
658 bool ball_detected = false;
659 if (::y2016::sensors::ball_detector.get()) {
660 ball_detected = ::y2016::sensors::ball_detector->voltage > 2.5;
661 }
662 if (unsafe_goal->force_intake || !ball_detected) {
663 output->voltage_top_rollers = ::std::max(
664 -kMaxIntakeTopVoltage,
665 ::std::min(unsafe_goal->voltage_top_rollers, kMaxIntakeTopVoltage));
666 output->voltage_bottom_rollers =
667 ::std::max(-kMaxIntakeBottomVoltage,
668 ::std::min(unsafe_goal->voltage_bottom_rollers,
669 kMaxIntakeBottomVoltage));
670 } else {
671 output->voltage_top_rollers = 0.0;
672 output->voltage_bottom_rollers = 0.0;
673 }
Austin Schuh843412b2016-03-20 16:48:46 -0700674 output->traverse_unlatched = unsafe_goal->traverse_unlatched;
675 output->traverse_down = unsafe_goal->traverse_down;
Comran Morshedf4cd7642016-02-15 20:40:49 +0000676 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800677 }
678
679 // Save debug/internal state.
Austin Schuha987f8b2016-02-28 22:02:20 -0800680 status->zeroed = arm_.zeroed() && intake_.zeroed();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800681
682 status->shoulder.angle = arm_.X_hat(0, 0);
683 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
684 status->shoulder.goal_angle = arm_.goal(0, 0);
685 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800686 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
687 status->shoulder.unprofiled_goal_angular_velocity =
688 arm_.unprofiled_goal(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800689 status->shoulder.voltage_error = arm_.X_hat(4, 0);
690 status->shoulder.calculated_velocity =
691 (arm_.shoulder_angle() - last_shoulder_angle_) / 0.005;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800692 status->shoulder.estimator_state = arm_.ShoulderEstimatorState();
693
694 status->wrist.angle = arm_.X_hat(2, 0);
695 status->wrist.angular_velocity = arm_.X_hat(3, 0);
696 status->wrist.goal_angle = arm_.goal(2, 0);
697 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800698 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
699 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800700 status->wrist.voltage_error = arm_.X_hat(5, 0);
701 status->wrist.calculated_velocity =
702 (arm_.wrist_angle() - last_wrist_angle_) / 0.005;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800703 status->wrist.estimator_state = arm_.WristEstimatorState();
704
705 status->intake.angle = intake_.X_hat(0, 0);
706 status->intake.angular_velocity = intake_.X_hat(1, 0);
707 status->intake.goal_angle = intake_.goal(0, 0);
708 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800709 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
710 status->intake.unprofiled_goal_angular_velocity =
711 intake_.unprofiled_goal(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800712 status->intake.calculated_velocity = (intake_.angle() - last_intake_angle_) / 0.005;
713 status->intake.voltage_error = intake_.X_hat(2, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800714 status->intake.estimator_state = intake_.IntakeEstimatorState();
Austin Schuhbe041152016-02-28 22:01:52 -0800715 status->intake.feedforwards_power = intake_.controller().ff_U(0, 0);
716
Austin Schuhf59b6bc2016-03-11 21:26:19 -0800717 status->shoulder_controller_index = arm_.controller_index();
718
Austin Schuhbe041152016-02-28 22:01:52 -0800719 last_shoulder_angle_ = arm_.shoulder_angle();
720 last_wrist_angle_ = arm_.wrist_angle();
721 last_intake_angle_ = intake_.angle();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800722
723 status->estopped = (state_ == ESTOP);
724
725 status->state = state_;
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800726 status->is_collided = is_collided;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800727
Adam Snaider06779722016-02-14 15:26:22 -0800728 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800729}
730
Philipp Schrader3da48ac2016-03-06 23:03:44 +0000731constexpr double Superstructure::kZeroingVoltage;
732constexpr double Superstructure::kOperatingVoltage;
Austin Schuh6ca0f792016-03-12 14:06:14 -0800733constexpr double Superstructure::kLandingShoulderDownVoltage;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000734constexpr double Superstructure::kShoulderMiddleAngle;
735constexpr double Superstructure::kLooseTolerance;
736constexpr double Superstructure::kIntakeUpperClear;
737constexpr double Superstructure::kIntakeLowerClear;
738constexpr double Superstructure::kShoulderUpAngle;
739constexpr double Superstructure::kShoulderLanded;
740constexpr double Superstructure::kTightTolerance;
741constexpr double Superstructure::kWristAlmostLevel;
742constexpr double Superstructure::kShoulderWristClearAngle;
Austin Schuhb1d682b2016-02-16 13:07:44 -0800743constexpr double Superstructure::kShoulderTransitionToLanded;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000744
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800745} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000746} // namespace control_loops
747} // namespace y2016