blob: 9c6b182b6f257c2f7ac6110657f5c6c19d07425c [file] [log] [blame]
Brian Silverman17f503e2015-08-02 18:17:18 -07001#include "y2014/control_loops/shooter/shooter.h"
2
3#include <stdio.h>
4
5#include <algorithm>
6#include <limits>
Austin Schuh214e9c12016-11-25 17:26:20 -08007#include <chrono>
Brian Silverman17f503e2015-08-02 18:17:18 -07008
9#include "aos/common/controls/control_loops.q.h"
10#include "aos/common/logging/logging.h"
11#include "aos/common/logging/queue_logging.h"
12
13#include "y2014/constants.h"
14#include "y2014/control_loops/shooter/shooter_motor_plant.h"
15
Austin Schuh24957102015-11-28 16:04:40 -080016namespace y2014 {
Brian Silverman17f503e2015-08-02 18:17:18 -070017namespace control_loops {
18
Austin Schuh214e9c12016-11-25 17:26:20 -080019namespace chrono = ::std::chrono;
20using ::aos::monotonic_clock;
21
Austin Schuh9d4aca82015-11-08 14:41:31 -080022using ::y2014::control_loops::shooter::kSpringConstant;
23using ::y2014::control_loops::shooter::kMaxExtension;
Austin Schuh0e997732015-11-08 15:14:53 -080024using ::y2014::control_loops::shooter::kDt;
Austin Schuh9d4aca82015-11-08 14:41:31 -080025using ::y2014::control_loops::shooter::MakeShooterLoop;
Brian Silverman17f503e2015-08-02 18:17:18 -070026
27void ZeroedStateFeedbackLoop::CapU() {
28 const double old_voltage = voltage_;
29 voltage_ += U(0, 0);
30
31 uncapped_voltage_ = voltage_;
32
33 // Make sure that reality and the observer can't get too far off. There is a
34 // delay by one cycle between the applied voltage and X_hat(2, 0), so compare
35 // against last cycle's voltage.
36 if (X_hat(2, 0) > last_voltage_ + 4.0) {
37 voltage_ -= X_hat(2, 0) - (last_voltage_ + 4.0);
38 LOG(DEBUG, "Capping due to runaway\n");
39 } else if (X_hat(2, 0) < last_voltage_ - 4.0) {
40 voltage_ += X_hat(2, 0) - (last_voltage_ - 4.0);
41 LOG(DEBUG, "Capping due to runaway\n");
42 }
43
44 voltage_ = std::min(max_voltage_, voltage_);
45 voltage_ = std::max(-max_voltage_, voltage_);
46 mutable_U(0, 0) = voltage_ - old_voltage;
47
Austin Schuh24957102015-11-28 16:04:40 -080048 LOG_STRUCT(
49 DEBUG, "shooter_output",
Brian Silvermanb601d892015-12-20 18:24:38 -050050 ::y2014::control_loops::ShooterVoltageToLog(X_hat(2, 0), voltage_));
Brian Silverman17f503e2015-08-02 18:17:18 -070051
52 last_voltage_ = voltage_;
53 capped_goal_ = false;
54}
55
56void ZeroedStateFeedbackLoop::CapGoal() {
57 if (uncapped_voltage() > max_voltage_) {
58 double dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080059 if (index() == 0) {
Brian Silverman17f503e2015-08-02 18:17:18 -070060 dx = (uncapped_voltage() - max_voltage_) /
Austin Schuhc5fceb82017-02-25 16:24:12 -080061 (K(0, 0) - plant().A(1, 0) * K(0, 2) / plant().A(1, 2));
Brian Silverman17f503e2015-08-02 18:17:18 -070062 mutable_R(0, 0) -= dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080063 mutable_R(2, 0) -= -plant().A(1, 0) / plant().A(1, 2) * dx;
Brian Silverman17f503e2015-08-02 18:17:18 -070064 } else {
65 dx = (uncapped_voltage() - max_voltage_) / K(0, 0);
66 mutable_R(0, 0) -= dx;
67 }
68 capped_goal_ = true;
Austin Schuh24957102015-11-28 16:04:40 -080069 LOG_STRUCT(DEBUG, "to prevent windup",
Brian Silvermanb601d892015-12-20 18:24:38 -050070 ::y2014::control_loops::ShooterMovingGoal(dx));
Brian Silverman17f503e2015-08-02 18:17:18 -070071 } else if (uncapped_voltage() < -max_voltage_) {
72 double dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080073 if (index() == 0) {
Brian Silverman17f503e2015-08-02 18:17:18 -070074 dx = (uncapped_voltage() + max_voltage_) /
Austin Schuhc5fceb82017-02-25 16:24:12 -080075 (K(0, 0) - plant().A(1, 0) * K(0, 2) / plant().A(1, 2));
Brian Silverman17f503e2015-08-02 18:17:18 -070076 mutable_R(0, 0) -= dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080077 mutable_R(2, 0) -= -plant().A(1, 0) / plant().A(1, 2) * dx;
Brian Silverman17f503e2015-08-02 18:17:18 -070078 } else {
79 dx = (uncapped_voltage() + max_voltage_) / K(0, 0);
80 mutable_R(0, 0) -= dx;
81 }
82 capped_goal_ = true;
Austin Schuh24957102015-11-28 16:04:40 -080083 LOG_STRUCT(DEBUG, "to prevent windup",
Brian Silvermanb601d892015-12-20 18:24:38 -050084 ::y2014::control_loops::ShooterMovingGoal(dx));
Brian Silverman17f503e2015-08-02 18:17:18 -070085 } else {
86 capped_goal_ = false;
87 }
88}
89
90void ZeroedStateFeedbackLoop::RecalculatePowerGoal() {
Austin Schuhc5fceb82017-02-25 16:24:12 -080091 if (index() == 0) {
92 mutable_R(2, 0) = (-plant().A(1, 0) / plant().A(1, 2) * R(0, 0) -
93 plant().A(1, 1) / plant().A(1, 2) * R(1, 0));
Brian Silverman17f503e2015-08-02 18:17:18 -070094 } else {
Austin Schuhc5fceb82017-02-25 16:24:12 -080095 mutable_R(2, 0) = -plant().A(1, 1) / plant().A(1, 2) * R(1, 0);
Brian Silverman17f503e2015-08-02 18:17:18 -070096 }
97}
98
99void ZeroedStateFeedbackLoop::SetCalibration(double encoder_val,
100 double known_position) {
101 double old_position = absolute_position();
102 double previous_offset = offset_;
103 offset_ = known_position - encoder_val;
104 double doffset = offset_ - previous_offset;
105 mutable_X_hat(0, 0) += doffset;
Brian Silverman17f503e2015-08-02 18:17:18 -0700106 // Offset the goal so we don't move.
107 mutable_R(0, 0) += doffset;
Austin Schuhc5fceb82017-02-25 16:24:12 -0800108 if (index() == 0) {
109 mutable_R(2, 0) += -plant().A(1, 0) / plant().A(1, 2) * (doffset);
Brian Silverman17f503e2015-08-02 18:17:18 -0700110 }
Austin Schuh24957102015-11-28 16:04:40 -0800111 LOG_STRUCT(DEBUG, "sensor edge (fake?)",
Brian Silvermanb601d892015-12-20 18:24:38 -0500112 ::y2014::control_loops::ShooterChangeCalibration(
Austin Schuh24957102015-11-28 16:04:40 -0800113 encoder_val, known_position, old_position, absolute_position(),
114 previous_offset, offset_));
Brian Silverman17f503e2015-08-02 18:17:18 -0700115}
116
Brian Silvermanb601d892015-12-20 18:24:38 -0500117ShooterMotor::ShooterMotor(::y2014::control_loops::ShooterQueue *my_shooter)
118 : aos::controls::ControlLoop<::y2014::control_loops::ShooterQueue>(
Austin Schuh24957102015-11-28 16:04:40 -0800119 my_shooter),
Brian Silverman17f503e2015-08-02 18:17:18 -0700120 shooter_(MakeShooterLoop()),
121 state_(STATE_INITIALIZE),
Brian Silverman17f503e2015-08-02 18:17:18 -0700122 cycles_not_moved_(0),
123 shot_count_(0),
124 zeroed_(false),
125 distal_posedge_validation_cycles_left_(0),
126 proximal_posedge_validation_cycles_left_(0),
127 last_distal_current_(true),
128 last_proximal_current_(true) {}
129
130double ShooterMotor::PowerToPosition(double power) {
Austin Schuh24957102015-11-28 16:04:40 -0800131 const constants::Values &values = constants::GetValues();
Brian Silverman17f503e2015-08-02 18:17:18 -0700132 double maxpower = 0.5 * kSpringConstant *
133 (kMaxExtension * kMaxExtension -
134 (kMaxExtension - values.shooter.upper_limit) *
135 (kMaxExtension - values.shooter.upper_limit));
136 if (power < 0) {
Austin Schuh24957102015-11-28 16:04:40 -0800137 LOG_STRUCT(WARNING, "negative power",
Brian Silvermanb601d892015-12-20 18:24:38 -0500138 ::y2014::control_loops::PowerAdjustment(power, 0));
Brian Silverman17f503e2015-08-02 18:17:18 -0700139 power = 0;
140 } else if (power > maxpower) {
Austin Schuh24957102015-11-28 16:04:40 -0800141 LOG_STRUCT(WARNING, "power too high",
Brian Silvermanb601d892015-12-20 18:24:38 -0500142 ::y2014::control_loops::PowerAdjustment(power, maxpower));
Brian Silverman17f503e2015-08-02 18:17:18 -0700143 power = maxpower;
144 }
145
146 double mp = kMaxExtension * kMaxExtension - (power + power) / kSpringConstant;
147 double new_pos = 0.10;
148 if (mp < 0) {
149 LOG(ERROR,
150 "Power calculation has negative number before square root (%f).\n", mp);
151 } else {
152 new_pos = kMaxExtension - ::std::sqrt(mp);
153 }
154
155 new_pos = ::std::min(::std::max(new_pos, values.shooter.lower_limit),
156 values.shooter.upper_limit);
157 return new_pos;
158}
159
160double ShooterMotor::PositionToPower(double position) {
161 double power = kSpringConstant * position * (kMaxExtension - position / 2.0);
162 return power;
163}
164
165void ShooterMotor::CheckCalibrations(
Brian Silvermanb601d892015-12-20 18:24:38 -0500166 const ::y2014::control_loops::ShooterQueue::Position *position) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700167 CHECK_NOTNULL(position);
Austin Schuh24957102015-11-28 16:04:40 -0800168 const constants::Values &values = constants::GetValues();
Brian Silverman17f503e2015-08-02 18:17:18 -0700169
170 // TODO(austin): Validate that this is the right edge.
171 // If we see a posedge on any of the hall effects,
172 if (position->pusher_proximal.posedge_count != last_proximal_posedge_count_ &&
173 !last_proximal_current_) {
174 proximal_posedge_validation_cycles_left_ = 2;
175 }
176 if (proximal_posedge_validation_cycles_left_ > 0) {
177 if (position->pusher_proximal.current) {
178 --proximal_posedge_validation_cycles_left_;
179 if (proximal_posedge_validation_cycles_left_ == 0) {
180 shooter_.SetCalibration(
181 position->pusher_proximal.posedge_value,
182 values.shooter.pusher_proximal.upper_angle);
183
184 LOG(DEBUG, "Setting calibration using proximal sensor\n");
185 zeroed_ = true;
186 }
187 } else {
188 proximal_posedge_validation_cycles_left_ = 0;
189 }
190 }
191
192 if (position->pusher_distal.posedge_count != last_distal_posedge_count_ &&
193 !last_distal_current_) {
194 distal_posedge_validation_cycles_left_ = 2;
195 }
196 if (distal_posedge_validation_cycles_left_ > 0) {
197 if (position->pusher_distal.current) {
198 --distal_posedge_validation_cycles_left_;
199 if (distal_posedge_validation_cycles_left_ == 0) {
200 shooter_.SetCalibration(
201 position->pusher_distal.posedge_value,
202 values.shooter.pusher_distal.upper_angle);
203
204 LOG(DEBUG, "Setting calibration using distal sensor\n");
205 zeroed_ = true;
206 }
207 } else {
208 distal_posedge_validation_cycles_left_ = 0;
209 }
210 }
211}
212
213// Positive is out, and positive power is out.
214void ShooterMotor::RunIteration(
Brian Silvermanb601d892015-12-20 18:24:38 -0500215 const ::y2014::control_loops::ShooterQueue::Goal *goal,
216 const ::y2014::control_loops::ShooterQueue::Position *position,
217 ::y2014::control_loops::ShooterQueue::Output *output,
218 ::y2014::control_loops::ShooterQueue::Status *status) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700219 if (goal && ::std::isnan(goal->shot_power)) {
220 state_ = STATE_ESTOP;
221 LOG(ERROR, "Estopping because got a shot power of NAN.\n");
222 }
223
224 // we must always have these or we have issues.
225 if (status == NULL) {
226 if (output) output->voltage = 0;
227 LOG(ERROR, "Thought I would just check for null and die.\n");
228 return;
229 }
230 status->ready = false;
231
232 if (WasReset()) {
233 state_ = STATE_INITIALIZE;
234 last_distal_current_ = position->pusher_distal.current;
235 last_proximal_current_ = position->pusher_proximal.current;
236 }
237 if (position) {
238 shooter_.CorrectPosition(position->position);
239 }
240
241 // Disable the motors now so that all early returns will return with the
242 // motors disabled.
243 if (output) output->voltage = 0;
244
Austin Schuh24957102015-11-28 16:04:40 -0800245 const constants::Values &values = constants::GetValues();
Brian Silverman17f503e2015-08-02 18:17:18 -0700246
247 // Don't even let the control loops run.
248 bool shooter_loop_disable = false;
249
250 const bool disabled =
251 !::aos::joystick_state.get() || !::aos::joystick_state->enabled;
252
253 // If true, move the goal if we saturate.
254 bool cap_goal = false;
255
256 // TODO(austin): Move the offset if we see or don't see a hall effect when we
257 // expect to see one.
258 // Probably not needed yet.
259
260 if (position) {
Austin Schuhc5fceb82017-02-25 16:24:12 -0800261 int last_index = shooter_.index();
Brian Silverman17f503e2015-08-02 18:17:18 -0700262 if (position->plunger && position->latch) {
263 // Use the controller without the spring if the latch is set and the
264 // plunger is back
Austin Schuhc5fceb82017-02-25 16:24:12 -0800265 shooter_.set_index(1);
Brian Silverman17f503e2015-08-02 18:17:18 -0700266 } else {
267 // Otherwise use the controller with the spring.
Austin Schuhc5fceb82017-02-25 16:24:12 -0800268 shooter_.set_index(0);
Brian Silverman17f503e2015-08-02 18:17:18 -0700269 }
Austin Schuhc5fceb82017-02-25 16:24:12 -0800270 if (shooter_.index() != last_index) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700271 shooter_.RecalculatePowerGoal();
272 }
273 }
274
275 switch (state_) {
276 case STATE_INITIALIZE:
277 if (position) {
278 // Reinitialize the internal filter state.
279 shooter_.InitializeState(position->position);
280
281 // Start off with the assumption that we are at the value
282 // futhest back given our sensors.
283 if (position->pusher_distal.current) {
284 shooter_.SetCalibration(position->position,
285 values.shooter.pusher_distal.lower_angle);
286 } else if (position->pusher_proximal.current) {
287 shooter_.SetCalibration(position->position,
288 values.shooter.pusher_proximal.upper_angle);
289 } else {
290 shooter_.SetCalibration(position->position,
291 values.shooter.upper_limit);
292 }
293
294 // Go to the current position.
295 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
296 // If the plunger is all the way back, we want to be latched.
297 latch_piston_ = position->plunger;
298 brake_piston_ = false;
299 if (position->latch == latch_piston_) {
300 state_ = STATE_REQUEST_LOAD;
301 } else {
302 shooter_loop_disable = true;
303 LOG(DEBUG,
304 "Not moving on until the latch has moved to avoid a crash\n");
305 }
306 } else {
307 // If we can't start yet because we don't know where we are, set the
308 // latch and brake to their defaults.
309 latch_piston_ = true;
310 brake_piston_ = true;
311 }
312 break;
313 case STATE_REQUEST_LOAD:
314 if (position) {
315 zeroed_ = false;
316 if (position->pusher_distal.current ||
317 position->pusher_proximal.current) {
318 // We started on the sensor, back up until we are found.
319 // If the plunger is all the way back and not latched, it won't be
320 // there for long.
321 state_ = STATE_LOAD_BACKTRACK;
322
323 // The plunger is already back and latched. Don't release it.
324 if (position->plunger && position->latch) {
325 latch_piston_ = true;
326 } else {
327 latch_piston_ = false;
328 }
329 } else if (position->plunger && position->latch) {
330 // The plunger is back and we are latched. We most likely got here
331 // from Initialize, in which case we want to 'load' again anyways to
332 // zero.
333 Load();
334 latch_piston_ = true;
335 } else {
336 // Off the sensor, start loading.
337 Load();
338 latch_piston_ = false;
339 }
340 }
341
342 // Hold our current position.
343 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
344 brake_piston_ = false;
345 break;
346 case STATE_LOAD_BACKTRACK:
347 // If we are here, then that means we started past the edge where we want
348 // to zero. Move backwards until we don't see the sensor anymore.
349 // The plunger is contacting the pusher (or will be shortly).
350
351 if (!disabled) {
352 shooter_.SetGoalPosition(
Austin Schuh0e997732015-11-08 15:14:53 -0800353 shooter_.goal_position() + values.shooter.zeroing_speed * kDt,
Brian Silverman17f503e2015-08-02 18:17:18 -0700354 values.shooter.zeroing_speed);
355 }
356 cap_goal = true;
357 shooter_.set_max_voltage(4.0);
358
359 if (position) {
360 if (!position->pusher_distal.current &&
361 !position->pusher_proximal.current) {
362 Load();
363 }
364 latch_piston_ = position->plunger;
365 }
366
367 brake_piston_ = false;
368 break;
369 case STATE_LOAD:
370 // If we are disabled right now, reset the timer.
371 if (disabled) {
372 Load();
373 // Latch defaults to true when disabled. Leave it latched until we have
374 // useful sensor data.
375 latch_piston_ = true;
376 }
377 if (output == nullptr) {
378 load_timeout_ += ::aos::controls::kLoopFrequency;
379 }
380 // Go to 0, which should be the latch position, or trigger a hall effect
381 // on the way. If we don't see edges where we are supposed to, the
382 // offset will be updated by code above.
383 shooter_.SetGoalPosition(0.0, 0.0);
384
385 if (position) {
386 CheckCalibrations(position);
387
388 // Latch if the plunger is far enough back to trigger the hall effect.
389 // This happens when the distal sensor is triggered.
390 latch_piston_ = position->pusher_distal.current || position->plunger;
391
392 // Check if we are latched and back. Make sure the plunger is all the
393 // way back as well.
394 if (position->plunger && position->latch &&
395 position->pusher_distal.current) {
396 if (!zeroed_) {
397 state_ = STATE_REQUEST_LOAD;
398 } else {
399 state_ = STATE_PREPARE_SHOT;
400 }
401 } else if (position->plunger &&
402 ::std::abs(shooter_.absolute_position() -
403 shooter_.goal_position()) < 0.001) {
404 // We are at the goal, but not latched.
405 state_ = STATE_LOADING_PROBLEM;
Austin Schuh214e9c12016-11-25 17:26:20 -0800406 loading_problem_end_time_ =
407 monotonic_clock::now() + kLoadProblemEndTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700408 }
409 }
Austin Schuh214e9c12016-11-25 17:26:20 -0800410 if (load_timeout_ < monotonic_clock::now()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700411 if (position) {
Austin Schuhadf2cde2015-11-08 20:35:16 -0800412 // If none of the sensors is triggered, estop.
413 // Otherwise, trigger anyways if it has been 0.5 seconds more.
414 if (!(position->pusher_distal.current ||
415 position->pusher_proximal.current) ||
Austin Schuh214e9c12016-11-25 17:26:20 -0800416 (load_timeout_ + chrono::milliseconds(500) <
417 monotonic_clock::now())) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700418 state_ = STATE_ESTOP;
419 LOG(ERROR, "Estopping because took too long to load.\n");
420 }
421 }
422 }
423 brake_piston_ = false;
424 break;
425 case STATE_LOADING_PROBLEM:
426 if (disabled) {
427 state_ = STATE_REQUEST_LOAD;
428 break;
429 }
430 // We got to the goal, but the latch hasn't registered as down. It might
431 // be stuck, or on it's way but not there yet.
Austin Schuh214e9c12016-11-25 17:26:20 -0800432 if (monotonic_clock::now() > loading_problem_end_time_) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700433 // Timeout by unloading.
434 Unload();
435 } else if (position && position->plunger && position->latch) {
436 // If both trigger, we are latched.
437 state_ = STATE_PREPARE_SHOT;
438 }
439 // Move a bit further back to help it trigger.
440 // If the latch is slow due to the air flowing through the tubes or
441 // inertia, but is otherwise free, this won't have much time to do
442 // anything and is safe. Otherwise this gives us a bit more room to free
443 // up the latch.
444 shooter_.SetGoalPosition(values.shooter.lower_limit, 0.0);
445 if (position) {
446 LOG(DEBUG, "Waiting on latch: plunger %d, latch: %d\n",
447 position->plunger, position->latch);
448 }
449
450 latch_piston_ = true;
451 brake_piston_ = false;
452 break;
453 case STATE_PREPARE_SHOT:
454 // Move the shooter to the shot power set point and then lock the brake.
455 // TODO(austin): Timeout. Low priority.
456
457 if (goal) {
458 shooter_.SetGoalPosition(PowerToPosition(goal->shot_power), 0.0);
459 }
460
461 LOG(DEBUG, "PDIFF: absolute_position: %.2f, pow: %.2f\n",
462 shooter_.absolute_position(),
463 goal ? PowerToPosition(goal->shot_power)
464 : ::std::numeric_limits<double>::quiet_NaN());
465 if (goal &&
466 ::std::abs(shooter_.absolute_position() -
467 PowerToPosition(goal->shot_power)) < 0.001 &&
468 ::std::abs(shooter_.absolute_velocity()) < 0.005) {
469 // We are there, set the brake and move on.
470 latch_piston_ = true;
471 brake_piston_ = true;
Austin Schuh214e9c12016-11-25 17:26:20 -0800472 shooter_brake_set_time_ = monotonic_clock::now() + kShooterBrakeSetTime;
Brian Silverman17f503e2015-08-02 18:17:18 -0700473 state_ = STATE_READY;
474 } else {
475 latch_piston_ = true;
476 brake_piston_ = false;
477 }
478 if (goal && goal->unload_requested) {
479 Unload();
480 }
481 break;
482 case STATE_READY:
483 LOG(DEBUG, "In ready\n");
484 // Wait until the brake is set, and a shot is requested or the shot power
485 // is changed.
Austin Schuh214e9c12016-11-25 17:26:20 -0800486 if (monotonic_clock::now() > shooter_brake_set_time_) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700487 status->ready = true;
488 // We have waited long enough for the brake to set, turn the shooter
489 // control loop off.
490 shooter_loop_disable = true;
491 LOG(DEBUG, "Brake is now set\n");
492 if (goal && goal->shot_requested && !disabled) {
493 LOG(DEBUG, "Shooting now\n");
494 shooter_loop_disable = true;
Austin Schuh214e9c12016-11-25 17:26:20 -0800495 shot_end_time_ = monotonic_clock::now() + kShotEndTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700496 firing_starting_position_ = shooter_.absolute_position();
497 state_ = STATE_FIRE;
498 }
499 }
500 if (state_ == STATE_READY && goal &&
501 ::std::abs(shooter_.absolute_position() -
502 PowerToPosition(goal->shot_power)) > 0.002) {
503 // TODO(austin): Add a state to release the brake.
504
505 // TODO(austin): Do we want to set the brake here or after shooting?
506 // Depends on air usage.
507 status->ready = false;
508 LOG(DEBUG, "Preparing shot again.\n");
509 state_ = STATE_PREPARE_SHOT;
510 }
511
512 if (goal) {
513 shooter_.SetGoalPosition(PowerToPosition(goal->shot_power), 0.0);
514 }
515
516 latch_piston_ = true;
517 brake_piston_ = true;
518
519 if (goal && goal->unload_requested) {
520 Unload();
521 }
522 break;
523
524 case STATE_FIRE:
525 if (disabled) {
526 if (position) {
527 if (position->plunger) {
528 // If disabled and the plunger is still back there, reset the
529 // timeout.
Austin Schuh214e9c12016-11-25 17:26:20 -0800530 shot_end_time_ = monotonic_clock::now() + kShotEndTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700531 }
532 }
533 }
534 shooter_loop_disable = true;
535 // Count the number of contiguous cycles during which we haven't moved.
536 if (::std::abs(last_position_.position - shooter_.absolute_position()) <
537 0.0005) {
538 ++cycles_not_moved_;
539 } else {
540 cycles_not_moved_ = 0;
541 }
542
543 // If we have moved any amount since the start and the shooter has now
544 // been still for a couple cycles, the shot finished.
545 // Also move on if it times out.
Austin Schuh829e6ad2015-11-08 14:10:37 -0800546 if (((::std::abs(firing_starting_position_ -
547 shooter_.absolute_position()) > 0.0005 &&
Austin Schuhadf2cde2015-11-08 20:35:16 -0800548 cycles_not_moved_ > 6) ||
Austin Schuh214e9c12016-11-25 17:26:20 -0800549 monotonic_clock::now() > shot_end_time_) &&
Austin Schuh829e6ad2015-11-08 14:10:37 -0800550 ::aos::robot_state->voltage_battery > 10.5) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700551 state_ = STATE_REQUEST_LOAD;
552 ++shot_count_;
553 }
554 latch_piston_ = false;
555 brake_piston_ = true;
556 break;
557 case STATE_UNLOAD:
558 // Reset the timeouts.
559 if (disabled) Unload();
560
561 // If it is latched and the plunger is back, move the pusher back to catch
562 // the plunger.
563 bool all_back;
564 if (position) {
565 all_back = position->plunger && position->latch;
566 } else {
567 all_back = last_position_.plunger && last_position_.latch;
568 }
569
570 if (all_back) {
571 // Pull back to 0, 0.
572 shooter_.SetGoalPosition(0.0, 0.0);
573 if (shooter_.absolute_position() < 0.005) {
574 // When we are close enough, 'fire'.
575 latch_piston_ = false;
576 } else {
577 latch_piston_ = true;
578
579 if (position) {
580 CheckCalibrations(position);
581 }
582 }
583 } else {
584 // The plunger isn't all the way back, or it is and it is unlatched, so
585 // we can now unload.
586 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
587 latch_piston_ = false;
588 state_ = STATE_UNLOAD_MOVE;
Austin Schuh214e9c12016-11-25 17:26:20 -0800589 unload_timeout_ = monotonic_clock::now() + kUnloadTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700590 }
591
Austin Schuh214e9c12016-11-25 17:26:20 -0800592 if (monotonic_clock::now() > unload_timeout_) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700593 // We have been stuck trying to unload for way too long, give up and
594 // turn everything off.
595 state_ = STATE_ESTOP;
596 LOG(ERROR, "Estopping because took too long to unload.\n");
597 }
598
599 brake_piston_ = false;
600 break;
601 case STATE_UNLOAD_MOVE: {
602 if (disabled) {
Austin Schuh214e9c12016-11-25 17:26:20 -0800603 unload_timeout_ = monotonic_clock::now() + kUnloadTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700604 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
605 }
606 cap_goal = true;
607 shooter_.set_max_voltage(6.0);
608
609 // Slowly move back until we hit the upper limit.
610 // If we were at the limit last cycle, we are done unloading.
611 // This is because if we saturate, we might hit the limit before we are
612 // actually there.
613 if (shooter_.goal_position() >= values.shooter.upper_limit) {
614 shooter_.SetGoalPosition(values.shooter.upper_limit, 0.0);
615 // We don't want the loop fighting the spring when we are unloaded.
616 // Turn it off.
617 shooter_loop_disable = true;
618 state_ = STATE_READY_UNLOAD;
619 } else {
620 shooter_.SetGoalPosition(
621 ::std::min(
622 values.shooter.upper_limit,
Austin Schuh0e997732015-11-08 15:14:53 -0800623 shooter_.goal_position() + values.shooter.unload_speed * kDt),
Brian Silverman17f503e2015-08-02 18:17:18 -0700624 values.shooter.unload_speed);
625 }
626
627 latch_piston_ = false;
628 brake_piston_ = false;
629 } break;
630 case STATE_READY_UNLOAD:
631 if (goal && goal->load_requested) {
632 state_ = STATE_REQUEST_LOAD;
633 }
634 // If we are ready to load again,
635 shooter_loop_disable = true;
636
637 latch_piston_ = false;
638 brake_piston_ = false;
639 break;
640
641 case STATE_ESTOP:
642 LOG(WARNING, "estopped\n");
643 // Totally lost, go to a safe state.
644 shooter_loop_disable = true;
645 latch_piston_ = true;
646 brake_piston_ = true;
647 break;
648 }
649
650 if (!shooter_loop_disable) {
651 LOG_STRUCT(DEBUG, "running the loop",
Brian Silvermanb601d892015-12-20 18:24:38 -0500652 ::y2014::control_loops::ShooterStatusToLog(
Austin Schuh24957102015-11-28 16:04:40 -0800653 shooter_.goal_position(), shooter_.absolute_position()));
Brian Silverman17f503e2015-08-02 18:17:18 -0700654 if (!cap_goal) {
655 shooter_.set_max_voltage(12.0);
656 }
657 shooter_.Update(output == NULL);
658 if (cap_goal) {
659 shooter_.CapGoal();
660 }
661 // We don't really want to output anything if we went through everything
662 // assuming the motors weren't working.
663 if (output) output->voltage = shooter_.voltage();
664 } else {
665 shooter_.Update(true);
666 shooter_.ZeroPower();
667 if (output) output->voltage = 0.0;
668 }
669
670 status->hard_stop_power = PositionToPower(shooter_.absolute_position());
671
672 if (output) {
673 output->latch_piston = latch_piston_;
674 output->brake_piston = brake_piston_;
675 }
676
677 if (position) {
678 LOG_STRUCT(DEBUG, "internal state",
Brian Silvermanb601d892015-12-20 18:24:38 -0500679 ::y2014::control_loops::ShooterStateToLog(
Brian Silverman17f503e2015-08-02 18:17:18 -0700680 shooter_.absolute_position(), shooter_.absolute_velocity(),
681 state_, position->latch, position->pusher_proximal.current,
682 position->pusher_distal.current, position->plunger,
683 brake_piston_, latch_piston_));
684
685 last_position_ = *position;
686
687 last_distal_posedge_count_ = position->pusher_distal.posedge_count;
688 last_proximal_posedge_count_ = position->pusher_proximal.posedge_count;
689 last_distal_current_ = position->pusher_distal.current;
690 last_proximal_current_ = position->pusher_proximal.current;
691 }
692
Austin Schuhadf2cde2015-11-08 20:35:16 -0800693 status->absolute_position = shooter_.absolute_position();
694 status->absolute_velocity = shooter_.absolute_velocity();
695 status->state = state_;
696
Brian Silverman17f503e2015-08-02 18:17:18 -0700697 status->shots = shot_count_;
698}
699
700void ShooterMotor::ZeroOutputs() {
701 queue_group()->output.MakeWithBuilder()
702 .voltage(0)
703 .latch_piston(latch_piston_)
704 .brake_piston(brake_piston_)
705 .Send();
706}
707
708} // namespace control_loops
Austin Schuh24957102015-11-28 16:04:40 -0800709} // namespace y2014