blob: 2a3e0d0e0f6869185685e4a59c965c087c64520e [file] [log] [blame]
Brian Silverman17f503e2015-08-02 18:17:18 -07001#include "y2014/control_loops/shooter/shooter.h"
2
Brian Silverman17f503e2015-08-02 18:17:18 -07003#include <algorithm>
Austin Schuh214e9c12016-11-25 17:26:20 -08004#include <chrono>
Tyler Chatowbf0609c2021-07-31 16:13:27 -07005#include <cstdio>
James Kuszmaul61750662021-06-21 21:32:33 -07006#include <limits>
Brian Silverman17f503e2015-08-02 18:17:18 -07007
John Park33858a32018-09-28 23:05:48 -07008#include "aos/logging/logging.h"
Brian Silverman17f503e2015-08-02 18:17:18 -07009#include "y2014/constants.h"
10#include "y2014/control_loops/shooter/shooter_motor_plant.h"
11
Austin Schuh24957102015-11-28 16:04:40 -080012namespace y2014 {
Brian Silverman17f503e2015-08-02 18:17:18 -070013namespace control_loops {
Alex Perrycb7da4b2019-08-28 19:35:56 -070014namespace shooter {
Brian Silverman17f503e2015-08-02 18:17:18 -070015
Austin Schuh214e9c12016-11-25 17:26:20 -080016namespace chrono = ::std::chrono;
17using ::aos::monotonic_clock;
18
Austin Schuh0e997732015-11-08 15:14:53 -080019using ::y2014::control_loops::shooter::kDt;
James Kuszmaul61750662021-06-21 21:32:33 -070020using ::y2014::control_loops::shooter::kMaxExtension;
21using ::y2014::control_loops::shooter::kSpringConstant;
Austin Schuh9d4aca82015-11-08 14:41:31 -080022using ::y2014::control_loops::shooter::MakeShooterLoop;
Brian Silverman17f503e2015-08-02 18:17:18 -070023
24void ZeroedStateFeedbackLoop::CapU() {
25 const double old_voltage = voltage_;
26 voltage_ += U(0, 0);
27
28 uncapped_voltage_ = voltage_;
29
30 // Make sure that reality and the observer can't get too far off. There is a
31 // delay by one cycle between the applied voltage and X_hat(2, 0), so compare
32 // against last cycle's voltage.
33 if (X_hat(2, 0) > last_voltage_ + 4.0) {
34 voltage_ -= X_hat(2, 0) - (last_voltage_ + 4.0);
Austin Schuhf257f3c2019-10-27 21:00:43 -070035 AOS_LOG(DEBUG, "Capping due to runaway\n");
Brian Silverman17f503e2015-08-02 18:17:18 -070036 } else if (X_hat(2, 0) < last_voltage_ - 4.0) {
37 voltage_ += X_hat(2, 0) - (last_voltage_ - 4.0);
Austin Schuhf257f3c2019-10-27 21:00:43 -070038 AOS_LOG(DEBUG, "Capping due to runaway\n");
Brian Silverman17f503e2015-08-02 18:17:18 -070039 }
40
41 voltage_ = std::min(max_voltage_, voltage_);
42 voltage_ = std::max(-max_voltage_, voltage_);
43 mutable_U(0, 0) = voltage_ - old_voltage;
44
Brian Silverman17f503e2015-08-02 18:17:18 -070045 last_voltage_ = voltage_;
46 capped_goal_ = false;
47}
48
49void ZeroedStateFeedbackLoop::CapGoal() {
50 if (uncapped_voltage() > max_voltage_) {
51 double dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080052 if (index() == 0) {
Brian Silverman17f503e2015-08-02 18:17:18 -070053 dx = (uncapped_voltage() - max_voltage_) /
Austin Schuh32501832017-02-25 18:32:56 -080054 (controller().K(0, 0) -
55 plant().A(1, 0) * controller().K(0, 2) / plant().A(1, 2));
Brian Silverman17f503e2015-08-02 18:17:18 -070056 mutable_R(0, 0) -= dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080057 mutable_R(2, 0) -= -plant().A(1, 0) / plant().A(1, 2) * dx;
Brian Silverman17f503e2015-08-02 18:17:18 -070058 } else {
Austin Schuh32501832017-02-25 18:32:56 -080059 dx = (uncapped_voltage() - max_voltage_) / controller().K(0, 0);
Brian Silverman17f503e2015-08-02 18:17:18 -070060 mutable_R(0, 0) -= dx;
61 }
62 capped_goal_ = true;
Brian Silverman17f503e2015-08-02 18:17:18 -070063 } else if (uncapped_voltage() < -max_voltage_) {
64 double dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080065 if (index() == 0) {
Brian Silverman17f503e2015-08-02 18:17:18 -070066 dx = (uncapped_voltage() + max_voltage_) /
Austin Schuh32501832017-02-25 18:32:56 -080067 (controller().K(0, 0) -
68 plant().A(1, 0) * controller().K(0, 2) / plant().A(1, 2));
Brian Silverman17f503e2015-08-02 18:17:18 -070069 mutable_R(0, 0) -= dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080070 mutable_R(2, 0) -= -plant().A(1, 0) / plant().A(1, 2) * dx;
Brian Silverman17f503e2015-08-02 18:17:18 -070071 } else {
Austin Schuh32501832017-02-25 18:32:56 -080072 dx = (uncapped_voltage() + max_voltage_) / controller().K(0, 0);
Brian Silverman17f503e2015-08-02 18:17:18 -070073 mutable_R(0, 0) -= dx;
74 }
75 capped_goal_ = true;
Brian Silverman17f503e2015-08-02 18:17:18 -070076 } else {
77 capped_goal_ = false;
78 }
79}
80
81void ZeroedStateFeedbackLoop::RecalculatePowerGoal() {
Austin Schuhc5fceb82017-02-25 16:24:12 -080082 if (index() == 0) {
83 mutable_R(2, 0) = (-plant().A(1, 0) / plant().A(1, 2) * R(0, 0) -
84 plant().A(1, 1) / plant().A(1, 2) * R(1, 0));
Brian Silverman17f503e2015-08-02 18:17:18 -070085 } else {
Austin Schuhc5fceb82017-02-25 16:24:12 -080086 mutable_R(2, 0) = -plant().A(1, 1) / plant().A(1, 2) * R(1, 0);
Brian Silverman17f503e2015-08-02 18:17:18 -070087 }
88}
89
90void ZeroedStateFeedbackLoop::SetCalibration(double encoder_val,
91 double known_position) {
Brian Silverman17f503e2015-08-02 18:17:18 -070092 double previous_offset = offset_;
93 offset_ = known_position - encoder_val;
94 double doffset = offset_ - previous_offset;
95 mutable_X_hat(0, 0) += doffset;
Brian Silverman17f503e2015-08-02 18:17:18 -070096 // Offset the goal so we don't move.
97 mutable_R(0, 0) += doffset;
Austin Schuhc5fceb82017-02-25 16:24:12 -080098 if (index() == 0) {
99 mutable_R(2, 0) += -plant().A(1, 0) / plant().A(1, 2) * (doffset);
Brian Silverman17f503e2015-08-02 18:17:18 -0700100 }
Brian Silverman17f503e2015-08-02 18:17:18 -0700101}
102
Austin Schuh55a13dc2019-01-27 22:39:03 -0800103ShooterMotor::ShooterMotor(::aos::EventLoop *event_loop,
104 const ::std::string &name)
James Kuszmaul61750662021-06-21 21:32:33 -0700105 : frc971::controls::ControlLoop<Goal, Position, Status, Output>(event_loop,
106 name),
Brian Silverman17f503e2015-08-02 18:17:18 -0700107 shooter_(MakeShooterLoop()),
108 state_(STATE_INITIALIZE),
Brian Silverman17f503e2015-08-02 18:17:18 -0700109 cycles_not_moved_(0),
110 shot_count_(0),
111 zeroed_(false),
112 distal_posedge_validation_cycles_left_(0),
113 proximal_posedge_validation_cycles_left_(0),
114 last_distal_current_(true),
115 last_proximal_current_(true) {}
116
117double ShooterMotor::PowerToPosition(double power) {
Austin Schuh24957102015-11-28 16:04:40 -0800118 const constants::Values &values = constants::GetValues();
Brian Silverman17f503e2015-08-02 18:17:18 -0700119 double maxpower = 0.5 * kSpringConstant *
120 (kMaxExtension * kMaxExtension -
121 (kMaxExtension - values.shooter.upper_limit) *
122 (kMaxExtension - values.shooter.upper_limit));
123 if (power < 0) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700124 power = 0;
125 } else if (power > maxpower) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700126 power = maxpower;
127 }
128
129 double mp = kMaxExtension * kMaxExtension - (power + power) / kSpringConstant;
130 double new_pos = 0.10;
131 if (mp < 0) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700132 AOS_LOG(ERROR,
133 "Power calculation has negative number before square root (%f).\n",
134 mp);
Brian Silverman17f503e2015-08-02 18:17:18 -0700135 } else {
136 new_pos = kMaxExtension - ::std::sqrt(mp);
137 }
138
139 new_pos = ::std::min(::std::max(new_pos, values.shooter.lower_limit),
James Kuszmaul61750662021-06-21 21:32:33 -0700140 values.shooter.upper_limit);
Brian Silverman17f503e2015-08-02 18:17:18 -0700141 return new_pos;
142}
143
144double ShooterMotor::PositionToPower(double position) {
145 double power = kSpringConstant * position * (kMaxExtension - position / 2.0);
146 return power;
147}
148
Alex Perrycb7da4b2019-08-28 19:35:56 -0700149void ShooterMotor::CheckCalibrations(const Position *position) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700150 AOS_CHECK_NOTNULL(position);
Austin Schuh24957102015-11-28 16:04:40 -0800151 const constants::Values &values = constants::GetValues();
Brian Silverman17f503e2015-08-02 18:17:18 -0700152
153 // TODO(austin): Validate that this is the right edge.
154 // If we see a posedge on any of the hall effects,
Alex Perrycb7da4b2019-08-28 19:35:56 -0700155 if (position->pusher_proximal()->posedge_count() !=
156 last_proximal_posedge_count_ &&
Brian Silverman17f503e2015-08-02 18:17:18 -0700157 !last_proximal_current_) {
158 proximal_posedge_validation_cycles_left_ = 2;
159 }
160 if (proximal_posedge_validation_cycles_left_ > 0) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700161 if (position->pusher_proximal()->current()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700162 --proximal_posedge_validation_cycles_left_;
163 if (proximal_posedge_validation_cycles_left_ == 0) {
James Kuszmaul61750662021-06-21 21:32:33 -0700164 shooter_.SetCalibration(position->pusher_proximal()->posedge_value(),
165 values.shooter.pusher_proximal.upper_angle);
Brian Silverman17f503e2015-08-02 18:17:18 -0700166
Austin Schuhf257f3c2019-10-27 21:00:43 -0700167 AOS_LOG(DEBUG, "Setting calibration using proximal sensor\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700168 zeroed_ = true;
169 }
170 } else {
171 proximal_posedge_validation_cycles_left_ = 0;
172 }
173 }
174
Alex Perrycb7da4b2019-08-28 19:35:56 -0700175 if (position->pusher_distal()->posedge_count() !=
176 last_distal_posedge_count_ &&
Brian Silverman17f503e2015-08-02 18:17:18 -0700177 !last_distal_current_) {
178 distal_posedge_validation_cycles_left_ = 2;
179 }
180 if (distal_posedge_validation_cycles_left_ > 0) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700181 if (position->pusher_distal()->current()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700182 --distal_posedge_validation_cycles_left_;
183 if (distal_posedge_validation_cycles_left_ == 0) {
James Kuszmaul61750662021-06-21 21:32:33 -0700184 shooter_.SetCalibration(position->pusher_distal()->posedge_value(),
185 values.shooter.pusher_distal.upper_angle);
Brian Silverman17f503e2015-08-02 18:17:18 -0700186
Austin Schuhf257f3c2019-10-27 21:00:43 -0700187 AOS_LOG(DEBUG, "Setting calibration using distal sensor\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700188 zeroed_ = true;
189 }
190 } else {
191 distal_posedge_validation_cycles_left_ = 0;
192 }
193 }
194}
195
196// Positive is out, and positive power is out.
James Kuszmaul61750662021-06-21 21:32:33 -0700197void ShooterMotor::RunIteration(const Goal *goal, const Position *position,
198 aos::Sender<Output>::Builder *output,
199 aos::Sender<Status>::Builder *status) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700200 OutputT output_struct;
James Kuszmaul61750662021-06-21 21:32:33 -0700201 const monotonic_clock::time_point monotonic_now =
202 event_loop()->monotonic_now();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700203 if (goal && ::std::isnan(goal->shot_power())) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700204 state_ = STATE_ESTOP;
205 AOS_LOG(ERROR, "Estopping because got a shot power of NAN.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700206 }
207
208 // we must always have these or we have issues.
209 if (status == NULL) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700210 if (output) output_struct.voltage = 0;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700211 AOS_LOG(ERROR, "Thought I would just check for null and die.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700212 return;
213 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700214 bool status_ready = false;
Brian Silverman17f503e2015-08-02 18:17:18 -0700215
216 if (WasReset()) {
217 state_ = STATE_INITIALIZE;
Alex Perrycb7da4b2019-08-28 19:35:56 -0700218 last_distal_current_ = position->pusher_distal()->current();
219 last_proximal_current_ = position->pusher_proximal()->current();
Brian Silverman17f503e2015-08-02 18:17:18 -0700220 }
221 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700222 shooter_.CorrectPosition(position->position());
Brian Silverman17f503e2015-08-02 18:17:18 -0700223 }
224
225 // Disable the motors now so that all early returns will return with the
226 // motors disabled.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700227 if (output) output_struct.voltage = 0;
Brian Silverman17f503e2015-08-02 18:17:18 -0700228
Austin Schuh24957102015-11-28 16:04:40 -0800229 const constants::Values &values = constants::GetValues();
Brian Silverman17f503e2015-08-02 18:17:18 -0700230
231 // Don't even let the control loops run.
232 bool shooter_loop_disable = false;
233
Alex Perrycb7da4b2019-08-28 19:35:56 -0700234 const bool disabled = !has_joystick_state() || !joystick_state().enabled();
Brian Silverman17f503e2015-08-02 18:17:18 -0700235
236 // If true, move the goal if we saturate.
237 bool cap_goal = false;
238
239 // TODO(austin): Move the offset if we see or don't see a hall effect when we
240 // expect to see one.
241 // Probably not needed yet.
242
243 if (position) {
Austin Schuhc5fceb82017-02-25 16:24:12 -0800244 int last_index = shooter_.index();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700245 if (position->plunger() && position->latch()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700246 // Use the controller without the spring if the latch is set and the
247 // plunger is back
Austin Schuhc5fceb82017-02-25 16:24:12 -0800248 shooter_.set_index(1);
Brian Silverman17f503e2015-08-02 18:17:18 -0700249 } else {
250 // Otherwise use the controller with the spring.
Austin Schuhc5fceb82017-02-25 16:24:12 -0800251 shooter_.set_index(0);
Brian Silverman17f503e2015-08-02 18:17:18 -0700252 }
Austin Schuhc5fceb82017-02-25 16:24:12 -0800253 if (shooter_.index() != last_index) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700254 shooter_.RecalculatePowerGoal();
255 }
256 }
257
258 switch (state_) {
259 case STATE_INITIALIZE:
260 if (position) {
261 // Reinitialize the internal filter state.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700262 shooter_.InitializeState(position->position());
Brian Silverman17f503e2015-08-02 18:17:18 -0700263
264 // Start off with the assumption that we are at the value
265 // futhest back given our sensors.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700266 if (position->pusher_distal()->current()) {
267 shooter_.SetCalibration(position->position(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700268 values.shooter.pusher_distal.lower_angle);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700269 } else if (position->pusher_proximal()->current()) {
270 shooter_.SetCalibration(position->position(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700271 values.shooter.pusher_proximal.upper_angle);
272 } else {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700273 shooter_.SetCalibration(position->position(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700274 values.shooter.upper_limit);
275 }
276
277 // Go to the current position.
278 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
279 // If the plunger is all the way back, we want to be latched.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700280 latch_piston_ = position->plunger();
Brian Silverman17f503e2015-08-02 18:17:18 -0700281 brake_piston_ = false;
Alex Perrycb7da4b2019-08-28 19:35:56 -0700282 if (position->latch() == latch_piston_) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700283 state_ = STATE_REQUEST_LOAD;
284 } else {
285 shooter_loop_disable = true;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700286 AOS_LOG(DEBUG,
287 "Not moving on until the latch has moved to avoid a crash\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700288 }
289 } else {
290 // If we can't start yet because we don't know where we are, set the
291 // latch and brake to their defaults.
292 latch_piston_ = true;
293 brake_piston_ = true;
294 }
295 break;
296 case STATE_REQUEST_LOAD:
297 if (position) {
298 zeroed_ = false;
Alex Perrycb7da4b2019-08-28 19:35:56 -0700299 if (position->pusher_distal()->current() ||
300 position->pusher_proximal()->current()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700301 // We started on the sensor, back up until we are found.
302 // If the plunger is all the way back and not latched, it won't be
303 // there for long.
304 state_ = STATE_LOAD_BACKTRACK;
305
306 // The plunger is already back and latched. Don't release it.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700307 if (position->plunger() && position->latch()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700308 latch_piston_ = true;
309 } else {
310 latch_piston_ = false;
311 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700312 } else if (position->plunger() && position->latch()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700313 // The plunger is back and we are latched. We most likely got here
314 // from Initialize, in which case we want to 'load' again anyways to
315 // zero.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700316 Load(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700317 latch_piston_ = true;
318 } else {
319 // Off the sensor, start loading.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700320 Load(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700321 latch_piston_ = false;
322 }
323 }
324
325 // Hold our current position.
326 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
327 brake_piston_ = false;
328 break;
329 case STATE_LOAD_BACKTRACK:
330 // If we are here, then that means we started past the edge where we want
331 // to zero. Move backwards until we don't see the sensor anymore.
332 // The plunger is contacting the pusher (or will be shortly).
333
334 if (!disabled) {
335 shooter_.SetGoalPosition(
Austin Schuh0e997732015-11-08 15:14:53 -0800336 shooter_.goal_position() + values.shooter.zeroing_speed * kDt,
Brian Silverman17f503e2015-08-02 18:17:18 -0700337 values.shooter.zeroing_speed);
338 }
339 cap_goal = true;
340 shooter_.set_max_voltage(4.0);
341
342 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700343 if (!position->pusher_distal()->current() &&
344 !position->pusher_proximal()->current()) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700345 Load(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700346 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700347 latch_piston_ = position->plunger();
Brian Silverman17f503e2015-08-02 18:17:18 -0700348 }
349
350 brake_piston_ = false;
351 break;
352 case STATE_LOAD:
353 // If we are disabled right now, reset the timer.
354 if (disabled) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700355 Load(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700356 // Latch defaults to true when disabled. Leave it latched until we have
357 // useful sensor data.
358 latch_piston_ = true;
359 }
360 if (output == nullptr) {
James Kuszmaul61750662021-06-21 21:32:33 -0700361 load_timeout_ += ::frc971::controls::kLoopFrequency;
Brian Silverman17f503e2015-08-02 18:17:18 -0700362 }
363 // Go to 0, which should be the latch position, or trigger a hall effect
364 // on the way. If we don't see edges where we are supposed to, the
365 // offset will be updated by code above.
366 shooter_.SetGoalPosition(0.0, 0.0);
367
368 if (position) {
369 CheckCalibrations(position);
370
371 // Latch if the plunger is far enough back to trigger the hall effect.
372 // This happens when the distal sensor is triggered.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700373 latch_piston_ =
374 position->pusher_distal()->current() || position->plunger();
Brian Silverman17f503e2015-08-02 18:17:18 -0700375
376 // Check if we are latched and back. Make sure the plunger is all the
377 // way back as well.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700378 if (position->plunger() && position->latch() &&
379 position->pusher_distal()->current()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700380 if (!zeroed_) {
381 state_ = STATE_REQUEST_LOAD;
382 } else {
383 state_ = STATE_PREPARE_SHOT;
384 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700385 } else if (position->plunger() &&
Brian Silverman17f503e2015-08-02 18:17:18 -0700386 ::std::abs(shooter_.absolute_position() -
387 shooter_.goal_position()) < 0.001) {
388 // We are at the goal, but not latched.
389 state_ = STATE_LOADING_PROBLEM;
James Kuszmaul61750662021-06-21 21:32:33 -0700390 loading_problem_end_time_ = monotonic_now + kLoadProblemEndTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700391 }
392 }
Austin Schuh9fe68f72019-08-10 19:32:03 -0700393 if (load_timeout_ < monotonic_now) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700394 if (position) {
Austin Schuhadf2cde2015-11-08 20:35:16 -0800395 // If none of the sensors is triggered, estop.
396 // Otherwise, trigger anyways if it has been 0.5 seconds more.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700397 if (!(position->pusher_distal()->current() ||
398 position->pusher_proximal()->current()) ||
399 (load_timeout_ + chrono::milliseconds(500) < monotonic_now)) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700400 state_ = STATE_ESTOP;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700401 AOS_LOG(ERROR, "Estopping because took too long to load.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700402 }
403 }
404 }
405 brake_piston_ = false;
406 break;
407 case STATE_LOADING_PROBLEM:
408 if (disabled) {
409 state_ = STATE_REQUEST_LOAD;
410 break;
411 }
412 // We got to the goal, but the latch hasn't registered as down. It might
413 // be stuck, or on it's way but not there yet.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700414 if (monotonic_now > loading_problem_end_time_) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700415 // Timeout by unloading.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700416 Unload(monotonic_now);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700417 } else if (position && position->plunger() && position->latch()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700418 // If both trigger, we are latched.
419 state_ = STATE_PREPARE_SHOT;
420 }
421 // Move a bit further back to help it trigger.
422 // If the latch is slow due to the air flowing through the tubes or
423 // inertia, but is otherwise free, this won't have much time to do
424 // anything and is safe. Otherwise this gives us a bit more room to free
425 // up the latch.
426 shooter_.SetGoalPosition(values.shooter.lower_limit, 0.0);
427 if (position) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700428 AOS_LOG(DEBUG, "Waiting on latch: plunger %d, latch: %d\n",
Alex Perrycb7da4b2019-08-28 19:35:56 -0700429 position->plunger(), position->latch());
Brian Silverman17f503e2015-08-02 18:17:18 -0700430 }
431
432 latch_piston_ = true;
433 brake_piston_ = false;
434 break;
435 case STATE_PREPARE_SHOT:
436 // Move the shooter to the shot power set point and then lock the brake.
437 // TODO(austin): Timeout. Low priority.
438
439 if (goal) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700440 shooter_.SetGoalPosition(PowerToPosition(goal->shot_power()), 0.0);
Brian Silverman17f503e2015-08-02 18:17:18 -0700441 }
442
Austin Schuhf257f3c2019-10-27 21:00:43 -0700443 AOS_LOG(DEBUG, "PDIFF: absolute_position: %.2f, pow: %.2f\n",
444 shooter_.absolute_position(),
Alex Perrycb7da4b2019-08-28 19:35:56 -0700445 goal ? PowerToPosition(goal->shot_power())
Austin Schuhf257f3c2019-10-27 21:00:43 -0700446 : ::std::numeric_limits<double>::quiet_NaN());
Brian Silverman17f503e2015-08-02 18:17:18 -0700447 if (goal &&
448 ::std::abs(shooter_.absolute_position() -
Alex Perrycb7da4b2019-08-28 19:35:56 -0700449 PowerToPosition(goal->shot_power())) < 0.001 &&
Brian Silverman17f503e2015-08-02 18:17:18 -0700450 ::std::abs(shooter_.absolute_velocity()) < 0.005) {
451 // We are there, set the brake and move on.
452 latch_piston_ = true;
453 brake_piston_ = true;
Austin Schuh9fe68f72019-08-10 19:32:03 -0700454 shooter_brake_set_time_ = monotonic_now + kShooterBrakeSetTime;
Brian Silverman17f503e2015-08-02 18:17:18 -0700455 state_ = STATE_READY;
456 } else {
457 latch_piston_ = true;
458 brake_piston_ = false;
459 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700460 if (goal && goal->unload_requested()) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700461 Unload(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700462 }
463 break;
464 case STATE_READY:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700465 AOS_LOG(DEBUG, "In ready\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700466 // Wait until the brake is set, and a shot is requested or the shot power
467 // is changed.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700468 if (monotonic_now > shooter_brake_set_time_) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700469 status_ready = true;
Brian Silverman17f503e2015-08-02 18:17:18 -0700470 // We have waited long enough for the brake to set, turn the shooter
471 // control loop off.
472 shooter_loop_disable = true;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700473 AOS_LOG(DEBUG, "Brake is now set\n");
Alex Perrycb7da4b2019-08-28 19:35:56 -0700474 if (goal && goal->shot_requested() && !disabled) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700475 AOS_LOG(DEBUG, "Shooting now\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700476 shooter_loop_disable = true;
Austin Schuh9fe68f72019-08-10 19:32:03 -0700477 shot_end_time_ = monotonic_now + kShotEndTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700478 firing_starting_position_ = shooter_.absolute_position();
479 state_ = STATE_FIRE;
480 }
481 }
482 if (state_ == STATE_READY && goal &&
483 ::std::abs(shooter_.absolute_position() -
Alex Perrycb7da4b2019-08-28 19:35:56 -0700484 PowerToPosition(goal->shot_power())) > 0.002) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700485 // TODO(austin): Add a state to release the brake.
486
487 // TODO(austin): Do we want to set the brake here or after shooting?
488 // Depends on air usage.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700489 status_ready = false;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700490 AOS_LOG(DEBUG, "Preparing shot again.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700491 state_ = STATE_PREPARE_SHOT;
492 }
493
494 if (goal) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700495 shooter_.SetGoalPosition(PowerToPosition(goal->shot_power()), 0.0);
Brian Silverman17f503e2015-08-02 18:17:18 -0700496 }
497
498 latch_piston_ = true;
499 brake_piston_ = true;
500
Alex Perrycb7da4b2019-08-28 19:35:56 -0700501 if (goal && goal->unload_requested()) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700502 Unload(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700503 }
504 break;
505
506 case STATE_FIRE:
507 if (disabled) {
508 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700509 if (position->plunger()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700510 // If disabled and the plunger is still back there, reset the
511 // timeout.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700512 shot_end_time_ = monotonic_now + kShotEndTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700513 }
514 }
515 }
516 shooter_loop_disable = true;
517 // Count the number of contiguous cycles during which we haven't moved.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700518 if (::std::abs(last_position_position_ - shooter_.absolute_position()) <
Brian Silverman17f503e2015-08-02 18:17:18 -0700519 0.0005) {
520 ++cycles_not_moved_;
521 } else {
522 cycles_not_moved_ = 0;
523 }
524
525 // If we have moved any amount since the start and the shooter has now
526 // been still for a couple cycles, the shot finished.
527 // Also move on if it times out.
Austin Schuh829e6ad2015-11-08 14:10:37 -0800528 if (((::std::abs(firing_starting_position_ -
529 shooter_.absolute_position()) > 0.0005 &&
Austin Schuhadf2cde2015-11-08 20:35:16 -0800530 cycles_not_moved_ > 6) ||
Austin Schuh9fe68f72019-08-10 19:32:03 -0700531 monotonic_now > shot_end_time_) &&
Alex Perrycb7da4b2019-08-28 19:35:56 -0700532 robot_state().voltage_battery() > 10.5) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700533 state_ = STATE_REQUEST_LOAD;
534 ++shot_count_;
535 }
536 latch_piston_ = false;
537 brake_piston_ = true;
538 break;
539 case STATE_UNLOAD:
540 // Reset the timeouts.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700541 if (disabled) Unload(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700542
543 // If it is latched and the plunger is back, move the pusher back to catch
544 // the plunger.
545 bool all_back;
546 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700547 all_back = position->plunger() && position->latch();
Brian Silverman17f503e2015-08-02 18:17:18 -0700548 } else {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700549 all_back = last_position_plunger_ && last_position_latch_;
Brian Silverman17f503e2015-08-02 18:17:18 -0700550 }
551
552 if (all_back) {
553 // Pull back to 0, 0.
554 shooter_.SetGoalPosition(0.0, 0.0);
555 if (shooter_.absolute_position() < 0.005) {
556 // When we are close enough, 'fire'.
557 latch_piston_ = false;
558 } else {
559 latch_piston_ = true;
560
561 if (position) {
562 CheckCalibrations(position);
563 }
564 }
565 } else {
566 // The plunger isn't all the way back, or it is and it is unlatched, so
567 // we can now unload.
568 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
569 latch_piston_ = false;
570 state_ = STATE_UNLOAD_MOVE;
Austin Schuh9fe68f72019-08-10 19:32:03 -0700571 unload_timeout_ = monotonic_now + kUnloadTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700572 }
573
Austin Schuh9fe68f72019-08-10 19:32:03 -0700574 if (monotonic_now > unload_timeout_) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700575 // We have been stuck trying to unload for way too long, give up and
576 // turn everything off.
577 state_ = STATE_ESTOP;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700578 AOS_LOG(ERROR, "Estopping because took too long to unload.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700579 }
580
581 brake_piston_ = false;
582 break;
583 case STATE_UNLOAD_MOVE: {
584 if (disabled) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700585 unload_timeout_ = monotonic_now + kUnloadTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700586 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
587 }
588 cap_goal = true;
589 shooter_.set_max_voltage(6.0);
590
591 // Slowly move back until we hit the upper limit.
592 // If we were at the limit last cycle, we are done unloading.
593 // This is because if we saturate, we might hit the limit before we are
594 // actually there.
595 if (shooter_.goal_position() >= values.shooter.upper_limit) {
596 shooter_.SetGoalPosition(values.shooter.upper_limit, 0.0);
597 // We don't want the loop fighting the spring when we are unloaded.
598 // Turn it off.
599 shooter_loop_disable = true;
600 state_ = STATE_READY_UNLOAD;
601 } else {
602 shooter_.SetGoalPosition(
603 ::std::min(
604 values.shooter.upper_limit,
Austin Schuh0e997732015-11-08 15:14:53 -0800605 shooter_.goal_position() + values.shooter.unload_speed * kDt),
Brian Silverman17f503e2015-08-02 18:17:18 -0700606 values.shooter.unload_speed);
607 }
608
609 latch_piston_ = false;
610 brake_piston_ = false;
611 } break;
612 case STATE_READY_UNLOAD:
Alex Perrycb7da4b2019-08-28 19:35:56 -0700613 if (goal && goal->load_requested()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700614 state_ = STATE_REQUEST_LOAD;
615 }
616 // If we are ready to load again,
617 shooter_loop_disable = true;
618
619 latch_piston_ = false;
620 brake_piston_ = false;
621 break;
622
623 case STATE_ESTOP:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700624 AOS_LOG(WARNING, "estopped\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700625 // Totally lost, go to a safe state.
626 shooter_loop_disable = true;
627 latch_piston_ = true;
628 brake_piston_ = true;
629 break;
630 }
631
632 if (!shooter_loop_disable) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700633 if (!cap_goal) {
634 shooter_.set_max_voltage(12.0);
635 }
636 shooter_.Update(output == NULL);
637 if (cap_goal) {
638 shooter_.CapGoal();
639 }
640 // We don't really want to output anything if we went through everything
641 // assuming the motors weren't working.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700642 if (output) output_struct.voltage = shooter_.voltage();
Brian Silverman17f503e2015-08-02 18:17:18 -0700643 } else {
644 shooter_.Update(true);
645 shooter_.ZeroPower();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700646 if (output) output_struct.voltage = 0.0;
Brian Silverman17f503e2015-08-02 18:17:18 -0700647 }
648
Alex Perrycb7da4b2019-08-28 19:35:56 -0700649 Status::Builder status_builder = status->MakeBuilder<Status>();
650
651 status_builder.add_ready(status_ready);
652 status_builder.add_hard_stop_power(
653 PositionToPower(shooter_.absolute_position()));
Brian Silverman17f503e2015-08-02 18:17:18 -0700654
655 if (output) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700656 output_struct.latch_piston = latch_piston_;
657 output_struct.brake_piston = brake_piston_;
658
milind1f1dca32021-07-03 13:50:07 -0700659 output->CheckOk(output->Send(Output::Pack(*output->fbb(), &output_struct)));
Brian Silverman17f503e2015-08-02 18:17:18 -0700660 }
661
662 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700663 last_position_latch_ = position->latch();
664 last_position_plunger_ = position->plunger();
665 last_position_position_ = position->position();
Brian Silverman17f503e2015-08-02 18:17:18 -0700666
Alex Perrycb7da4b2019-08-28 19:35:56 -0700667 last_distal_posedge_count_ = position->pusher_distal()->posedge_count();
668 last_proximal_posedge_count_ = position->pusher_proximal()->posedge_count();
669 last_distal_current_ = position->pusher_distal()->current();
670 last_proximal_current_ = position->pusher_proximal()->current();
Brian Silverman17f503e2015-08-02 18:17:18 -0700671 }
672
Alex Perrycb7da4b2019-08-28 19:35:56 -0700673 status_builder.add_absolute_position(shooter_.absolute_position());
674 status_builder.add_absolute_velocity(shooter_.absolute_velocity());
675 status_builder.add_state(state_);
Austin Schuhadf2cde2015-11-08 20:35:16 -0800676
Alex Perrycb7da4b2019-08-28 19:35:56 -0700677 status_builder.add_shots(shot_count_);
678
milind1f1dca32021-07-03 13:50:07 -0700679 (void)status->Send(status_builder.Finish());
Brian Silverman17f503e2015-08-02 18:17:18 -0700680}
681
Alex Perrycb7da4b2019-08-28 19:35:56 -0700682flatbuffers::Offset<Output> ShooterMotor::Zero(
683 aos::Sender<Output>::Builder *output) {
684 Output::Builder output_builder = output->MakeBuilder<Output>();
685 output_builder.add_voltage(0.0);
686 output_builder.add_latch_piston(latch_piston_);
687 output_builder.add_brake_piston(brake_piston_);
688 return output_builder.Finish();
Brian Silverman17f503e2015-08-02 18:17:18 -0700689}
690
Alex Perrycb7da4b2019-08-28 19:35:56 -0700691} // namespace shooter
Brian Silverman17f503e2015-08-02 18:17:18 -0700692} // namespace control_loops
Austin Schuh24957102015-11-28 16:04:40 -0800693} // namespace y2014