blob: ba49675883a200f1871debf6b2753b8dccd9b6da [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 {
Austin Schuhbe041152016-02-28 22:01:52 -080017constexpr double kZeroingVoltage = 5.0;
18constexpr double kOperatingVoltage = 12.0;
19constexpr double kLandingShoulderDownVoltage = -2.0;
Austin Schuh2d7820b2016-02-16 13:47:42 -080020// The maximum voltage the intake roller will be allowed to use.
Austin Schuhbe041152016-02-28 22:01:52 -080021constexpr float kMaxIntakeTopVoltage = 12.0;
22constexpr float kMaxIntakeBottomVoltage = 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) {
38 double shoulder_angle = arm_->shoulder_angle();
39 double wrist_angle = arm_->wrist_angle();
40 double intake_angle = intake_->angle();
41
42 // TODO(phil): This may need tuning to account for bounciness in the limbs or
43 // some other thing that I haven't thought of. At the very least,
44 // incorporating a small safety margin makes writing test cases much easier
45 // since you can directly compare statuses against the constants in the
46 // CollisionAvoidance class.
47 constexpr double kSafetyMargin = 0.01; // radians
48
49 // Avoid colliding the shooter with the frame.
50 // If the shoulder is below a certain angle or we want to move it below
51 // that angle, then the shooter has to stay level to the ground. Otherwise,
52 // it will crash into the frame.
53 if (shoulder_angle < kMinShoulderAngleForHorizontalShooter ||
54 shoulder_angle_goal < kMinShoulderAngleForHorizontalShooter) {
55 wrist_angle_goal = 0.0;
56
57 // Make sure that we don't move the shoulder below a certain angle until
58 // the wrist is level with the ground.
59 if (::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
60 shoulder_angle_goal =
61 ::std::max(shoulder_angle_goal,
62 kMinShoulderAngleForHorizontalShooter + kSafetyMargin);
63 }
64 }
65
66 // Is the arm where it could interfere with the intake right now?
67 bool shoulder_is_in_danger =
68 (shoulder_angle < kMinShoulderAngleForIntakeInterference &&
69 shoulder_angle > kMaxShoulderAngleUntilSafeIntakeStowing);
70
71 // Is the arm moving into collision zone from above?
72 bool shoulder_moving_into_danger_from_above =
73 (shoulder_angle >= kMinShoulderAngleForIntakeInterference &&
74 shoulder_angle_goal <= kMinShoulderAngleForIntakeInterference);
75
76 // Is the arm moving into collision zone from below?
77 bool shoulder_moving_into_danger_from_below =
78 (shoulder_angle <= kMaxShoulderAngleUntilSafeIntakeStowing &&
79 shoulder_angle_goal >= kMaxShoulderAngleUntilSafeIntakeStowing);
80
81 // Avoid colliding the arm with the intake.
82 if (shoulder_is_in_danger || shoulder_moving_into_danger_from_above ||
83 shoulder_moving_into_danger_from_below) {
84 // If the arm could collide with the intake, we make sure to move the
85 // intake out of the way. The arm has priority.
86 intake_angle_goal =
87 ::std::min(intake_angle_goal,
88 kMaxIntakeAngleBeforeArmInterference - kSafetyMargin);
89
90 // Don't let the shoulder move into the collision area until the intake is
91 // out of the way.
92 if (intake_angle > kMaxIntakeAngleBeforeArmInterference) {
93 const double kHalfwayPointBetweenSafeZones =
94 (kMinShoulderAngleForIntakeInterference +
95 kMaxShoulderAngleUntilSafeIntakeStowing) /
96 2.0;
97
98 if (shoulder_angle >= kHalfwayPointBetweenSafeZones) {
99 // The shoulder is closer to being above the collision area. Move it up
100 // there.
101 shoulder_angle_goal =
102 ::std::max(shoulder_angle_goal,
103 kMinShoulderAngleForIntakeInterference + kSafetyMargin);
104 } else {
105 // The shoulder is closer to being below the collision zone (i.e. in
106 // stowing/intake position), keep it there for now.
107 shoulder_angle_goal =
108 ::std::min(shoulder_angle_goal,
109 kMaxShoulderAngleUntilSafeIntakeStowing - kSafetyMargin);
110 }
111 }
112 }
113
114 // Send the possibly adjusted goals to the components.
115 arm_->set_unprofiled_goal(shoulder_angle_goal, wrist_angle_goal);
116 intake_->set_unprofiled_goal(intake_angle_goal);
117}
118
Philipp Schrader07147532016-02-16 01:23:07 +0000119bool CollisionAvoidance::collided() const {
120 return collided_with_given_angles(arm_->shoulder_angle(), arm_->wrist_angle(),
121 intake_->angle());
122}
123
124bool CollisionAvoidance::collided_with_given_angles(double shoulder_angle,
125 double wrist_angle,
126 double intake_angle) {
127 // The arm and the intake must not hit.
128 if (shoulder_angle >=
129 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing &&
130 shoulder_angle <=
131 CollisionAvoidance::kMinShoulderAngleForIntakeInterference &&
132 intake_angle > CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference) {
Austin Schuhc85cdd52016-02-16 13:05:35 -0800133 LOG(DEBUG, "Collided: Intake %f > %f, and shoulder %f < %f < %f.\n", intake_angle,
134 CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference,
135 CollisionAvoidance::kMinShoulderAngleForIntakeInterference,
136 shoulder_angle,
137 CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing);
Philipp Schrader07147532016-02-16 01:23:07 +0000138 return true;
139 }
140
141 // The wrist must go back to zero when the shoulder is moving the arm into
142 // a stowed/intaking position.
143 if (shoulder_angle <
144 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter &&
145 ::std::abs(wrist_angle) > kMaxWristAngleForSafeArmStowing) {
Austin Schuhc85cdd52016-02-16 13:05:35 -0800146 LOG(DEBUG, "Collided: Shoulder %f < %f and wrist |%f| < %f.\n",
147 shoulder_angle,
148 CollisionAvoidance::kMinShoulderAngleForHorizontalShooter, wrist_angle,
149 kMaxWristAngleForSafeArmStowing);
Philipp Schrader07147532016-02-16 01:23:07 +0000150 return true;
151 }
152
153 return false;
154}
155
Philipp Schrader0119eb12016-02-15 18:16:21 +0000156constexpr double CollisionAvoidance::kMinShoulderAngleForHorizontalShooter;
157constexpr double CollisionAvoidance::kMinShoulderAngleForIntakeInterference;
158constexpr double CollisionAvoidance::kMaxIntakeAngleBeforeArmInterference;
159constexpr double CollisionAvoidance::kMaxWristAngleForSafeArmStowing;
160constexpr double CollisionAvoidance::kMaxShoulderAngleUntilSafeIntakeStowing;
161
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800162Superstructure::Superstructure(
163 control_loops::SuperstructureQueue *superstructure_queue)
164 : aos::controls::ControlLoop<control_loops::SuperstructureQueue>(
Philipp Schrader0119eb12016-02-15 18:16:21 +0000165 superstructure_queue),
166 collision_avoidance_(&intake_, &arm_) {}
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800167
Adam Snaider06779722016-02-14 15:26:22 -0800168bool Superstructure::IsArmNear(double shoulder_tolerance,
169 double wrist_tolerance) {
170 return ((arm_.unprofiled_goal() - arm_.X_hat())
171 .block<2, 1>(0, 0)
172 .lpNorm<Eigen::Infinity>() < shoulder_tolerance) &&
173 ((arm_.unprofiled_goal() - arm_.X_hat())
174 .block<4, 1>(0, 0)
175 .lpNorm<Eigen::Infinity>() < wrist_tolerance) &&
176 ((arm_.unprofiled_goal() - arm_.goal())
177 .block<4, 1>(0, 0)
178 .lpNorm<Eigen::Infinity>() < 1e-6);
179}
180
181bool Superstructure::IsArmNear(double tolerance) {
182 return ((arm_.unprofiled_goal() - arm_.X_hat())
183 .block<4, 1>(0, 0)
184 .lpNorm<Eigen::Infinity>() < tolerance) &&
185 ((arm_.unprofiled_goal() - arm_.goal())
186 .block<4, 1>(0, 0)
187 .lpNorm<Eigen::Infinity>() < 1e-6);
188}
189
190bool Superstructure::IsIntakeNear(double tolerance) {
191 return ((intake_.unprofiled_goal() - intake_.X_hat())
192 .block<2, 1>(0, 0)
193 .lpNorm<Eigen::Infinity>() < tolerance);
194}
195
196double Superstructure::MoveButKeepAbove(double reference_angle,
197 double current_angle,
198 double move_distance) {
199 return -MoveButKeepBelow(-reference_angle, -current_angle, -move_distance);
200}
201
202double Superstructure::MoveButKeepBelow(double reference_angle,
203 double current_angle,
204 double move_distance) {
205 // There are 3 interesting places to move to.
206 const double small_negative_move = current_angle - move_distance;
207 const double small_positive_move = current_angle + move_distance;
208 // And the reference angle.
209
210 // Move the the highest one that is below reference_angle.
211 if (small_negative_move > reference_angle) {
212 return reference_angle;
213 } else if (small_positive_move > reference_angle) {
214 return small_negative_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800215 } else {
Adam Snaider06779722016-02-14 15:26:22 -0800216 return small_positive_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800217 }
218}
219
220void Superstructure::RunIteration(
221 const control_loops::SuperstructureQueue::Goal *unsafe_goal,
222 const control_loops::SuperstructureQueue::Position *position,
223 control_loops::SuperstructureQueue::Output *output,
224 control_loops::SuperstructureQueue::Status *status) {
Adam Snaider06779722016-02-14 15:26:22 -0800225 const State state_before_switch = state_;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800226 if (WasReset()) {
227 LOG(ERROR, "WPILib reset, restarting\n");
228 arm_.Reset();
229 intake_.Reset();
230 state_ = UNINITIALIZED;
231 }
232
233 // Bool to track if we should turn the motors on or not.
234 bool disable = output == nullptr;
235
236 arm_.Correct(position->shoulder, position->wrist);
237 intake_.Correct(position->intake);
238
Adam Snaider06779722016-02-14 15:26:22 -0800239 // There are 2 main zeroing paths, HIGH_ARM_ZERO and LOW_ARM_ZERO.
240 //
241 // HIGH_ARM_ZERO works by lifting the arm all the way up so it is clear,
242 // moving the shooter to be horizontal, moving the intake out, and then moving
243 // the arm back down.
244 //
245 // LOW_ARM_ZERO works by moving the intake out of the way, lifting the arm up,
246 // leveling the shooter, and then moving back down.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800247
Diana Vandenberge2843c62016-02-13 17:44:20 -0800248 if (arm_.error() || intake_.error()) {
249 state_ = ESTOP;
250 }
251
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800252 switch (state_) {
253 case UNINITIALIZED:
Adam Snaider06779722016-02-14 15:26:22 -0800254 // Wait in the uninitialized state until both the arm and intake are
255 // initialized.
256 LOG(DEBUG, "Uninitialized, waiting for intake and arm\n");
257 if (arm_.initialized() && intake_.initialized()) {
258 state_ = DISABLED_INITIALIZED;
259 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800260 disable = true;
261 break;
262
Adam Snaider06779722016-02-14 15:26:22 -0800263 case DISABLED_INITIALIZED:
264 // Wait here until we are either fully zeroed while disabled, or we become
265 // enabled. At that point, figure out if we should HIGH_ARM_ZERO or
266 // LOW_ARM_ZERO.
267 if (disable) {
268 if (arm_.zeroed() && intake_.zeroed()) {
269 state_ = SLOW_RUNNING;
270 }
271 } else {
272 if (arm_.shoulder_angle() >= kShoulderMiddleAngle) {
273 state_ = HIGH_ARM_ZERO_LIFT_ARM;
274 } else {
275 state_ = LOW_ARM_ZERO_LOWER_INTAKE;
276 }
277 }
278
279 // Set the goals to where we are now so when we start back up, we don't
280 // jump.
281 intake_.ForceGoal(intake_.angle());
282 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
283 // Set up the profile to be the zeroing profile.
284 intake_.AdjustProfile(0.5, 10);
285 arm_.AdjustProfile(0.5, 10, 0.5, 10);
286
287 // We are not ready to start doing anything yet.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800288 disable = true;
Adam Snaider06779722016-02-14 15:26:22 -0800289 break;
290
291 case HIGH_ARM_ZERO_LIFT_ARM:
292 if (disable) {
293 state_ = DISABLED_INITIALIZED;
294 } else {
295 // Raise the shoulder up out of the way.
296 arm_.set_unprofiled_goal(kShoulderUpAngle, arm_.wrist_angle());
297 if (IsArmNear(kLooseTolerance)) {
298 // Close enough, start the next move.
299 state_ = HIGH_ARM_ZERO_LEVEL_SHOOTER;
300 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800301 }
302 break;
303
Adam Snaider06779722016-02-14 15:26:22 -0800304 case HIGH_ARM_ZERO_LEVEL_SHOOTER:
305 if (disable) {
306 state_ = DISABLED_INITIALIZED;
307 } else {
308 // Move the shooter to be level.
309 arm_.set_unprofiled_goal(kShoulderUpAngle, 0.0);
310
311 if (IsArmNear(kLooseTolerance)) {
312 // Close enough, start the next move.
313 state_ = HIGH_ARM_ZERO_MOVE_INTAKE_OUT;
314 }
315 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800316 break;
317
Adam Snaider06779722016-02-14 15:26:22 -0800318 case HIGH_ARM_ZERO_MOVE_INTAKE_OUT:
319 if (disable) {
320 state_ = DISABLED_INITIALIZED;
321 } else {
322 // If we were just asked to move the intake, make sure it moves far
323 // enough.
324 if (last_state_ != HIGH_ARM_ZERO_MOVE_INTAKE_OUT) {
325 intake_.set_unprofiled_goal(
326 MoveButKeepBelow(kIntakeUpperClear, intake_.angle(),
327 kIntakeEncoderIndexDifference * 2.5));
328 }
329
330 if (IsIntakeNear(kLooseTolerance)) {
331 // Close enough, start the next move.
332 state_ = HIGH_ARM_ZERO_LOWER_ARM;
333 }
334 }
335 break;
336
337 case HIGH_ARM_ZERO_LOWER_ARM:
338 if (disable) {
339 state_ = DISABLED_INITIALIZED;
340 } else {
341 // Land the shooter in the belly-pan. It should be zeroed by the time
342 // it gets there. If not, just estop.
343 arm_.set_unprofiled_goal(kShoulderLanded, 0.0);
344 if (arm_.zeroed() && intake_.zeroed()) {
345 state_ = RUNNING;
346 } else if (IsArmNear(kLooseTolerance)) {
347 LOG(ERROR,
348 "Failed to zero while executing the HIGH_ARM_ZERO sequence. "
349 "Arm: %d Intake %d\n",
350 arm_.zeroed(), intake_.zeroed());
351 state_ = ESTOP;
352 }
353 }
354 break;
355
356 case LOW_ARM_ZERO_LOWER_INTAKE:
357 if (disable) {
358 state_ = DISABLED_INITIALIZED;
359 } else {
360 // Move the intake down out of the way of the arm. Make sure to move it
361 // far enough to zero.
362 if (last_state_ != LOW_ARM_ZERO_LOWER_INTAKE) {
363 intake_.set_unprofiled_goal(
364 MoveButKeepBelow(kIntakeLowerClear, intake_.angle(),
365 kIntakeEncoderIndexDifference * 2.5));
366 }
367 if (IsIntakeNear(kLooseTolerance)) {
368 if (::std::abs(arm_.wrist_angle()) < kWristAlmostLevel) {
369 state_ = LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER;
370 } else {
371 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
372 }
373 }
374 }
375 break;
376
377 case LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER:
378 if (disable) {
379 state_ = DISABLED_INITIALIZED;
380 } else {
381 // If we are supposed to level the shooter, set it to level, and wait
382 // until it is very close to level.
383 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
384 if (IsArmNear(kLooseTolerance, kTightTolerance)) {
385 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
386 }
387 }
388 break;
389
390 case LOW_ARM_ZERO_LIFT_SHOULDER:
391 if (disable) {
392 state_ = DISABLED_INITIALIZED;
393 } else {
394 // Decide where to move to. We need to move far enough to see an index
395 // pulse, but must also get high enough that we can safely level the
396 // shooter.
397 if (last_state_ != LOW_ARM_ZERO_LIFT_SHOULDER) {
398 arm_.set_unprofiled_goal(
399 MoveButKeepAbove(kShoulderWristClearAngle, arm_.shoulder_angle(),
400 ::std::max(kWristEncoderIndexDifference,
401 kShoulderEncoderIndexDifference) *
402 2.5),
403 arm_.unprofiled_goal(2, 0));
404 }
405
406 // Wait until we are level and then go for it.
407 if (IsArmNear(kLooseTolerance)) {
408 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
409 }
410 }
411 break;
412
413 case LOW_ARM_ZERO_LEVEL_SHOOTER:
414 if (disable) {
415 state_ = DISABLED_INITIALIZED;
416 } else {
417 // Move the shooter level (and keep the same height). We don't want to
418 // got to RUNNING until we are completely level so that we don't
419 // give control back in a weird case where we might crash.
420 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
421 if (IsArmNear(kLooseTolerance)) {
422 if (arm_.zeroed() && intake_.zeroed()) {
423 state_ = RUNNING;
424 } else {
425 LOG(ERROR,
426 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
427 "Arm: %d Intake %d\n",
428 arm_.zeroed(), intake_.zeroed());
429 state_ = ESTOP;
430 }
431 }
432 }
433 break;
434
Austin Schuhb1d682b2016-02-16 13:07:44 -0800435 // These 4 cases are very similar.
Adam Snaider06779722016-02-14 15:26:22 -0800436 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800437 case RUNNING:
Austin Schuhb1d682b2016-02-16 13:07:44 -0800438 case LANDING_SLOW_RUNNING:
439 case LANDING_RUNNING: {
Austin Schuh829fe572016-02-14 21:41:16 -0800440 if (disable) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800441 // If we are disabled, go to slow running (or landing slow running) if
442 // we are collided.
443 if (collided()) {
444 if (state_ == RUNNING) {
445 state_ = SLOW_RUNNING;
446 } else if (state_ == LANDING_RUNNING) {
447 state_ = LANDING_SLOW_RUNNING;
448 }
449 }
Austin Schuh829fe572016-02-14 21:41:16 -0800450
Austin Schuhb1d682b2016-02-16 13:07:44 -0800451 // Reset the profile to the current position so it moves well from here.
Austin Schuh829fe572016-02-14 21:41:16 -0800452 intake_.ForceGoal(intake_.angle());
453 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
454 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800455 // If we are in slow_running and are no longer collided, let 'er rip.
Austin Schuh829fe572016-02-14 21:41:16 -0800456 if (state_ == SLOW_RUNNING) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800457 if (!collided()) {
458 state_ = RUNNING;
459 }
460 } else if (state_ == LANDING_SLOW_RUNNING) {
461 if (!collided()) {
462 state_ = LANDING_RUNNING;
463 }
Austin Schuh829fe572016-02-14 21:41:16 -0800464 }
465 }
466
Austin Schuhb1d682b2016-02-16 13:07:44 -0800467 double requested_shoulder = constants::Values::kShoulderRange.lower;
468 double requested_wrist = 0.0;
469 double requested_intake = M_PI / 2.0;
470
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800471 if (unsafe_goal) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800472 // If we are in one of the landing states, limit the accelerations and
473 // velocities to land cleanly.
474 if (state_ == LANDING_SLOW_RUNNING || state_ == LANDING_RUNNING) {
475 arm_.AdjustProfile(0.5, // Shoulder Velocity
476 4.0, // Shoulder acceleration,
477 4.0, // Wrist velocity
478 10.0); // Wrist acceleration.
479 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
480 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800481
Austin Schuhb1d682b2016-02-16 13:07:44 -0800482 requested_shoulder =
483 ::std::max(unsafe_goal->angle_shoulder,
484 constants::Values::kShoulderRange.lower);
485 requested_wrist = 0.0;
486 requested_intake = unsafe_goal->angle_intake;
Austin Schuha1dd5272016-02-16 15:39:38 -0800487 // Transition to landing once the profile is close to finished for the
488 // shoulder.
489 if (arm_.goal(0, 0) > kShoulderTransitionToLanded + 1e-4 ||
490 arm_.unprofiled_goal(0, 0) > kShoulderTransitionToLanded + 1e-4) {
491 if (state_ == LANDING_RUNNING) {
492 state_ = RUNNING;
493 } else {
494 state_ = SLOW_RUNNING;
495 }
496 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000497 } else {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800498 // Otherwise, give the user what he asked for.
499 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
500 unsafe_goal->max_angular_acceleration_shoulder,
501 unsafe_goal->max_angular_velocity_wrist,
502 unsafe_goal->max_angular_acceleration_wrist);
503 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_intake,
504 unsafe_goal->max_angular_acceleration_intake);
505
506 // Except, don't let the shoulder go all the way down.
507 requested_shoulder = ::std::max(unsafe_goal->angle_shoulder,
508 kShoulderTransitionToLanded);
509 requested_wrist = unsafe_goal->angle_wrist;
510 requested_intake = unsafe_goal->angle_intake;
511
512 // Transition to landing once the profile is close to finished for the
513 // shoulder.
Austin Schuha1dd5272016-02-16 15:39:38 -0800514 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4 &&
515 arm_.unprofiled_goal(0, 0) <=
516 kShoulderTransitionToLanded + 1e-4) {
Austin Schuhb1d682b2016-02-16 13:07:44 -0800517 if (state_ == RUNNING) {
518 state_ = LANDING_RUNNING;
519 } else {
520 state_ = LANDING_SLOW_RUNNING;
521 }
522 }
Philipp Schrader0119eb12016-02-15 18:16:21 +0000523 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800524 }
525
Austin Schuhb1d682b2016-02-16 13:07:44 -0800526 // Push the request out to hardware!
527 collision_avoidance_.UpdateGoal(requested_shoulder, requested_wrist,
528 requested_intake);
529
530 // ESTOP if we hit the hard limits.
531 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
532 state_ = ESTOP;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800533 }
Austin Schuhb1d682b2016-02-16 13:07:44 -0800534 } break;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800535
536 case ESTOP:
537 LOG(ERROR, "Estop\n");
538 disable = true;
539 break;
540 }
541
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800542 // Set the voltage limits.
Comran Morshed011f7d92016-02-16 23:03:06 +0000543 const double max_voltage = (state_ == RUNNING || state_ == LANDING_RUNNING)
544 ? kOperatingVoltage
545 : kZeroingVoltage;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800546 arm_.set_max_voltage(max_voltage, max_voltage);
547 intake_.set_max_voltage(max_voltage);
548
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800549 if (IsRunning()) {
550 // We don't want lots of negative voltage when we are near the bellypan on
551 // the shoulder...
552 // TODO(austin): Do I want to push negative power into the belly pan at this
553 // point? Maybe just put the goal slightly below the bellypan and call that
554 // good enough.
555 if (arm_.goal(0, 0) <= kShoulderTransitionToLanded + 1e-4) {
Comran Morshed011f7d92016-02-16 23:03:06 +0000556 arm_.set_shoulder_asymetric_limits(kLandingShoulderDownVoltage,
557 max_voltage);
Austin Schuh9f4e8a72016-02-16 15:28:47 -0800558 }
559 }
560
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800561 // Calculate the loops for a cycle.
Austin Schuhbe041152016-02-28 22:01:52 -0800562 {
563 Eigen::Matrix<double, 3, 1> error = intake_.controller().error();
564 status->intake.position_power = intake_.controller().K(0, 0) * error(0, 0);
565 status->intake.velocity_power = intake_.controller().K(0, 1) * error(1, 0);
566 }
567
568 {
569 Eigen::Matrix<double, 6, 1> error = arm_.controller().error();
570 status->shoulder.position_power = arm_.controller().K(0, 0) * error(0, 0);
571 status->shoulder.velocity_power = arm_.controller().K(0, 1) * error(1, 0);
572 status->wrist.position_power = arm_.controller().K(0, 2) * error(2, 0);
573 status->wrist.velocity_power = arm_.controller().K(0, 3) * error(3, 0);
574 }
575
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800576 arm_.Update(disable);
577 intake_.Update(disable);
578
579 // Write out all the voltages.
580 if (output) {
581 output->voltage_intake = intake_.intake_voltage();
582 output->voltage_shoulder = arm_.shoulder_voltage();
583 output->voltage_wrist = arm_.wrist_voltage();
Comran Morshedf4cd7642016-02-15 20:40:49 +0000584
585 // Logic to run our rollers on the intake.
Campbell Crowleyd4fd6552016-02-21 17:53:46 -0800586 output->voltage_top_rollers = 0.0;
587 output->voltage_bottom_rollers = 0.0;
Comran Morshedf4cd7642016-02-15 20:40:49 +0000588 if (unsafe_goal) {
Campbell Crowleyd4fd6552016-02-21 17:53:46 -0800589 output->voltage_top_rollers = ::std::max(
590 -kMaxIntakeTopVoltage,
591 ::std::min(unsafe_goal->voltage_top_rollers, kMaxIntakeTopVoltage));
592 output->voltage_bottom_rollers = ::std::max(
593 -kMaxIntakeBottomVoltage,
594 ::std::min(unsafe_goal->voltage_bottom_rollers, kMaxIntakeBottomVoltage));
Comran Morshedf4cd7642016-02-15 20:40:49 +0000595 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800596 }
597
598 // Save debug/internal state.
Austin Schuha987f8b2016-02-28 22:02:20 -0800599 status->zeroed = arm_.zeroed() && intake_.zeroed();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800600
601 status->shoulder.angle = arm_.X_hat(0, 0);
602 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
603 status->shoulder.goal_angle = arm_.goal(0, 0);
604 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800605 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
606 status->shoulder.unprofiled_goal_angular_velocity =
607 arm_.unprofiled_goal(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800608 status->shoulder.voltage_error = arm_.X_hat(4, 0);
609 status->shoulder.calculated_velocity =
610 (arm_.shoulder_angle() - last_shoulder_angle_) / 0.005;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800611 status->shoulder.estimator_state = arm_.ShoulderEstimatorState();
612
613 status->wrist.angle = arm_.X_hat(2, 0);
614 status->wrist.angular_velocity = arm_.X_hat(3, 0);
615 status->wrist.goal_angle = arm_.goal(2, 0);
616 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800617 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
618 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800619 status->wrist.voltage_error = arm_.X_hat(5, 0);
620 status->wrist.calculated_velocity =
621 (arm_.wrist_angle() - last_wrist_angle_) / 0.005;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800622 status->wrist.estimator_state = arm_.WristEstimatorState();
623
624 status->intake.angle = intake_.X_hat(0, 0);
625 status->intake.angular_velocity = intake_.X_hat(1, 0);
626 status->intake.goal_angle = intake_.goal(0, 0);
627 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800628 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
629 status->intake.unprofiled_goal_angular_velocity =
630 intake_.unprofiled_goal(1, 0);
Austin Schuhbe041152016-02-28 22:01:52 -0800631 status->intake.calculated_velocity = (intake_.angle() - last_intake_angle_) / 0.005;
632 status->intake.voltage_error = intake_.X_hat(2, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800633 status->intake.estimator_state = intake_.IntakeEstimatorState();
Austin Schuhbe041152016-02-28 22:01:52 -0800634 status->intake.feedforwards_power = intake_.controller().ff_U(0, 0);
635
636 last_shoulder_angle_ = arm_.shoulder_angle();
637 last_wrist_angle_ = arm_.wrist_angle();
638 last_intake_angle_ = intake_.angle();
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800639
640 status->estopped = (state_ == ESTOP);
641
642 status->state = state_;
643
Adam Snaider06779722016-02-14 15:26:22 -0800644 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800645}
646
Philipp Schrader0119eb12016-02-15 18:16:21 +0000647constexpr double Superstructure::kShoulderMiddleAngle;
648constexpr double Superstructure::kLooseTolerance;
649constexpr double Superstructure::kIntakeUpperClear;
650constexpr double Superstructure::kIntakeLowerClear;
651constexpr double Superstructure::kShoulderUpAngle;
652constexpr double Superstructure::kShoulderLanded;
653constexpr double Superstructure::kTightTolerance;
654constexpr double Superstructure::kWristAlmostLevel;
655constexpr double Superstructure::kShoulderWristClearAngle;
Austin Schuhb1d682b2016-02-16 13:07:44 -0800656constexpr double Superstructure::kShoulderTransitionToLanded;
Philipp Schrader0119eb12016-02-15 18:16:21 +0000657
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800658} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000659} // namespace control_loops
660} // namespace y2016