blob: 52589f76985fd11c008249f2660f3c8e1bdd3ae2 [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>
7
8#include "aos/common/controls/control_loops.q.h"
9#include "aos/common/logging/logging.h"
10#include "aos/common/logging/queue_logging.h"
11
12#include "y2014/constants.h"
13#include "y2014/control_loops/shooter/shooter_motor_plant.h"
14
15namespace frc971 {
16namespace control_loops {
17
18using ::aos::time::Time;
19
20void ZeroedStateFeedbackLoop::CapU() {
21 const double old_voltage = voltage_;
22 voltage_ += U(0, 0);
23
24 uncapped_voltage_ = voltage_;
25
26 // Make sure that reality and the observer can't get too far off. There is a
27 // delay by one cycle between the applied voltage and X_hat(2, 0), so compare
28 // against last cycle's voltage.
29 if (X_hat(2, 0) > last_voltage_ + 4.0) {
30 voltage_ -= X_hat(2, 0) - (last_voltage_ + 4.0);
31 LOG(DEBUG, "Capping due to runaway\n");
32 } else if (X_hat(2, 0) < last_voltage_ - 4.0) {
33 voltage_ += X_hat(2, 0) - (last_voltage_ - 4.0);
34 LOG(DEBUG, "Capping due to runaway\n");
35 }
36
37 voltage_ = std::min(max_voltage_, voltage_);
38 voltage_ = std::max(-max_voltage_, voltage_);
39 mutable_U(0, 0) = voltage_ - old_voltage;
40
41 LOG_STRUCT(DEBUG, "output", ShooterVoltageToLog(X_hat(2, 0), voltage_));
42
43 last_voltage_ = voltage_;
44 capped_goal_ = false;
45}
46
47void ZeroedStateFeedbackLoop::CapGoal() {
48 if (uncapped_voltage() > max_voltage_) {
49 double dx;
50 if (controller_index() == 0) {
51 dx = (uncapped_voltage() - max_voltage_) /
52 (K(0, 0) - A(1, 0) * K(0, 2) / A(1, 2));
53 mutable_R(0, 0) -= dx;
54 mutable_R(2, 0) -= -A(1, 0) / A(1, 2) * dx;
55 } else {
56 dx = (uncapped_voltage() - max_voltage_) / K(0, 0);
57 mutable_R(0, 0) -= dx;
58 }
59 capped_goal_ = true;
60 LOG_STRUCT(DEBUG, "to prevent windup", ShooterMovingGoal(dx));
61 } else if (uncapped_voltage() < -max_voltage_) {
62 double dx;
63 if (controller_index() == 0) {
64 dx = (uncapped_voltage() + max_voltage_) /
65 (K(0, 0) - A(1, 0) * K(0, 2) / A(1, 2));
66 mutable_R(0, 0) -= dx;
67 mutable_R(2, 0) -= -A(1, 0) / A(1, 2) * dx;
68 } else {
69 dx = (uncapped_voltage() + max_voltage_) / K(0, 0);
70 mutable_R(0, 0) -= dx;
71 }
72 capped_goal_ = true;
73 LOG_STRUCT(DEBUG, "to prevent windup", ShooterMovingGoal(dx));
74 } else {
75 capped_goal_ = false;
76 }
77}
78
79void ZeroedStateFeedbackLoop::RecalculatePowerGoal() {
80 if (controller_index() == 0) {
81 mutable_R(2, 0) = (-A(1, 0) / A(1, 2) * R(0, 0) - A(1, 1) / A(1, 2) * R(1, 0));
82 } else {
83 mutable_R(2, 0) = -A(1, 1) / A(1, 2) * R(1, 0);
84 }
85}
86
87void ZeroedStateFeedbackLoop::SetCalibration(double encoder_val,
88 double known_position) {
89 double old_position = absolute_position();
90 double previous_offset = offset_;
91 offset_ = known_position - encoder_val;
92 double doffset = offset_ - previous_offset;
93 mutable_X_hat(0, 0) += doffset;
Brian Silverman17f503e2015-08-02 18:17:18 -070094 // Offset the goal so we don't move.
95 mutable_R(0, 0) += doffset;
96 if (controller_index() == 0) {
97 mutable_R(2, 0) += -A(1, 0) / A(1, 2) * (doffset);
98 }
99 LOG_STRUCT(
100 DEBUG, "sensor edge (fake?)",
101 ShooterChangeCalibration(encoder_val, known_position, old_position,
102 absolute_position(), previous_offset, offset_));
103}
104
Brian Silvermanbe5ded62015-05-14 00:23:49 -0400105ShooterMotor::ShooterMotor(control_loops::ShooterQueue *my_shooter)
106 : aos::controls::ControlLoop<control_loops::ShooterQueue>(my_shooter),
Brian Silverman17f503e2015-08-02 18:17:18 -0700107 shooter_(MakeShooterLoop()),
108 state_(STATE_INITIALIZE),
109 loading_problem_end_time_(0, 0),
110 load_timeout_(0, 0),
111 shooter_brake_set_time_(0, 0),
112 unload_timeout_(0, 0),
113 shot_end_time_(0, 0),
114 cycles_not_moved_(0),
115 shot_count_(0),
116 zeroed_(false),
117 distal_posedge_validation_cycles_left_(0),
118 proximal_posedge_validation_cycles_left_(0),
119 last_distal_current_(true),
120 last_proximal_current_(true) {}
121
122double ShooterMotor::PowerToPosition(double power) {
123 const frc971::constants::Values &values = constants::GetValues();
124 double maxpower = 0.5 * kSpringConstant *
125 (kMaxExtension * kMaxExtension -
126 (kMaxExtension - values.shooter.upper_limit) *
127 (kMaxExtension - values.shooter.upper_limit));
128 if (power < 0) {
129 LOG_STRUCT(WARNING, "negative power", PowerAdjustment(power, 0));
130 power = 0;
131 } else if (power > maxpower) {
132 LOG_STRUCT(WARNING, "power too high", PowerAdjustment(power, maxpower));
133 power = maxpower;
134 }
135
136 double mp = kMaxExtension * kMaxExtension - (power + power) / kSpringConstant;
137 double new_pos = 0.10;
138 if (mp < 0) {
139 LOG(ERROR,
140 "Power calculation has negative number before square root (%f).\n", mp);
141 } else {
142 new_pos = kMaxExtension - ::std::sqrt(mp);
143 }
144
145 new_pos = ::std::min(::std::max(new_pos, values.shooter.lower_limit),
146 values.shooter.upper_limit);
147 return new_pos;
148}
149
150double ShooterMotor::PositionToPower(double position) {
151 double power = kSpringConstant * position * (kMaxExtension - position / 2.0);
152 return power;
153}
154
155void ShooterMotor::CheckCalibrations(
Brian Silvermanbe5ded62015-05-14 00:23:49 -0400156 const control_loops::ShooterQueue::Position *position) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700157 CHECK_NOTNULL(position);
158 const frc971::constants::Values &values = constants::GetValues();
159
160 // TODO(austin): Validate that this is the right edge.
161 // If we see a posedge on any of the hall effects,
162 if (position->pusher_proximal.posedge_count != last_proximal_posedge_count_ &&
163 !last_proximal_current_) {
164 proximal_posedge_validation_cycles_left_ = 2;
165 }
166 if (proximal_posedge_validation_cycles_left_ > 0) {
167 if (position->pusher_proximal.current) {
168 --proximal_posedge_validation_cycles_left_;
169 if (proximal_posedge_validation_cycles_left_ == 0) {
170 shooter_.SetCalibration(
171 position->pusher_proximal.posedge_value,
172 values.shooter.pusher_proximal.upper_angle);
173
174 LOG(DEBUG, "Setting calibration using proximal sensor\n");
175 zeroed_ = true;
176 }
177 } else {
178 proximal_posedge_validation_cycles_left_ = 0;
179 }
180 }
181
182 if (position->pusher_distal.posedge_count != last_distal_posedge_count_ &&
183 !last_distal_current_) {
184 distal_posedge_validation_cycles_left_ = 2;
185 }
186 if (distal_posedge_validation_cycles_left_ > 0) {
187 if (position->pusher_distal.current) {
188 --distal_posedge_validation_cycles_left_;
189 if (distal_posedge_validation_cycles_left_ == 0) {
190 shooter_.SetCalibration(
191 position->pusher_distal.posedge_value,
192 values.shooter.pusher_distal.upper_angle);
193
194 LOG(DEBUG, "Setting calibration using distal sensor\n");
195 zeroed_ = true;
196 }
197 } else {
198 distal_posedge_validation_cycles_left_ = 0;
199 }
200 }
201}
202
203// Positive is out, and positive power is out.
204void ShooterMotor::RunIteration(
Brian Silvermanbe5ded62015-05-14 00:23:49 -0400205 const control_loops::ShooterQueue::Goal *goal,
206 const control_loops::ShooterQueue::Position *position,
207 control_loops::ShooterQueue::Output *output,
208 control_loops::ShooterQueue::Status *status) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700209 constexpr double dt = 0.01;
210
211 if (goal && ::std::isnan(goal->shot_power)) {
212 state_ = STATE_ESTOP;
213 LOG(ERROR, "Estopping because got a shot power of NAN.\n");
214 }
215
216 // we must always have these or we have issues.
217 if (status == NULL) {
218 if (output) output->voltage = 0;
219 LOG(ERROR, "Thought I would just check for null and die.\n");
220 return;
221 }
222 status->ready = false;
223
224 if (WasReset()) {
225 state_ = STATE_INITIALIZE;
226 last_distal_current_ = position->pusher_distal.current;
227 last_proximal_current_ = position->pusher_proximal.current;
228 }
229 if (position) {
230 shooter_.CorrectPosition(position->position);
231 }
232
233 // Disable the motors now so that all early returns will return with the
234 // motors disabled.
235 if (output) output->voltage = 0;
236
237 const frc971::constants::Values &values = constants::GetValues();
238
239 // Don't even let the control loops run.
240 bool shooter_loop_disable = false;
241
242 const bool disabled =
243 !::aos::joystick_state.get() || !::aos::joystick_state->enabled;
244
245 // If true, move the goal if we saturate.
246 bool cap_goal = false;
247
248 // TODO(austin): Move the offset if we see or don't see a hall effect when we
249 // expect to see one.
250 // Probably not needed yet.
251
252 if (position) {
253 int last_controller_index = shooter_.controller_index();
254 if (position->plunger && position->latch) {
255 // Use the controller without the spring if the latch is set and the
256 // plunger is back
257 shooter_.set_controller_index(1);
258 } else {
259 // Otherwise use the controller with the spring.
260 shooter_.set_controller_index(0);
261 }
262 if (shooter_.controller_index() != last_controller_index) {
263 shooter_.RecalculatePowerGoal();
264 }
265 }
266
267 switch (state_) {
268 case STATE_INITIALIZE:
269 if (position) {
270 // Reinitialize the internal filter state.
271 shooter_.InitializeState(position->position);
272
273 // Start off with the assumption that we are at the value
274 // futhest back given our sensors.
275 if (position->pusher_distal.current) {
276 shooter_.SetCalibration(position->position,
277 values.shooter.pusher_distal.lower_angle);
278 } else if (position->pusher_proximal.current) {
279 shooter_.SetCalibration(position->position,
280 values.shooter.pusher_proximal.upper_angle);
281 } else {
282 shooter_.SetCalibration(position->position,
283 values.shooter.upper_limit);
284 }
285
286 // Go to the current position.
287 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
288 // If the plunger is all the way back, we want to be latched.
289 latch_piston_ = position->plunger;
290 brake_piston_ = false;
291 if (position->latch == latch_piston_) {
292 state_ = STATE_REQUEST_LOAD;
293 } else {
294 shooter_loop_disable = true;
295 LOG(DEBUG,
296 "Not moving on until the latch has moved to avoid a crash\n");
297 }
298 } else {
299 // If we can't start yet because we don't know where we are, set the
300 // latch and brake to their defaults.
301 latch_piston_ = true;
302 brake_piston_ = true;
303 }
304 break;
305 case STATE_REQUEST_LOAD:
306 if (position) {
307 zeroed_ = false;
308 if (position->pusher_distal.current ||
309 position->pusher_proximal.current) {
310 // We started on the sensor, back up until we are found.
311 // If the plunger is all the way back and not latched, it won't be
312 // there for long.
313 state_ = STATE_LOAD_BACKTRACK;
314
315 // The plunger is already back and latched. Don't release it.
316 if (position->plunger && position->latch) {
317 latch_piston_ = true;
318 } else {
319 latch_piston_ = false;
320 }
321 } else if (position->plunger && position->latch) {
322 // The plunger is back and we are latched. We most likely got here
323 // from Initialize, in which case we want to 'load' again anyways to
324 // zero.
325 Load();
326 latch_piston_ = true;
327 } else {
328 // Off the sensor, start loading.
329 Load();
330 latch_piston_ = false;
331 }
332 }
333
334 // Hold our current position.
335 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
336 brake_piston_ = false;
337 break;
338 case STATE_LOAD_BACKTRACK:
339 // If we are here, then that means we started past the edge where we want
340 // to zero. Move backwards until we don't see the sensor anymore.
341 // The plunger is contacting the pusher (or will be shortly).
342
343 if (!disabled) {
344 shooter_.SetGoalPosition(
345 shooter_.goal_position() + values.shooter.zeroing_speed * dt,
346 values.shooter.zeroing_speed);
347 }
348 cap_goal = true;
349 shooter_.set_max_voltage(4.0);
350
351 if (position) {
352 if (!position->pusher_distal.current &&
353 !position->pusher_proximal.current) {
354 Load();
355 }
356 latch_piston_ = position->plunger;
357 }
358
359 brake_piston_ = false;
360 break;
361 case STATE_LOAD:
362 // If we are disabled right now, reset the timer.
363 if (disabled) {
364 Load();
365 // Latch defaults to true when disabled. Leave it latched until we have
366 // useful sensor data.
367 latch_piston_ = true;
368 }
369 if (output == nullptr) {
370 load_timeout_ += ::aos::controls::kLoopFrequency;
371 }
372 // Go to 0, which should be the latch position, or trigger a hall effect
373 // on the way. If we don't see edges where we are supposed to, the
374 // offset will be updated by code above.
375 shooter_.SetGoalPosition(0.0, 0.0);
376
377 if (position) {
378 CheckCalibrations(position);
379
380 // Latch if the plunger is far enough back to trigger the hall effect.
381 // This happens when the distal sensor is triggered.
382 latch_piston_ = position->pusher_distal.current || position->plunger;
383
384 // Check if we are latched and back. Make sure the plunger is all the
385 // way back as well.
386 if (position->plunger && position->latch &&
387 position->pusher_distal.current) {
388 if (!zeroed_) {
389 state_ = STATE_REQUEST_LOAD;
390 } else {
391 state_ = STATE_PREPARE_SHOT;
392 }
393 } else if (position->plunger &&
394 ::std::abs(shooter_.absolute_position() -
395 shooter_.goal_position()) < 0.001) {
396 // We are at the goal, but not latched.
397 state_ = STATE_LOADING_PROBLEM;
398 loading_problem_end_time_ = Time::Now() + kLoadProblemEndTimeout;
399 }
400 }
401 if (load_timeout_ < Time::Now()) {
402 if (position) {
403 if (!position->pusher_distal.current ||
404 !position->pusher_proximal.current) {
405 state_ = STATE_ESTOP;
406 LOG(ERROR, "Estopping because took too long to load.\n");
407 }
408 }
409 }
410 brake_piston_ = false;
411 break;
412 case STATE_LOADING_PROBLEM:
413 if (disabled) {
414 state_ = STATE_REQUEST_LOAD;
415 break;
416 }
417 // We got to the goal, but the latch hasn't registered as down. It might
418 // be stuck, or on it's way but not there yet.
419 if (Time::Now() > loading_problem_end_time_) {
420 // Timeout by unloading.
421 Unload();
422 } else if (position && position->plunger && position->latch) {
423 // If both trigger, we are latched.
424 state_ = STATE_PREPARE_SHOT;
425 }
426 // Move a bit further back to help it trigger.
427 // If the latch is slow due to the air flowing through the tubes or
428 // inertia, but is otherwise free, this won't have much time to do
429 // anything and is safe. Otherwise this gives us a bit more room to free
430 // up the latch.
431 shooter_.SetGoalPosition(values.shooter.lower_limit, 0.0);
432 if (position) {
433 LOG(DEBUG, "Waiting on latch: plunger %d, latch: %d\n",
434 position->plunger, position->latch);
435 }
436
437 latch_piston_ = true;
438 brake_piston_ = false;
439 break;
440 case STATE_PREPARE_SHOT:
441 // Move the shooter to the shot power set point and then lock the brake.
442 // TODO(austin): Timeout. Low priority.
443
444 if (goal) {
445 shooter_.SetGoalPosition(PowerToPosition(goal->shot_power), 0.0);
446 }
447
448 LOG(DEBUG, "PDIFF: absolute_position: %.2f, pow: %.2f\n",
449 shooter_.absolute_position(),
450 goal ? PowerToPosition(goal->shot_power)
451 : ::std::numeric_limits<double>::quiet_NaN());
452 if (goal &&
453 ::std::abs(shooter_.absolute_position() -
454 PowerToPosition(goal->shot_power)) < 0.001 &&
455 ::std::abs(shooter_.absolute_velocity()) < 0.005) {
456 // We are there, set the brake and move on.
457 latch_piston_ = true;
458 brake_piston_ = true;
459 shooter_brake_set_time_ = Time::Now() + kShooterBrakeSetTime;
460 state_ = STATE_READY;
461 } else {
462 latch_piston_ = true;
463 brake_piston_ = false;
464 }
465 if (goal && goal->unload_requested) {
466 Unload();
467 }
468 break;
469 case STATE_READY:
470 LOG(DEBUG, "In ready\n");
471 // Wait until the brake is set, and a shot is requested or the shot power
472 // is changed.
473 if (Time::Now() > shooter_brake_set_time_) {
474 status->ready = true;
475 // We have waited long enough for the brake to set, turn the shooter
476 // control loop off.
477 shooter_loop_disable = true;
478 LOG(DEBUG, "Brake is now set\n");
479 if (goal && goal->shot_requested && !disabled) {
480 LOG(DEBUG, "Shooting now\n");
481 shooter_loop_disable = true;
482 shot_end_time_ = Time::Now() + kShotEndTimeout;
483 firing_starting_position_ = shooter_.absolute_position();
484 state_ = STATE_FIRE;
485 }
486 }
487 if (state_ == STATE_READY && goal &&
488 ::std::abs(shooter_.absolute_position() -
489 PowerToPosition(goal->shot_power)) > 0.002) {
490 // TODO(austin): Add a state to release the brake.
491
492 // TODO(austin): Do we want to set the brake here or after shooting?
493 // Depends on air usage.
494 status->ready = false;
495 LOG(DEBUG, "Preparing shot again.\n");
496 state_ = STATE_PREPARE_SHOT;
497 }
498
499 if (goal) {
500 shooter_.SetGoalPosition(PowerToPosition(goal->shot_power), 0.0);
501 }
502
503 latch_piston_ = true;
504 brake_piston_ = true;
505
506 if (goal && goal->unload_requested) {
507 Unload();
508 }
509 break;
510
511 case STATE_FIRE:
512 if (disabled) {
513 if (position) {
514 if (position->plunger) {
515 // If disabled and the plunger is still back there, reset the
516 // timeout.
517 shot_end_time_ = Time::Now() + kShotEndTimeout;
518 }
519 }
520 }
521 shooter_loop_disable = true;
522 // Count the number of contiguous cycles during which we haven't moved.
523 if (::std::abs(last_position_.position - shooter_.absolute_position()) <
524 0.0005) {
525 ++cycles_not_moved_;
526 } else {
527 cycles_not_moved_ = 0;
528 }
529
530 // If we have moved any amount since the start and the shooter has now
531 // been still for a couple cycles, the shot finished.
532 // Also move on if it times out.
Austin Schuh829e6ad2015-11-08 14:10:37 -0800533 if (((::std::abs(firing_starting_position_ -
534 shooter_.absolute_position()) > 0.0005 &&
535 cycles_not_moved_ > 3) ||
536 Time::Now() > shot_end_time_) &&
537 ::aos::robot_state->voltage_battery > 10.5) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700538 state_ = STATE_REQUEST_LOAD;
539 ++shot_count_;
540 }
541 latch_piston_ = false;
542 brake_piston_ = true;
543 break;
544 case STATE_UNLOAD:
545 // Reset the timeouts.
546 if (disabled) Unload();
547
548 // If it is latched and the plunger is back, move the pusher back to catch
549 // the plunger.
550 bool all_back;
551 if (position) {
552 all_back = position->plunger && position->latch;
553 } else {
554 all_back = last_position_.plunger && last_position_.latch;
555 }
556
557 if (all_back) {
558 // Pull back to 0, 0.
559 shooter_.SetGoalPosition(0.0, 0.0);
560 if (shooter_.absolute_position() < 0.005) {
561 // When we are close enough, 'fire'.
562 latch_piston_ = false;
563 } else {
564 latch_piston_ = true;
565
566 if (position) {
567 CheckCalibrations(position);
568 }
569 }
570 } else {
571 // The plunger isn't all the way back, or it is and it is unlatched, so
572 // we can now unload.
573 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
574 latch_piston_ = false;
575 state_ = STATE_UNLOAD_MOVE;
576 unload_timeout_ = Time::Now() + kUnloadTimeout;
577 }
578
579 if (Time::Now() > unload_timeout_) {
580 // We have been stuck trying to unload for way too long, give up and
581 // turn everything off.
582 state_ = STATE_ESTOP;
583 LOG(ERROR, "Estopping because took too long to unload.\n");
584 }
585
586 brake_piston_ = false;
587 break;
588 case STATE_UNLOAD_MOVE: {
589 if (disabled) {
590 unload_timeout_ = Time::Now() + kUnloadTimeout;
591 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
592 }
593 cap_goal = true;
594 shooter_.set_max_voltage(6.0);
595
596 // Slowly move back until we hit the upper limit.
597 // If we were at the limit last cycle, we are done unloading.
598 // This is because if we saturate, we might hit the limit before we are
599 // actually there.
600 if (shooter_.goal_position() >= values.shooter.upper_limit) {
601 shooter_.SetGoalPosition(values.shooter.upper_limit, 0.0);
602 // We don't want the loop fighting the spring when we are unloaded.
603 // Turn it off.
604 shooter_loop_disable = true;
605 state_ = STATE_READY_UNLOAD;
606 } else {
607 shooter_.SetGoalPosition(
608 ::std::min(
609 values.shooter.upper_limit,
610 shooter_.goal_position() + values.shooter.unload_speed * dt),
611 values.shooter.unload_speed);
612 }
613
614 latch_piston_ = false;
615 brake_piston_ = false;
616 } break;
617 case STATE_READY_UNLOAD:
618 if (goal && goal->load_requested) {
619 state_ = STATE_REQUEST_LOAD;
620 }
621 // If we are ready to load again,
622 shooter_loop_disable = true;
623
624 latch_piston_ = false;
625 brake_piston_ = false;
626 break;
627
628 case STATE_ESTOP:
629 LOG(WARNING, "estopped\n");
630 // Totally lost, go to a safe state.
631 shooter_loop_disable = true;
632 latch_piston_ = true;
633 brake_piston_ = true;
634 break;
635 }
636
637 if (!shooter_loop_disable) {
638 LOG_STRUCT(DEBUG, "running the loop",
639 ShooterStatusToLog(shooter_.goal_position(),
640 shooter_.absolute_position()));
641 if (!cap_goal) {
642 shooter_.set_max_voltage(12.0);
643 }
644 shooter_.Update(output == NULL);
645 if (cap_goal) {
646 shooter_.CapGoal();
647 }
648 // We don't really want to output anything if we went through everything
649 // assuming the motors weren't working.
650 if (output) output->voltage = shooter_.voltage();
651 } else {
652 shooter_.Update(true);
653 shooter_.ZeroPower();
654 if (output) output->voltage = 0.0;
655 }
656
657 status->hard_stop_power = PositionToPower(shooter_.absolute_position());
658
659 if (output) {
660 output->latch_piston = latch_piston_;
661 output->brake_piston = brake_piston_;
662 }
663
664 if (position) {
665 LOG_STRUCT(DEBUG, "internal state",
666 ShooterStateToLog(
667 shooter_.absolute_position(), shooter_.absolute_velocity(),
668 state_, position->latch, position->pusher_proximal.current,
669 position->pusher_distal.current, position->plunger,
670 brake_piston_, latch_piston_));
671
672 last_position_ = *position;
673
674 last_distal_posedge_count_ = position->pusher_distal.posedge_count;
675 last_proximal_posedge_count_ = position->pusher_proximal.posedge_count;
676 last_distal_current_ = position->pusher_distal.current;
677 last_proximal_current_ = position->pusher_proximal.current;
678 }
679
680 status->shots = shot_count_;
681}
682
683void ShooterMotor::ZeroOutputs() {
684 queue_group()->output.MakeWithBuilder()
685 .voltage(0)
686 .latch_piston(latch_piston_)
687 .brake_piston(brake_piston_)
688 .Send();
689}
690
691} // namespace control_loops
692} // namespace frc971