blob: cdbceb45b85eb9becae515ce35e368f83e044246 [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
122 switch (state_) {
123 case UNINITIALIZED:
Adam Snaider06779722016-02-14 15:26:22 -0800124 // Wait in the uninitialized state until both the arm and intake are
125 // initialized.
126 LOG(DEBUG, "Uninitialized, waiting for intake and arm\n");
127 if (arm_.initialized() && intake_.initialized()) {
128 state_ = DISABLED_INITIALIZED;
129 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800130 disable = true;
131 break;
132
Adam Snaider06779722016-02-14 15:26:22 -0800133 case DISABLED_INITIALIZED:
134 // Wait here until we are either fully zeroed while disabled, or we become
135 // enabled. At that point, figure out if we should HIGH_ARM_ZERO or
136 // LOW_ARM_ZERO.
137 if (disable) {
138 if (arm_.zeroed() && intake_.zeroed()) {
139 state_ = SLOW_RUNNING;
140 }
141 } else {
142 if (arm_.shoulder_angle() >= kShoulderMiddleAngle) {
143 state_ = HIGH_ARM_ZERO_LIFT_ARM;
144 } else {
145 state_ = LOW_ARM_ZERO_LOWER_INTAKE;
146 }
147 }
148
149 // Set the goals to where we are now so when we start back up, we don't
150 // jump.
151 intake_.ForceGoal(intake_.angle());
152 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
153 // Set up the profile to be the zeroing profile.
154 intake_.AdjustProfile(0.5, 10);
155 arm_.AdjustProfile(0.5, 10, 0.5, 10);
156
157 // We are not ready to start doing anything yet.
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800158 disable = true;
Adam Snaider06779722016-02-14 15:26:22 -0800159 break;
160
161 case HIGH_ARM_ZERO_LIFT_ARM:
162 if (disable) {
163 state_ = DISABLED_INITIALIZED;
164 } else {
165 // Raise the shoulder up out of the way.
166 arm_.set_unprofiled_goal(kShoulderUpAngle, arm_.wrist_angle());
167 if (IsArmNear(kLooseTolerance)) {
168 // Close enough, start the next move.
169 state_ = HIGH_ARM_ZERO_LEVEL_SHOOTER;
170 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800171 }
172 break;
173
Adam Snaider06779722016-02-14 15:26:22 -0800174 case HIGH_ARM_ZERO_LEVEL_SHOOTER:
175 if (disable) {
176 state_ = DISABLED_INITIALIZED;
177 } else {
178 // Move the shooter to be level.
179 arm_.set_unprofiled_goal(kShoulderUpAngle, 0.0);
180
181 if (IsArmNear(kLooseTolerance)) {
182 // Close enough, start the next move.
183 state_ = HIGH_ARM_ZERO_MOVE_INTAKE_OUT;
184 }
185 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800186 break;
187
Adam Snaider06779722016-02-14 15:26:22 -0800188 case HIGH_ARM_ZERO_MOVE_INTAKE_OUT:
189 if (disable) {
190 state_ = DISABLED_INITIALIZED;
191 } else {
192 // If we were just asked to move the intake, make sure it moves far
193 // enough.
194 if (last_state_ != HIGH_ARM_ZERO_MOVE_INTAKE_OUT) {
195 intake_.set_unprofiled_goal(
196 MoveButKeepBelow(kIntakeUpperClear, intake_.angle(),
197 kIntakeEncoderIndexDifference * 2.5));
198 }
199
200 if (IsIntakeNear(kLooseTolerance)) {
201 // Close enough, start the next move.
202 state_ = HIGH_ARM_ZERO_LOWER_ARM;
203 }
204 }
205 break;
206
207 case HIGH_ARM_ZERO_LOWER_ARM:
208 if (disable) {
209 state_ = DISABLED_INITIALIZED;
210 } else {
211 // Land the shooter in the belly-pan. It should be zeroed by the time
212 // it gets there. If not, just estop.
213 arm_.set_unprofiled_goal(kShoulderLanded, 0.0);
214 if (arm_.zeroed() && intake_.zeroed()) {
215 state_ = RUNNING;
216 } else if (IsArmNear(kLooseTolerance)) {
217 LOG(ERROR,
218 "Failed to zero while executing the HIGH_ARM_ZERO sequence. "
219 "Arm: %d Intake %d\n",
220 arm_.zeroed(), intake_.zeroed());
221 state_ = ESTOP;
222 }
223 }
224 break;
225
226 case LOW_ARM_ZERO_LOWER_INTAKE:
227 if (disable) {
228 state_ = DISABLED_INITIALIZED;
229 } else {
230 // Move the intake down out of the way of the arm. Make sure to move it
231 // far enough to zero.
232 if (last_state_ != LOW_ARM_ZERO_LOWER_INTAKE) {
233 intake_.set_unprofiled_goal(
234 MoveButKeepBelow(kIntakeLowerClear, intake_.angle(),
235 kIntakeEncoderIndexDifference * 2.5));
236 }
237 if (IsIntakeNear(kLooseTolerance)) {
238 if (::std::abs(arm_.wrist_angle()) < kWristAlmostLevel) {
239 state_ = LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER;
240 } else {
241 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
242 }
243 }
244 }
245 break;
246
247 case LOW_ARM_ZERO_MAYBE_LEVEL_SHOOTER:
248 if (disable) {
249 state_ = DISABLED_INITIALIZED;
250 } else {
251 // If we are supposed to level the shooter, set it to level, and wait
252 // until it is very close to level.
253 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
254 if (IsArmNear(kLooseTolerance, kTightTolerance)) {
255 state_ = LOW_ARM_ZERO_LIFT_SHOULDER;
256 }
257 }
258 break;
259
260 case LOW_ARM_ZERO_LIFT_SHOULDER:
261 if (disable) {
262 state_ = DISABLED_INITIALIZED;
263 } else {
264 // Decide where to move to. We need to move far enough to see an index
265 // pulse, but must also get high enough that we can safely level the
266 // shooter.
267 if (last_state_ != LOW_ARM_ZERO_LIFT_SHOULDER) {
268 arm_.set_unprofiled_goal(
269 MoveButKeepAbove(kShoulderWristClearAngle, arm_.shoulder_angle(),
270 ::std::max(kWristEncoderIndexDifference,
271 kShoulderEncoderIndexDifference) *
272 2.5),
273 arm_.unprofiled_goal(2, 0));
274 }
275
276 // Wait until we are level and then go for it.
277 if (IsArmNear(kLooseTolerance)) {
278 state_ = LOW_ARM_ZERO_LEVEL_SHOOTER;
279 }
280 }
281 break;
282
283 case LOW_ARM_ZERO_LEVEL_SHOOTER:
284 if (disable) {
285 state_ = DISABLED_INITIALIZED;
286 } else {
287 // Move the shooter level (and keep the same height). We don't want to
288 // got to RUNNING until we are completely level so that we don't
289 // give control back in a weird case where we might crash.
290 arm_.set_unprofiled_goal(arm_.unprofiled_goal(0, 0), 0.0);
291 if (IsArmNear(kLooseTolerance)) {
292 if (arm_.zeroed() && intake_.zeroed()) {
293 state_ = RUNNING;
294 } else {
295 LOG(ERROR,
296 "Failed to zero while executing the LOW_ARM_ZERO sequence. "
297 "Arm: %d Intake %d\n",
298 arm_.zeroed(), intake_.zeroed());
299 state_ = ESTOP;
300 }
301 }
302 }
303 break;
304
305 case SLOW_RUNNING:
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800306 case RUNNING:
Austin Schuh829fe572016-02-14 21:41:16 -0800307 if (disable) {
308 // TODO(austin): Enter SLOW_RUNNING if we are collided.
309
310 // If we are disabled, reset the profile to the current position.
311 intake_.ForceGoal(intake_.angle());
312 arm_.ForceGoal(arm_.shoulder_angle(), arm_.wrist_angle());
313 } else {
314 if (state_ == SLOW_RUNNING) {
315 // TODO(austin): Exit SLOW_RUNNING if we are not collided.
316 LOG(ERROR, "Need to transition on non-collided, not all the time.\n");
317 state_ = RUNNING;
318 }
319 }
320
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800321 if (unsafe_goal) {
322 arm_.AdjustProfile(unsafe_goal->max_angular_velocity_shoulder,
Austin Schuh829fe572016-02-14 21:41:16 -0800323 unsafe_goal->max_angular_acceleration_shoulder,
324 unsafe_goal->max_angular_velocity_wrist,
325 unsafe_goal->max_angular_acceleration_wrist);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800326 intake_.AdjustProfile(unsafe_goal->max_angular_velocity_wrist,
Austin Schuh829fe572016-02-14 21:41:16 -0800327 unsafe_goal->max_angular_acceleration_intake);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800328
329 arm_.set_unprofiled_goal(unsafe_goal->angle_shoulder,
Austin Schuh829fe572016-02-14 21:41:16 -0800330 unsafe_goal->angle_wrist);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800331 intake_.set_unprofiled_goal(unsafe_goal->angle_intake);
332 }
333
Adam Snaider06779722016-02-14 15:26:22 -0800334 // ESTOP if we hit any of the limits. It is safe(ish) to hit the limits
335 // while zeroing since we use such low power.
336 if (state_ == RUNNING || state_ == SLOW_RUNNING) {
337 // ESTOP if we hit the hard limits.
338 if ((arm_.CheckHardLimits() || intake_.CheckHardLimits()) && output) {
339 state_ = ESTOP;
340 }
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800341 }
342 break;
343
344 case ESTOP:
345 LOG(ERROR, "Estop\n");
346 disable = true;
347 break;
348 }
349
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800350 // Set the voltage limits.
351 const double max_voltage = state_ == RUNNING ? 12.0 : kZeroingVoltage;
352 arm_.set_max_voltage(max_voltage, max_voltage);
353 intake_.set_max_voltage(max_voltage);
354
355 // Calculate the loops for a cycle.
356 arm_.Update(disable);
357 intake_.Update(disable);
358
359 // Write out all the voltages.
360 if (output) {
361 output->voltage_intake = intake_.intake_voltage();
362 output->voltage_shoulder = arm_.shoulder_voltage();
363 output->voltage_wrist = arm_.wrist_voltage();
364 }
365
366 // Save debug/internal state.
367 // TODO(austin): Save the voltage errors.
368 status->zeroed = state_ == RUNNING;
369
370 status->shoulder.angle = arm_.X_hat(0, 0);
371 status->shoulder.angular_velocity = arm_.X_hat(1, 0);
372 status->shoulder.goal_angle = arm_.goal(0, 0);
373 status->shoulder.goal_angular_velocity = arm_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800374 status->shoulder.unprofiled_goal_angle = arm_.unprofiled_goal(0, 0);
375 status->shoulder.unprofiled_goal_angular_velocity =
376 arm_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800377 status->shoulder.estimator_state = arm_.ShoulderEstimatorState();
378
379 status->wrist.angle = arm_.X_hat(2, 0);
380 status->wrist.angular_velocity = arm_.X_hat(3, 0);
381 status->wrist.goal_angle = arm_.goal(2, 0);
382 status->wrist.goal_angular_velocity = arm_.goal(3, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800383 status->wrist.unprofiled_goal_angle = arm_.unprofiled_goal(2, 0);
384 status->wrist.unprofiled_goal_angular_velocity = arm_.unprofiled_goal(3, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800385 status->wrist.estimator_state = arm_.WristEstimatorState();
386
387 status->intake.angle = intake_.X_hat(0, 0);
388 status->intake.angular_velocity = intake_.X_hat(1, 0);
389 status->intake.goal_angle = intake_.goal(0, 0);
390 status->intake.goal_angular_velocity = intake_.goal(1, 0);
Austin Schuh39fd6102016-02-13 22:59:48 -0800391 status->intake.unprofiled_goal_angle = intake_.unprofiled_goal(0, 0);
392 status->intake.unprofiled_goal_angular_velocity =
393 intake_.unprofiled_goal(1, 0);
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800394 status->intake.estimator_state = intake_.IntakeEstimatorState();
395
396 status->estopped = (state_ == ESTOP);
397
398 status->state = state_;
399
Adam Snaider06779722016-02-14 15:26:22 -0800400 last_state_ = state_before_switch;
Austin Schuh2fc10fa2016-02-08 00:44:34 -0800401}
402
403} // namespace superstructure
Comran Morshed25f81a02016-01-23 13:40:10 +0000404} // namespace control_loops
405} // namespace y2016