blob: 2366fb74b90689e7f0820704dfdf2dd0c681199d [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 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 Schuh6ca0f792016-03-12 14:06:14 -0800178 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) {
Brian Silverman9180fc12016-05-16 00:09:02 -0700212 LOG(DEBUG, "Collided: Shoulder %f < %f and wrist |%f| > %f.\n",
Austin Schuhc85cdd52016-02-16 13:05:35 -0800213 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.
Austin Schuh3634ed32017-02-05 16:28:49 -0800348 intake_.ForceGoal(intake_.position());
Adam Snaider06779722016-02-14 15:26:22 -0800349 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(
Austin Schuh3634ed32017-02-05 16:28:49 -0800393 MoveButKeepBelow(kIntakeUpperClear, intake_.position(),
Adam Snaider06779722016-02-14 15:26:22 -0800394 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(
Austin Schuh3634ed32017-02-05 16:28:49 -0800431 MoveButKeepBelow(kIntakeLowerClear, intake_.position(),
Adam Snaider06779722016-02-14 15:26:22 -0800432 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
Brian Silverman741b27a2016-05-16 00:09:26 -0700473 // If we're about to ask the wrist to go past one of its limits, then
474 // move the goal so it will be just at the limit when we finish lifting
475 // the shoulder. If it wasn't intersecting something before, this can't
476 // cause it to crash into anything.
477 const double ungrounded_wrist = arm_.goal(2, 0) - arm_.goal(0, 0);
478 const double unprofiled_ungrounded_wrist =
479 arm_.unprofiled_goal(2, 0) - arm_.unprofiled_goal(0, 0);
480 if (unprofiled_ungrounded_wrist >
481 constants::Values::kWristRange.upper &&
482 ungrounded_wrist >
483 constants::Values::kWristRange.upper - kWristAlmostLevel) {
484 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0),
485 constants::Values::kWristRange.upper +
486 arm_.unprofiled_goal(0, 0));
487 } else if (unprofiled_ungrounded_wrist <
488 constants::Values::kWristRange.lower &&
489 ungrounded_wrist < constants::Values::kWristRange.lower +
490 kWristAlmostLevel) {
491 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0),
492 constants::Values::kWristRange.lower +
493 arm_.unprofiled_goal(0, 0));
494 }
495
Adam Snaider06779722016-02-14 15:26:22 -0800496 // Wait until we are level and then go for it.
497 if (IsArmNear(kLooseTolerance)) {
498 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
499 }
500 }
501 break;
502
503 case LOW_ARM_ZERO_LEVEL_SHOOTER:
504 if (disable) {
505 state_ = DISABLED_INITIALIZED;
506 } else {
507 // Move the shooter level (and keep the same height). We don't want to
508 // got to RUNNING until we are completely level so that we don't
509 // give control back in a weird case where we might crash.
510 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
511 if (IsArmNear(kLooseTolerance)) {
512 if (arm_.zeroed() && intake_.zeroed()) {
513 state_ = RUNNING;
514 } else {
515 LOG(ERROR,
516 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
517 "Arm: %d Intake %d\n",
518 arm_.zeroed(), intake_.zeroed());
519 state_ = ESTOP;
520 }
521 }
522 }
523 break;
524
Austin Schuhb1d682b2016-02-16 13:07:44 -0800525 // These 4 cases are very similar.
Adam Snaider06779722016-02-14 15:26:22 -0800526 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800527 case RUNNING:
Austin Schuhb1d682b2016-02-16 13:07:44 -0800528 case LANDING_SLOW_RUNNING:
529 case LANDING_RUNNING: {
Austin Schuh829fe572016-02-14 21:41:16 -0800530 if (disable) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800531 // If we are disabled, go to slow running (or landing slow running) if
532 // we are collided.
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800533 if (is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800534 if (state_ == RUNNING) {
535 state_ = SLOW_RUNNING;
536 } else if (state_ == LANDING_RUNNING) {
537 state_ = LANDING_SLOW_RUNNING;
538 }
539 }
Austin Schuh829fe572016-02-14 21:41:16 -0800540
Austin Schuhb1d682b2016-02-16 13:07:44 -0800541 // Reset the profile to the current position so it moves well from here.
Austin Schuh3634ed32017-02-05 16:28:49 -0800542 intake_.ForceGoal(intake_.position());
Austin Schuh829fe572016-02-14 21:41:16 -0800543 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
544 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800545 // If we are in slow_running and are no longer collided, let 'er rip.
Austin Schuh829fe572016-02-14 21:41:16 -0800546 if (state_ == SLOW_RUNNING) {
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800547 if (!is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800548 state_ = RUNNING;
549 }
550 } else if (state_ == LANDING_SLOW_RUNNING) {
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800551 if (!is_collided) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800552 state_ = LANDING_RUNNING;
553 }
Austin Schuh829fe572016-02-14 21:41:16 -0800554 }
555 }
556
Austin Schuhb1d682b2016-02-16 13:07:44 -0800557 double requested_shoulder = constants::Values::kShoulderRange.lower;
558 double requested_wrist = 0.0;
559 double requested_intake = M_PI / 2.0;
560
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800561 if (unsafe_goal) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800562 // If we are in one of the landing states, limit the accelerations and
563 // velocities to land cleanly.
564 if (state_ == LANDING_SLOW_RUNNING || state_ == LANDING_RUNNING) {
565 arm_.AdjustProfile(0.5, // Shoulder Velocity
566 4.0, // Shoulder acceleration,
567 4.0, // Wrist velocity
568 10.0); // Wrist acceleration.
569 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
570 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800571
Austin Schuhb1d682b2016-02-16 13:07:44 -0800572 requested_shoulder =
573 ::std::max(unsafe_goal->angle_shoulder,
574 constants::Values::kShoulderRange.lower);
575 requested_wrist = 0.0;
576 requested_intake = unsafe_goal->angle_intake;
Austin Schuha1dd5272016-02-16 15:39:38 -0800577 // Transition to landing once the profile is close to finished for the
578 // shoulder.
579 if (arm_.goal(0, 0) > kShoulderTransitionToLanded + 1e-4 ||
580 arm_.unprofiled_goal(0, 0) > kShoulderTransitionToLanded + 1e-4) {
581 if (state_ == LANDING_RUNNING) {
582 state_ = RUNNING;
583 } else {
584 state_ = SLOW_RUNNING;
585 }
586 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000587 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800588 // Otherwise, give the user what he asked for.
589 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
590 unsafe_goal->max_angular_acceleration_shoulder,
591 unsafe_goal->max_angular_velocity_wrist,
592 unsafe_goal->max_angular_acceleration_wrist);
593 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
594 unsafe_goal->max_angular_acceleration_intake);
595
596 // Except, don't let the shoulder go all the way down.
597 requested_shoulder = ::std::max(unsafe_goal->angle_shoulder,
598 kShoulderTransitionToLanded);
599 requested_wrist = unsafe_goal->angle_wrist;
600 requested_intake = unsafe_goal->angle_intake;
601
602 // Transition to landing once the profile is close to finished for the
603 // shoulder.
Austin Schuha1dd5272016-02-16 15:39:38 -0800604 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 &&
605 arm_.unprofiled_goal(0, 0) <=
606 kShoulderTransitionToLanded + 1e-4) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800607 if (state_ == RUNNING) {
608 state_ = LANDING_RUNNING;
609 } else {
610 state_ = LANDING_SLOW_RUNNING;
611 }
612 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000613 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800614 }
615
Austin Schuhb1d682b2016-02-16 13:07:44 -0800616 // Push the request out to hardware!
617 collision_avoidance_.UpdateGoal(requested_shoulder, requested_wrist,
618 requested_intake);
619
620 // ESTOP if we hit the hard limits.
621 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
622 state_ = ESTOP;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800623 }
Austin Schuhb1d682b2016-02-16 13:07:44 -0800624 } break;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800625
626 case ESTOP:
627 LOG(ERROR, "Estop\n");
628 disable = true;
629 break;
630 }
631
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800632 // Set the voltage limits.
Comran Morshed011f7d92016-02-16 23:03:06 +0000633 const double max_voltage = (state_ == RUNNING || state_ == LANDING_RUNNING)
634 ? kOperatingVoltage
635 : kZeroingVoltage;
Comran Morshed71466fe2016-04-21 20:21:14 -0700636 if (unsafe_goal) {
Austin Schuh0c001a82016-05-01 12:30:09 -0700637 constexpr float kTriggerThreshold = 12.0 * 0.90 / 0.005;
Comran Morshed71466fe2016-04-21 20:21:14 -0700638
639 if (unsafe_goal->voltage_climber > 1.0) {
640 kill_shoulder_accumulator_ +=
641 ::std::min(kMaxClimberVoltage, unsafe_goal->voltage_climber);
642 } else {
643 kill_shoulder_accumulator_ = 0.0;
644 }
645
646 if (kill_shoulder_accumulator_ > kTriggerThreshold) {
647 kill_shoulder_ = true;
648 }
649 }
Austin Schuh0c001a82016-05-01 12:30:09 -0700650 arm_.set_max_voltage(
Austin Schuh473a5652017-02-05 01:30:42 -0800651 {{kill_shoulder_ ? 0.0 : max_voltage,
652 kill_shoulder_ ? (arm_.X_hat(0, 0) < 0.05 ? kShooterHangingLowVoltage
653 : kShooterHangingVoltage)
654 : max_voltage}});
655 intake_.set_max_voltage({{max_voltage}});
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800656
Diana Vandenberg9cc9ab62016-04-20 21:27:47 -0700657 if (IsRunning() && !kill_shoulder_) {
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800658 // We don't want lots of negative voltage when we are near the bellypan on
659 // the shoulder...
660 // TODO(austin): Do I want to push negative power into the belly pan at this
661 // point? Maybe just put the goal slightly below the bellypan and call that
662 // good enough.
Austin Schuh6ca0f792016-03-12 14:06:14 -0800663 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 ||
664 arm_.X_hat(0, 0) <= kShoulderTransitionToLanded + 1e-4) {
Comran Morshed011f7d92016-02-16 23:03:06 +0000665 arm_.set_shoulder_asymetric_limits(kLandingShoulderDownVoltage,
666 max_voltage);
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800667 }
668 }
669
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800670 // Calculate the loops for a cycle.
Austin Schuhbe041152016-02-28 22:01:52 -0800671 {
672 Eigen::Matrix<double, 3, 1> error = intake_.controller().error();
Austin Schuh32501832017-02-25 18:32:56 -0800673 status->intake.position_power =
674 intake_.controller().controller().K(0, 0) * error(0, 0);
675 status->intake.velocity_power =
676 intake_.controller().controller().K(0, 1) * error(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800677 }
678
679 {
680 Eigen::Matrix<double, 6, 1> error = arm_.controller().error();
Austin Schuh32501832017-02-25 18:32:56 -0800681 status->shoulder.position_power =
682 arm_.controller().controller().K(0, 0) * error(0, 0);
683 status->shoulder.velocity_power =
684 arm_.controller().controller().K(0, 1) * error(1, 0);
685 status->wrist.position_power =
686 arm_.controller().controller().K(0, 2) * error(2, 0);
687 status->wrist.velocity_power =
688 arm_.controller().controller().K(0, 3) * error(3, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800689 }
690
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800691 arm_.Update(disable);
692 intake_.Update(disable);
693
694 // Write out all the voltages.
695 if (output) {
Austin Schuh3634ed32017-02-05 16:28:49 -0800696 output->voltage_intake = intake_.voltage();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800697 output->voltage_shoulder = arm_.shoulder_voltage();
698 output->voltage_wrist = arm_.wrist_voltage();
Comran Morshedf4cd7642016-02-15 20:40:49 +0000699
Campbell Crowleyd4fd6552016-02-21 17:53:46 -0800700 output->voltage_top_rollers = 0.0;
701 output->voltage_bottom_rollers = 0.0;
Comran Morshed71466fe2016-04-21 20:21:14 -0700702
703 output->voltage_climber = 0.0;
704 output->unfold_climber = false;
Comran Morshedf4cd7642016-02-15 20:40:49 +0000705 if (unsafe_goal) {
Comran Morshed71466fe2016-04-21 20:21:14 -0700706 // Ball detector lights.
Austin Schuhfef64ac2016-04-24 19:08:01 -0700707 ::y2016::sensors::ball_detector.FetchLatest();
Austin Schuh1defd262016-04-03 16:13:32 -0700708 bool ball_detected = false;
709 if (::y2016::sensors::ball_detector.get()) {
710 ball_detected = ::y2016::sensors::ball_detector->voltage > 2.5;
711 }
Comran Morshed71466fe2016-04-21 20:21:14 -0700712
713 // Climber.
714 output->voltage_climber = ::std::max(
715 static_cast<float>(0.0),
716 ::std::min(unsafe_goal->voltage_climber, kMaxClimberVoltage));
717 output->unfold_climber = unsafe_goal->unfold_climber;
718
719 // Intake.
Austin Schuh1defd262016-04-03 16:13:32 -0700720 if (unsafe_goal->force_intake || !ball_detected) {
721 output->voltage_top_rollers = ::std::max(
722 -kMaxIntakeTopVoltage,
723 ::std::min(unsafe_goal->voltage_top_rollers, kMaxIntakeTopVoltage));
724 output->voltage_bottom_rollers =
725 ::std::max(-kMaxIntakeBottomVoltage,
726 ::std::min(unsafe_goal->voltage_bottom_rollers,
727 kMaxIntakeBottomVoltage));
728 } else {
729 output->voltage_top_rollers = 0.0;
730 output->voltage_bottom_rollers = 0.0;
731 }
Comran Morshed71466fe2016-04-21 20:21:14 -0700732
733 // Traverse.
Austin Schuh843412b2016-03-20 16:48:46 -0700734 output->traverse_unlatched = unsafe_goal->traverse_unlatched;
735 output->traverse_down = unsafe_goal->traverse_down;
Comran Morshedf4cd7642016-02-15 20:40:49 +0000736 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800737 }
738
739 // Save debug/internal state.
Austin Schuha987f8b2016-02-28 22:02:20 -0800740 status->zeroed = arm_.zeroed() && intake_.zeroed();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800741
742 status->shoulder.angle = arm_.X_hat(0, 0);
743 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
744 status->shoulder.goal_angle = arm_.goal(0, 0);
745 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800746 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
747 status->shoulder.unprofiled_goal_angular_velocity =
748 arm_.unprofiled_goal(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800749 status->shoulder.voltage_error = arm_.X_hat(4, 0);
750 status->shoulder.calculated_velocity =
751 (arm_.shoulder_angle() - last_shoulder_angle_) / 0.005;
Austin Schuh473a5652017-02-05 01:30:42 -0800752 status->shoulder.estimator_state = arm_.EstimatorState(0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800753
754 status->wrist.angle = arm_.X_hat(2, 0);
755 status->wrist.angular_velocity = arm_.X_hat(3, 0);
756 status->wrist.goal_angle = arm_.goal(2, 0);
757 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800758 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
759 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800760 status->wrist.voltage_error = arm_.X_hat(5, 0);
761 status->wrist.calculated_velocity =
762 (arm_.wrist_angle() - last_wrist_angle_) / 0.005;
Austin Schuh473a5652017-02-05 01:30:42 -0800763 status->wrist.estimator_state = arm_.EstimatorState(1);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800764
765 status->intake.angle = intake_.X_hat(0, 0);
766 status->intake.angular_velocity = intake_.X_hat(1, 0);
767 status->intake.goal_angle = intake_.goal(0, 0);
768 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800769 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
770 status->intake.unprofiled_goal_angular_velocity =
771 intake_.unprofiled_goal(1, 0);
Comran Morshed71466fe2016-04-21 20:21:14 -0700772 status->intake.calculated_velocity =
Austin Schuh3634ed32017-02-05 16:28:49 -0800773 (intake_.position() - last_intake_angle_) / 0.005;
Austin Schuhbe041152016-02-28 22:01:52 -0800774 status->intake.voltage_error = intake_.X_hat(2, 0);
Austin Schuh473a5652017-02-05 01:30:42 -0800775 status->intake.estimator_state = intake_.EstimatorState(0);
Austin Schuhbe041152016-02-28 22:01:52 -0800776 status->intake.feedforwards_power = intake_.controller().ff_U(0, 0);
777
Austin Schuhf59b6bc2016-03-11 21:26:19 -0800778 status->shoulder_controller_index = arm_.controller_index();
779
Austin Schuhbe041152016-02-28 22:01:52 -0800780 last_shoulder_angle_ = arm_.shoulder_angle();
781 last_wrist_angle_ = arm_.wrist_angle();
Austin Schuh3634ed32017-02-05 16:28:49 -0800782 last_intake_angle_ = intake_.position();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800783
784 status->estopped = (state_ == ESTOP);
785
786 status->state = state_;
Austin Schuh5b1a4cd2016-03-11 21:28:38 -0800787 status->is_collided = is_collided;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800788
Adam Snaider06779722016-02-14 15:26:22 -0800789 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800790}
791
Philipp Schrader3da48ac2016-03-06 23:03:44 +0000792constexpr double Superstructure::kZeroingVoltage;
793constexpr double Superstructure::kOperatingVoltage;
Austin Schuh6ca0f792016-03-12 14:06:14 -0800794constexpr double Superstructure::kLandingShoulderDownVoltage;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000795constexpr double Superstructure::kShoulderMiddleAngle;
796constexpr double Superstructure::kLooseTolerance;
797constexpr double Superstructure::kIntakeUpperClear;
798constexpr double Superstructure::kIntakeLowerClear;
799constexpr double Superstructure::kShoulderUpAngle;
800constexpr double Superstructure::kShoulderLanded;
801constexpr double Superstructure::kTightTolerance;
802constexpr double Superstructure::kWristAlmostLevel;
803constexpr double Superstructure::kShoulderWristClearAngle;
Austin Schuhb1d682b2016-02-16 13:07:44 -0800804constexpr double Superstructure::kShoulderTransitionToLanded;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000805
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800806} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000807} // namespace control_loops
808} // namespace y2016