blob: bf4d3b5616a5dd1c53e07d6d60ff5995356a41e7 [file] [log] [blame]
Comran Morshed25f81a02016-01-23 13:40:10 +00001#include "y2016/control_loops/superstructure/superstructure.h"
Austin Schuh10c2d112016-02-14 13:42:28 -08002#include "y2016/control_loops/superstructure/superstructure_controls.h"
Comran Morshed25f81a02016-01-23 13:40:10 +00003
4#include "aos/common/controls/control_loops.q.h"
5#include "aos/common/logging/logging.h"
6
Austin Schuh2fc10fa2016-02-08 00:44:34 -08007#include "y2016/control_loops/superstructure/integral_intake_plant.h"
8#include "y2016/control_loops/superstructure/integral_arm_plant.h"
9
10#include "y2016/constants.h"
11
Comran Morshed25f81a02016-01-23 13:40:10 +000012namespace y2016 {
13namespace control_loops {
Austin Schuh2fc10fa2016-02-08 00:44:34 -080014namespace superstructure {
Comran Morshed25f81a02016-01-23 13:40:10 +000015
Austin Schuh2fc10fa2016-02-08 00:44:34 -080016namespace {
Adam Snaider06779722016-02-14 15:26:22 -080017constexpr double kIntakeEncoderIndexDifference =
18 constants::Values::kIntakeEncoderIndexDifference;
19constexpr double kWristEncoderIndexDifference =
20 constants::Values::kWristEncoderIndexDifference;
21constexpr double kShoulderEncoderIndexDifference =
22 constants::Values::kShoulderEncoderIndexDifference;
23
Austin Schuh2fc10fa2016-02-08 00:44:34 -080024constexpr double kZeroingVoltage = 4.0;
Austin Schuh2fc10fa2016-02-08 00:44:34 -080025} // namespace
26
Austin Schuh2fc10fa2016-02-08 00:44:34 -080027Superstructure::Superstructure(
28 control_loops::SuperstructureQueue *superstructure_queue)
29 : aos::controls::ControlLoop<control_loops::SuperstructureQueue>(
30 superstructure_queue) {}
31
Adam Snaider06779722016-02-14 15:26:22 -080032bool Superstructure::IsArmNear(double shoulder_tolerance,
33 double wrist_tolerance) {
34 return ((arm_.unprofiled_goal() - arm_.X_hat())
35 .block<2, 1>(0, 0)
36 .lpNorm<Eigen::Infinity>() < shoulder_tolerance) &&
37 ((arm_.unprofiled_goal() - arm_.X_hat())
38 .block<4, 1>(0, 0)
39 .lpNorm<Eigen::Infinity>() < wrist_tolerance) &&
40 ((arm_.unprofiled_goal() - arm_.goal())
41 .block<4, 1>(0, 0)
42 .lpNorm<Eigen::Infinity>() < 1e-6);
43}
44
45bool Superstructure::IsArmNear(double tolerance) {
46 return ((arm_.unprofiled_goal() - arm_.X_hat())
47 .block<4, 1>(0, 0)
48 .lpNorm<Eigen::Infinity>() < tolerance) &&
49 ((arm_.unprofiled_goal() - arm_.goal())
50 .block<4, 1>(0, 0)
51 .lpNorm<Eigen::Infinity>() < 1e-6);
52}
53
54bool Superstructure::IsIntakeNear(double tolerance) {
55 return ((intake_.unprofiled_goal() - intake_.X_hat())
56 .block<2, 1>(0, 0)
57 .lpNorm<Eigen::Infinity>() < tolerance);
58}
59
60double Superstructure::MoveButKeepAbove(double reference_angle,
61 double current_angle,
62 double move_distance) {
63 return -MoveButKeepBelow(-reference_angle, -current_angle, -move_distance);
64}
65
66double Superstructure::MoveButKeepBelow(double reference_angle,
67 double current_angle,
68 double move_distance) {
69 // There are 3 interesting places to move to.
70 const double small_negative_move = current_angle - move_distance;
71 const double small_positive_move = current_angle + move_distance;
72 // And the reference angle.
73
74 // Move the the highest one that is below reference_angle.
75 if (small_negative_move > reference_angle) {
76 return reference_angle;
77 } else if (small_positive_move > reference_angle) {
78 return small_negative_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -080079 } else {
Adam Snaider06779722016-02-14 15:26:22 -080080 return small_positive_move;
Austin Schuh2fc10fa2016-02-08 00:44:34 -080081 }
82}
83
Adam Snaider06779722016-02-14 15:26:22 -080084constexpr double Superstructure::kShoulderMiddleAngle;
85constexpr double Superstructure::kLooseTolerance;
86constexpr double Superstructure::kIntakeUpperClear;
87constexpr double Superstructure::kIntakeLowerClear;
88constexpr double Superstructure::kShoulderUpAngle;
89constexpr double Superstructure::kShoulderLanded;
90constexpr double Superstructure::kTightTolerance;
91constexpr double Superstructure::kWristAlmostLevel;
92constexpr double Superstructure::kShoulderWristClearAngle;
93
Austin Schuh2fc10fa2016-02-08 00:44:34 -080094void Superstructure::RunIteration(
95 const control_loops::SuperstructureQueue::Goal *unsafe_goal,
96 const control_loops::SuperstructureQueue::Position *position,
97 control_loops::SuperstructureQueue::Output *output,
98 control_loops::SuperstructureQueue::Status *status) {
Adam Snaider06779722016-02-14 15:26:22 -080099 const State state_before_switch = state_;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800100 if (WasReset()) {
101 LOG(ERROR, "WPILib reset, restarting\n");
102 arm_.Reset();
103 intake_.Reset();
104 state_ = UNINITIALIZED;
105 }
106
107 // Bool to track if we should turn the motors on or not.
108 bool disable = output == nullptr;
109
110 arm_.Correct(position->shoulder, position->wrist);
111 intake_.Correct(position->intake);
112
Adam Snaider06779722016-02-14 15:26:22 -0800113 // There are 2 main zeroing paths, HIGH_ARM_ZERO and LOW_ARM_ZERO.
114 //
115 // HIGH_ARM_ZERO works by lifting the arm all the way up so it is clear,
116 // moving the shooter to be horizontal, moving the intake out, and then moving
117 // the arm back down.
118 //
119 // LOW_ARM_ZERO works by moving the intake out of the way, lifting the arm up,
120 // leveling the shooter, and then moving back down.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800121
Diana Vandenberge2843c62016-02-13 17:44:20 -0800122 if (arm_.error() || intake_.error()) {
123 state_ = ESTOP;
124 }
125
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800126 switch (state_) {
127 case UNINITIALIZED:
Adam Snaider06779722016-02-14 15:26:22 -0800128 // Wait in the uninitialized state until both the arm and intake are
129 // initialized.
130 LOG(DEBUG, "Uninitialized, waiting for intake and arm\n");
131 if (arm_.initialized() && intake_.initialized()) {
132 state_ = DISABLED_INITIALIZED;
133 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800134 disable = true;
135 break;
136
Adam Snaider06779722016-02-14 15:26:22 -0800137 case DISABLED_INITIALIZED:
138 // Wait here until we are either fully zeroed while disabled, or we become
139 // enabled. At that point, figure out if we should HIGH_ARM_ZERO or
140 // LOW_ARM_ZERO.
141 if (disable) {
142 if (arm_.zeroed() && intake_.zeroed()) {
143 state_ = SLOW_RUNNING;
144 }
145 } else {
146 if (arm_.shoulder_angle() >= kShoulderMiddleAngle) {
147 state_ = HIGH_ARM_ZERO_LIFT_ARM;
148 } else {
149 state_ = LOW_ARM_ZERO_LOWER_INTAKE;
150 }
151 }
152
153 // Set the goals to where we are now so when we start back up, we don't
154 // jump.
155 intake_.ForceGoal(intake_.angle());
156 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
157 // Set up the profile to be the zeroing profile.
158 intake_.AdjustProfile(0.5, 10);
159 arm_.AdjustProfile(0.5, 10, 0.5, 10);
160
161 // We are not ready to start doing anything yet.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800162 disable = true;
Adam Snaider06779722016-02-14 15:26:22 -0800163 break;
164
165 case HIGH_ARM_ZERO_LIFT_ARM:
166 if (disable) {
167 state_ = DISABLED_INITIALIZED;
168 } else {
169 // Raise the shoulder up out of the way.
170 arm_.set_unprofiled_goal(kShoulderUpAngle, arm_.wrist_angle());
171 if (IsArmNear(kLooseTolerance)) {
172 // Close enough, start the next move.
173 state_ = HIGH_ARM_ZERO_LEVEL_SHOOTER;
174 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800175 }
176 break;
177
Adam Snaider06779722016-02-14 15:26:22 -0800178 case HIGH_ARM_ZERO_LEVEL_SHOOTER:
179 if (disable) {
180 state_ = DISABLED_INITIALIZED;
181 } else {
182 // Move the shooter to be level.
183 arm_.set_unprofiled_goal(kShoulderUpAngle, 0.0);
184
185 if (IsArmNear(kLooseTolerance)) {
186 // Close enough, start the next move.
187 state_ = HIGH_ARM_ZERO_MOVE_INTAKE_OUT;
188 }
189 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800190 break;
191
Adam Snaider06779722016-02-14 15:26:22 -0800192 case HIGH_ARM_ZERO_MOVE_INTAKE_OUT:
193 if (disable) {
194 state_ = DISABLED_INITIALIZED;
195 } else {
196 // If we were just asked to move the intake, make sure it moves far
197 // enough.
198 if (last_state_ != HIGH_ARM_ZERO_MOVE_INTAKE_OUT) {
199 intake_.set_unprofiled_goal(
200 MoveButKeepBelow(kIntakeUpperClear, intake_.angle(),
201 kIntakeEncoderIndexDifference * 2.5));
202 }
203
204 if (IsIntakeNear(kLooseTolerance)) {
205 // Close enough, start the next move.
206 state_ = HIGH_ARM_ZERO_LOWER_ARM;
207 }
208 }
209 break;
210
211 case HIGH_ARM_ZERO_LOWER_ARM:
212 if (disable) {
213 state_ = DISABLED_INITIALIZED;
214 } else {
215 // Land the shooter in the belly-pan. It should be zeroed by the time
216 // it gets there. If not, just estop.
217 arm_.set_unprofiled_goal(kShoulderLanded, 0.0);
218 if (arm_.zeroed() && intake_.zeroed()) {
219 state_ = RUNNING;
220 } else if (IsArmNear(kLooseTolerance)) {
221 LOG(ERROR,
222 "Failed to zero while executing the HIGH_ARM_ZERO sequence. "
223 "Arm: %d Intake %d\n",
224 arm_.zeroed(), intake_.zeroed());
225 state_ = ESTOP;
226 }
227 }
228 break;
229
230 case LOW_ARM_ZERO_LOWER_INTAKE:
231 if (disable) {
232 state_ = DISABLED_INITIALIZED;
233 } else {
234 // Move the intake down out of the way of the arm. Make sure to move it
235 // far enough to zero.
236 if (last_state_ != LOW_ARM_ZERO_LOWER_INTAKE) {
237 intake_.set_unprofiled_goal(
238 MoveButKeepBelow(kIntakeLowerClear, intake_.angle(),
239 kIntakeEncoderIndexDifference * 2.5));
240 }
241 if (IsIntakeNear(kLooseTolerance)) {
242 if (::std::abs(arm_.wrist_angle()) < kWristAlmostLevel) {
243 state_ = LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER;
244 } else {
245 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
246 }
247 }
248 }
249 break;
250
251 case LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER:
252 if (disable) {
253 state_ = DISABLED_INITIALIZED;
254 } else {
255 // If we are supposed to level the shooter, set it to level, and wait
256 // until it is very close to level.
257 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
258 if (IsArmNear(kLooseTolerance, kTightTolerance)) {
259 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
260 }
261 }
262 break;
263
264 case LOW_ARM_ZERO_LIFT_SHOULDER:
265 if (disable) {
266 state_ = DISABLED_INITIALIZED;
267 } else {
268 // Decide where to move to. We need to move far enough to see an index
269 // pulse, but must also get high enough that we can safely level the
270 // shooter.
271 if (last_state_ != LOW_ARM_ZERO_LIFT_SHOULDER) {
272 arm_.set_unprofiled_goal(
273 MoveButKeepAbove(kShoulderWristClearAngle, arm_.shoulder_angle(),
274 ::std::max(kWristEncoderIndexDifference,
275 kShoulderEncoderIndexDifference) *
276 2.5),
277 arm_.unprofiled_goal(2, 0));
278 }
279
280 // Wait until we are level and then go for it.
281 if (IsArmNear(kLooseTolerance)) {
282 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
283 }
284 }
285 break;
286
287 case LOW_ARM_ZERO_LEVEL_SHOOTER:
288 if (disable) {
289 state_ = DISABLED_INITIALIZED;
290 } else {
291 // Move the shooter level (and keep the same height). We don't want to
292 // got to RUNNING until we are completely level so that we don't
293 // give control back in a weird case where we might crash.
294 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
295 if (IsArmNear(kLooseTolerance)) {
296 if (arm_.zeroed() && intake_.zeroed()) {
297 state_ = RUNNING;
298 } else {
299 LOG(ERROR,
300 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
301 "Arm: %d Intake %d\n",
302 arm_.zeroed(), intake_.zeroed());
303 state_ = ESTOP;
304 }
305 }
306 }
307 break;
308
309 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800310 case RUNNING:
Austin Schuh829fe572016-02-14 21:41:16 -0800311 if (disable) {
312 // TODO(austin): Enter SLOW_RUNNING if we are collided.
313
314 // If we are disabled, reset the profile to the current position.
315 intake_.ForceGoal(intake_.angle());
316 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
317 } else {
318 if (state_ == SLOW_RUNNING) {
319 // TODO(austin): Exit SLOW_RUNNING if we are not collided.
320 LOG(ERROR, "Need to transition on non-collided, not all the time.\n");
321 state_ = RUNNING;
322 }
323 }
324
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800325 if (unsafe_goal) {
326 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
Austin Schuh829fe572016-02-14 21:41:16 -0800327 unsafe_goal->max_angular_acceleration_shoulder,
328 unsafe_goal->max_angular_velocity_wrist,
329 unsafe_goal->max_angular_acceleration_wrist);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800330 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_wrist,
Austin Schuh829fe572016-02-14 21:41:16 -0800331 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800332
333 arm_.set_unprofiled_goal(unsafe_goal->angle_shoulder,
Austin Schuh829fe572016-02-14 21:41:16 -0800334 unsafe_goal->angle_wrist);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800335 intake_.set_unprofiled_goal(unsafe_goal->angle_intake);
336 }
337
Adam Snaider06779722016-02-14 15:26:22 -0800338 // ESTOP if we hit any of the limits. It is safe(ish) to hit the limits
339 // while zeroing since we use such low power.
340 if (state_ == RUNNING || state_ == SLOW_RUNNING) {
341 // ESTOP if we hit the hard limits.
342 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
343 state_ = ESTOP;
344 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800345 }
346 break;
347
348 case ESTOP:
349 LOG(ERROR, "Estop\n");
350 disable = true;
351 break;
352 }
353
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800354 // Set the voltage limits.
355 const double max_voltage = state_ == RUNNING ? 12.0 : kZeroingVoltage;
356 arm_.set_max_voltage(max_voltage, max_voltage);
357 intake_.set_max_voltage(max_voltage);
358
359 // Calculate the loops for a cycle.
360 arm_.Update(disable);
361 intake_.Update(disable);
362
363 // Write out all the voltages.
364 if (output) {
365 output->voltage_intake = intake_.intake_voltage();
366 output->voltage_shoulder = arm_.shoulder_voltage();
367 output->voltage_wrist = arm_.wrist_voltage();
368 }
369
370 // Save debug/internal state.
371 // TODO(austin): Save the voltage errors.
372 status->zeroed = state_ == RUNNING;
373
374 status->shoulder.angle = arm_.X_hat(0, 0);
375 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
376 status->shoulder.goal_angle = arm_.goal(0, 0);
377 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800378 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
379 status->shoulder.unprofiled_goal_angular_velocity =
380 arm_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800381 status->shoulder.estimator_state = arm_.ShoulderEstimatorState();
382
383 status->wrist.angle = arm_.X_hat(2, 0);
384 status->wrist.angular_velocity = arm_.X_hat(3, 0);
385 status->wrist.goal_angle = arm_.goal(2, 0);
386 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800387 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
388 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800389 status->wrist.estimator_state = arm_.WristEstimatorState();
390
391 status->intake.angle = intake_.X_hat(0, 0);
392 status->intake.angular_velocity = intake_.X_hat(1, 0);
393 status->intake.goal_angle = intake_.goal(0, 0);
394 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800395 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
396 status->intake.unprofiled_goal_angular_velocity =
397 intake_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800398 status->intake.estimator_state = intake_.IntakeEstimatorState();
399
400 status->estopped = (state_ == ESTOP);
401
402 status->state = state_;
403
Adam Snaider06779722016-02-14 15:26:22 -0800404 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800405}
406
407} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000408} // namespace control_loops
409} // namespace y2016