blob: b0ce4bae630a157b3549d3e13b88f7dc2030e3ef [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
Stephan Pleinesf63bde82024-01-13 15:59:33 -080012namespace y2014::control_loops::shooter {
Brian Silverman17f503e2015-08-02 18:17:18 -070013
Austin Schuh214e9c12016-11-25 17:26:20 -080014namespace chrono = ::std::chrono;
15using ::aos::monotonic_clock;
16
Austin Schuh0e997732015-11-08 15:14:53 -080017using ::y2014::control_loops::shooter::kDt;
James Kuszmaul61750662021-06-21 21:32:33 -070018using ::y2014::control_loops::shooter::kMaxExtension;
19using ::y2014::control_loops::shooter::kSpringConstant;
Austin Schuh9d4aca82015-11-08 14:41:31 -080020using ::y2014::control_loops::shooter::MakeShooterLoop;
Brian Silverman17f503e2015-08-02 18:17:18 -070021
22void ZeroedStateFeedbackLoop::CapU() {
23 const double old_voltage = voltage_;
24 voltage_ += U(0, 0);
25
26 uncapped_voltage_ = voltage_;
27
28 // Make sure that reality and the observer can't get too far off. There is a
29 // delay by one cycle between the applied voltage and X_hat(2, 0), so compare
30 // against last cycle's voltage.
31 if (X_hat(2, 0) > last_voltage_ + 4.0) {
32 voltage_ -= X_hat(2, 0) - (last_voltage_ + 4.0);
Austin Schuhf257f3c2019-10-27 21:00:43 -070033 AOS_LOG(DEBUG, "Capping due to runaway\n");
Brian Silverman17f503e2015-08-02 18:17:18 -070034 } else if (X_hat(2, 0) < last_voltage_ - 4.0) {
35 voltage_ += X_hat(2, 0) - (last_voltage_ - 4.0);
Austin Schuhf257f3c2019-10-27 21:00:43 -070036 AOS_LOG(DEBUG, "Capping due to runaway\n");
Brian Silverman17f503e2015-08-02 18:17:18 -070037 }
38
39 voltage_ = std::min(max_voltage_, voltage_);
40 voltage_ = std::max(-max_voltage_, voltage_);
41 mutable_U(0, 0) = voltage_ - old_voltage;
42
Brian Silverman17f503e2015-08-02 18:17:18 -070043 last_voltage_ = voltage_;
44 capped_goal_ = false;
45}
46
47void ZeroedStateFeedbackLoop::CapGoal() {
48 if (uncapped_voltage() > max_voltage_) {
49 double dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080050 if (index() == 0) {
Brian Silverman17f503e2015-08-02 18:17:18 -070051 dx = (uncapped_voltage() - max_voltage_) /
Austin Schuh32501832017-02-25 18:32:56 -080052 (controller().K(0, 0) -
53 plant().A(1, 0) * controller().K(0, 2) / plant().A(1, 2));
Brian Silverman17f503e2015-08-02 18:17:18 -070054 mutable_R(0, 0) -= dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080055 mutable_R(2, 0) -= -plant().A(1, 0) / plant().A(1, 2) * dx;
Brian Silverman17f503e2015-08-02 18:17:18 -070056 } else {
Austin Schuh32501832017-02-25 18:32:56 -080057 dx = (uncapped_voltage() - max_voltage_) / controller().K(0, 0);
Brian Silverman17f503e2015-08-02 18:17:18 -070058 mutable_R(0, 0) -= dx;
59 }
60 capped_goal_ = true;
Brian Silverman17f503e2015-08-02 18:17:18 -070061 } else if (uncapped_voltage() < -max_voltage_) {
62 double dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080063 if (index() == 0) {
Brian Silverman17f503e2015-08-02 18:17:18 -070064 dx = (uncapped_voltage() + max_voltage_) /
Austin Schuh32501832017-02-25 18:32:56 -080065 (controller().K(0, 0) -
66 plant().A(1, 0) * controller().K(0, 2) / plant().A(1, 2));
Brian Silverman17f503e2015-08-02 18:17:18 -070067 mutable_R(0, 0) -= dx;
Austin Schuhc5fceb82017-02-25 16:24:12 -080068 mutable_R(2, 0) -= -plant().A(1, 0) / plant().A(1, 2) * dx;
Brian Silverman17f503e2015-08-02 18:17:18 -070069 } else {
Austin Schuh32501832017-02-25 18:32:56 -080070 dx = (uncapped_voltage() + max_voltage_) / controller().K(0, 0);
Brian Silverman17f503e2015-08-02 18:17:18 -070071 mutable_R(0, 0) -= dx;
72 }
73 capped_goal_ = true;
Brian Silverman17f503e2015-08-02 18:17:18 -070074 } else {
75 capped_goal_ = false;
76 }
77}
78
79void ZeroedStateFeedbackLoop::RecalculatePowerGoal() {
Austin Schuhc5fceb82017-02-25 16:24:12 -080080 if (index() == 0) {
81 mutable_R(2, 0) = (-plant().A(1, 0) / plant().A(1, 2) * R(0, 0) -
82 plant().A(1, 1) / plant().A(1, 2) * R(1, 0));
Brian Silverman17f503e2015-08-02 18:17:18 -070083 } else {
Austin Schuhc5fceb82017-02-25 16:24:12 -080084 mutable_R(2, 0) = -plant().A(1, 1) / plant().A(1, 2) * R(1, 0);
Brian Silverman17f503e2015-08-02 18:17:18 -070085 }
86}
87
88void ZeroedStateFeedbackLoop::SetCalibration(double encoder_val,
89 double known_position) {
Brian Silverman17f503e2015-08-02 18:17:18 -070090 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;
Austin Schuhc5fceb82017-02-25 16:24:12 -080096 if (index() == 0) {
97 mutable_R(2, 0) += -plant().A(1, 0) / plant().A(1, 2) * (doffset);
Brian Silverman17f503e2015-08-02 18:17:18 -070098 }
Brian Silverman17f503e2015-08-02 18:17:18 -070099}
100
Austin Schuh55a13dc2019-01-27 22:39:03 -0800101ShooterMotor::ShooterMotor(::aos::EventLoop *event_loop,
102 const ::std::string &name)
James Kuszmaul61750662021-06-21 21:32:33 -0700103 : frc971::controls::ControlLoop<Goal, Position, Status, Output>(event_loop,
104 name),
Brian Silverman17f503e2015-08-02 18:17:18 -0700105 shooter_(MakeShooterLoop()),
106 state_(STATE_INITIALIZE),
Brian Silverman17f503e2015-08-02 18:17:18 -0700107 cycles_not_moved_(0),
108 shot_count_(0),
109 zeroed_(false),
110 distal_posedge_validation_cycles_left_(0),
111 proximal_posedge_validation_cycles_left_(0),
112 last_distal_current_(true),
113 last_proximal_current_(true) {}
114
115double ShooterMotor::PowerToPosition(double power) {
Austin Schuh24957102015-11-28 16:04:40 -0800116 const constants::Values &values = constants::GetValues();
Brian Silverman17f503e2015-08-02 18:17:18 -0700117 double maxpower = 0.5 * kSpringConstant *
118 (kMaxExtension * kMaxExtension -
119 (kMaxExtension - values.shooter.upper_limit) *
120 (kMaxExtension - values.shooter.upper_limit));
121 if (power < 0) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700122 power = 0;
123 } else if (power > maxpower) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700124 power = maxpower;
125 }
126
127 double mp = kMaxExtension * kMaxExtension - (power + power) / kSpringConstant;
128 double new_pos = 0.10;
129 if (mp < 0) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700130 AOS_LOG(ERROR,
131 "Power calculation has negative number before square root (%f).\n",
132 mp);
Brian Silverman17f503e2015-08-02 18:17:18 -0700133 } else {
134 new_pos = kMaxExtension - ::std::sqrt(mp);
135 }
136
137 new_pos = ::std::min(::std::max(new_pos, values.shooter.lower_limit),
James Kuszmaul61750662021-06-21 21:32:33 -0700138 values.shooter.upper_limit);
Brian Silverman17f503e2015-08-02 18:17:18 -0700139 return new_pos;
140}
141
142double ShooterMotor::PositionToPower(double position) {
143 double power = kSpringConstant * position * (kMaxExtension - position / 2.0);
144 return power;
145}
146
Alex Perrycb7da4b2019-08-28 19:35:56 -0700147void ShooterMotor::CheckCalibrations(const Position *position) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700148 AOS_CHECK_NOTNULL(position);
Austin Schuh24957102015-11-28 16:04:40 -0800149 const constants::Values &values = constants::GetValues();
Brian Silverman17f503e2015-08-02 18:17:18 -0700150
151 // TODO(austin): Validate that this is the right edge.
152 // If we see a posedge on any of the hall effects,
Alex Perrycb7da4b2019-08-28 19:35:56 -0700153 if (position->pusher_proximal()->posedge_count() !=
154 last_proximal_posedge_count_ &&
Brian Silverman17f503e2015-08-02 18:17:18 -0700155 !last_proximal_current_) {
156 proximal_posedge_validation_cycles_left_ = 2;
157 }
158 if (proximal_posedge_validation_cycles_left_ > 0) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700159 if (position->pusher_proximal()->current()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700160 --proximal_posedge_validation_cycles_left_;
161 if (proximal_posedge_validation_cycles_left_ == 0) {
James Kuszmaul61750662021-06-21 21:32:33 -0700162 shooter_.SetCalibration(position->pusher_proximal()->posedge_value(),
163 values.shooter.pusher_proximal.upper_angle);
Brian Silverman17f503e2015-08-02 18:17:18 -0700164
Austin Schuhf257f3c2019-10-27 21:00:43 -0700165 AOS_LOG(DEBUG, "Setting calibration using proximal sensor\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700166 zeroed_ = true;
167 }
168 } else {
169 proximal_posedge_validation_cycles_left_ = 0;
170 }
171 }
172
Alex Perrycb7da4b2019-08-28 19:35:56 -0700173 if (position->pusher_distal()->posedge_count() !=
174 last_distal_posedge_count_ &&
Brian Silverman17f503e2015-08-02 18:17:18 -0700175 !last_distal_current_) {
176 distal_posedge_validation_cycles_left_ = 2;
177 }
178 if (distal_posedge_validation_cycles_left_ > 0) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700179 if (position->pusher_distal()->current()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700180 --distal_posedge_validation_cycles_left_;
181 if (distal_posedge_validation_cycles_left_ == 0) {
James Kuszmaul61750662021-06-21 21:32:33 -0700182 shooter_.SetCalibration(position->pusher_distal()->posedge_value(),
183 values.shooter.pusher_distal.upper_angle);
Brian Silverman17f503e2015-08-02 18:17:18 -0700184
Austin Schuhf257f3c2019-10-27 21:00:43 -0700185 AOS_LOG(DEBUG, "Setting calibration using distal sensor\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700186 zeroed_ = true;
187 }
188 } else {
189 distal_posedge_validation_cycles_left_ = 0;
190 }
191 }
192}
193
194// Positive is out, and positive power is out.
James Kuszmaul61750662021-06-21 21:32:33 -0700195void ShooterMotor::RunIteration(const Goal *goal, const Position *position,
196 aos::Sender<Output>::Builder *output,
197 aos::Sender<Status>::Builder *status) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700198 OutputT output_struct;
James Kuszmaul61750662021-06-21 21:32:33 -0700199 const monotonic_clock::time_point monotonic_now =
200 event_loop()->monotonic_now();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700201 if (goal && ::std::isnan(goal->shot_power())) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700202 state_ = STATE_ESTOP;
203 AOS_LOG(ERROR, "Estopping because got a shot power of NAN.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700204 }
205
206 // we must always have these or we have issues.
207 if (status == NULL) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700208 if (output) output_struct.voltage = 0;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700209 AOS_LOG(ERROR, "Thought I would just check for null and die.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700210 return;
211 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700212 bool status_ready = false;
Brian Silverman17f503e2015-08-02 18:17:18 -0700213
214 if (WasReset()) {
215 state_ = STATE_INITIALIZE;
Alex Perrycb7da4b2019-08-28 19:35:56 -0700216 last_distal_current_ = position->pusher_distal()->current();
217 last_proximal_current_ = position->pusher_proximal()->current();
Brian Silverman17f503e2015-08-02 18:17:18 -0700218 }
219 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700220 shooter_.CorrectPosition(position->position());
Brian Silverman17f503e2015-08-02 18:17:18 -0700221 }
222
223 // Disable the motors now so that all early returns will return with the
224 // motors disabled.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700225 if (output) output_struct.voltage = 0;
Brian Silverman17f503e2015-08-02 18:17:18 -0700226
Austin Schuh24957102015-11-28 16:04:40 -0800227 const constants::Values &values = constants::GetValues();
Brian Silverman17f503e2015-08-02 18:17:18 -0700228
229 // Don't even let the control loops run.
230 bool shooter_loop_disable = false;
231
Alex Perrycb7da4b2019-08-28 19:35:56 -0700232 const bool disabled = !has_joystick_state() || !joystick_state().enabled();
Brian Silverman17f503e2015-08-02 18:17:18 -0700233
234 // If true, move the goal if we saturate.
235 bool cap_goal = false;
236
237 // TODO(austin): Move the offset if we see or don't see a hall effect when we
238 // expect to see one.
239 // Probably not needed yet.
240
241 if (position) {
Austin Schuhc5fceb82017-02-25 16:24:12 -0800242 int last_index = shooter_.index();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700243 if (position->plunger() && position->latch()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700244 // Use the controller without the spring if the latch is set and the
245 // plunger is back
Austin Schuhc5fceb82017-02-25 16:24:12 -0800246 shooter_.set_index(1);
Brian Silverman17f503e2015-08-02 18:17:18 -0700247 } else {
248 // Otherwise use the controller with the spring.
Austin Schuhc5fceb82017-02-25 16:24:12 -0800249 shooter_.set_index(0);
Brian Silverman17f503e2015-08-02 18:17:18 -0700250 }
Austin Schuhc5fceb82017-02-25 16:24:12 -0800251 if (shooter_.index() != last_index) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700252 shooter_.RecalculatePowerGoal();
253 }
254 }
255
256 switch (state_) {
257 case STATE_INITIALIZE:
258 if (position) {
259 // Reinitialize the internal filter state.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700260 shooter_.InitializeState(position->position());
Brian Silverman17f503e2015-08-02 18:17:18 -0700261
262 // Start off with the assumption that we are at the value
263 // futhest back given our sensors.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700264 if (position->pusher_distal()->current()) {
265 shooter_.SetCalibration(position->position(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700266 values.shooter.pusher_distal.lower_angle);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700267 } else if (position->pusher_proximal()->current()) {
268 shooter_.SetCalibration(position->position(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700269 values.shooter.pusher_proximal.upper_angle);
270 } else {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700271 shooter_.SetCalibration(position->position(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700272 values.shooter.upper_limit);
273 }
274
275 // Go to the current position.
276 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
277 // If the plunger is all the way back, we want to be latched.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700278 latch_piston_ = position->plunger();
Brian Silverman17f503e2015-08-02 18:17:18 -0700279 brake_piston_ = false;
Alex Perrycb7da4b2019-08-28 19:35:56 -0700280 if (position->latch() == latch_piston_) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700281 state_ = STATE_REQUEST_LOAD;
282 } else {
283 shooter_loop_disable = true;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700284 AOS_LOG(DEBUG,
285 "Not moving on until the latch has moved to avoid a crash\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700286 }
287 } else {
288 // If we can't start yet because we don't know where we are, set the
289 // latch and brake to their defaults.
290 latch_piston_ = true;
291 brake_piston_ = true;
292 }
293 break;
294 case STATE_REQUEST_LOAD:
295 if (position) {
296 zeroed_ = false;
Alex Perrycb7da4b2019-08-28 19:35:56 -0700297 if (position->pusher_distal()->current() ||
298 position->pusher_proximal()->current()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700299 // We started on the sensor, back up until we are found.
300 // If the plunger is all the way back and not latched, it won't be
301 // there for long.
302 state_ = STATE_LOAD_BACKTRACK;
303
304 // The plunger is already back and latched. Don't release it.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700305 if (position->plunger() && position->latch()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700306 latch_piston_ = true;
307 } else {
308 latch_piston_ = false;
309 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700310 } else if (position->plunger() && position->latch()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700311 // The plunger is back and we are latched. We most likely got here
312 // from Initialize, in which case we want to 'load' again anyways to
313 // zero.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700314 Load(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700315 latch_piston_ = true;
316 } else {
317 // Off the sensor, start loading.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700318 Load(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700319 latch_piston_ = false;
320 }
321 }
322
323 // Hold our current position.
324 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
325 brake_piston_ = false;
326 break;
327 case STATE_LOAD_BACKTRACK:
328 // If we are here, then that means we started past the edge where we want
329 // to zero. Move backwards until we don't see the sensor anymore.
330 // The plunger is contacting the pusher (or will be shortly).
331
332 if (!disabled) {
333 shooter_.SetGoalPosition(
Austin Schuh0e997732015-11-08 15:14:53 -0800334 shooter_.goal_position() + values.shooter.zeroing_speed * kDt,
Brian Silverman17f503e2015-08-02 18:17:18 -0700335 values.shooter.zeroing_speed);
336 }
337 cap_goal = true;
338 shooter_.set_max_voltage(4.0);
339
340 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700341 if (!position->pusher_distal()->current() &&
342 !position->pusher_proximal()->current()) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700343 Load(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700344 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700345 latch_piston_ = position->plunger();
Brian Silverman17f503e2015-08-02 18:17:18 -0700346 }
347
348 brake_piston_ = false;
349 break;
350 case STATE_LOAD:
351 // If we are disabled right now, reset the timer.
352 if (disabled) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700353 Load(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700354 // Latch defaults to true when disabled. Leave it latched until we have
355 // useful sensor data.
356 latch_piston_ = true;
357 }
358 if (output == nullptr) {
James Kuszmaul61750662021-06-21 21:32:33 -0700359 load_timeout_ += ::frc971::controls::kLoopFrequency;
Brian Silverman17f503e2015-08-02 18:17:18 -0700360 }
361 // Go to 0, which should be the latch position, or trigger a hall effect
362 // on the way. If we don't see edges where we are supposed to, the
363 // offset will be updated by code above.
364 shooter_.SetGoalPosition(0.0, 0.0);
365
366 if (position) {
367 CheckCalibrations(position);
368
369 // Latch if the plunger is far enough back to trigger the hall effect.
370 // This happens when the distal sensor is triggered.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700371 latch_piston_ =
372 position->pusher_distal()->current() || position->plunger();
Brian Silverman17f503e2015-08-02 18:17:18 -0700373
374 // Check if we are latched and back. Make sure the plunger is all the
375 // way back as well.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700376 if (position->plunger() && position->latch() &&
377 position->pusher_distal()->current()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700378 if (!zeroed_) {
379 state_ = STATE_REQUEST_LOAD;
380 } else {
381 state_ = STATE_PREPARE_SHOT;
382 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700383 } else if (position->plunger() &&
Brian Silverman17f503e2015-08-02 18:17:18 -0700384 ::std::abs(shooter_.absolute_position() -
385 shooter_.goal_position()) < 0.001) {
386 // We are at the goal, but not latched.
387 state_ = STATE_LOADING_PROBLEM;
James Kuszmaul61750662021-06-21 21:32:33 -0700388 loading_problem_end_time_ = monotonic_now + kLoadProblemEndTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700389 }
390 }
Austin Schuh9fe68f72019-08-10 19:32:03 -0700391 if (load_timeout_ < monotonic_now) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700392 if (position) {
Austin Schuhadf2cde2015-11-08 20:35:16 -0800393 // If none of the sensors is triggered, estop.
394 // Otherwise, trigger anyways if it has been 0.5 seconds more.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700395 if (!(position->pusher_distal()->current() ||
396 position->pusher_proximal()->current()) ||
397 (load_timeout_ + chrono::milliseconds(500) < monotonic_now)) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700398 state_ = STATE_ESTOP;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700399 AOS_LOG(ERROR, "Estopping because took too long to load.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700400 }
401 }
402 }
403 brake_piston_ = false;
404 break;
405 case STATE_LOADING_PROBLEM:
406 if (disabled) {
407 state_ = STATE_REQUEST_LOAD;
408 break;
409 }
410 // We got to the goal, but the latch hasn't registered as down. It might
411 // be stuck, or on it's way but not there yet.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700412 if (monotonic_now > loading_problem_end_time_) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700413 // Timeout by unloading.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700414 Unload(monotonic_now);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700415 } else if (position && position->plunger() && position->latch()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700416 // If both trigger, we are latched.
417 state_ = STATE_PREPARE_SHOT;
418 }
419 // Move a bit further back to help it trigger.
420 // If the latch is slow due to the air flowing through the tubes or
421 // inertia, but is otherwise free, this won't have much time to do
422 // anything and is safe. Otherwise this gives us a bit more room to free
423 // up the latch.
424 shooter_.SetGoalPosition(values.shooter.lower_limit, 0.0);
425 if (position) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700426 AOS_LOG(DEBUG, "Waiting on latch: plunger %d, latch: %d\n",
Alex Perrycb7da4b2019-08-28 19:35:56 -0700427 position->plunger(), position->latch());
Brian Silverman17f503e2015-08-02 18:17:18 -0700428 }
429
430 latch_piston_ = true;
431 brake_piston_ = false;
432 break;
433 case STATE_PREPARE_SHOT:
434 // Move the shooter to the shot power set point and then lock the brake.
435 // TODO(austin): Timeout. Low priority.
436
437 if (goal) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700438 shooter_.SetGoalPosition(PowerToPosition(goal->shot_power()), 0.0);
Brian Silverman17f503e2015-08-02 18:17:18 -0700439 }
440
Austin Schuhf257f3c2019-10-27 21:00:43 -0700441 AOS_LOG(DEBUG, "PDIFF: absolute_position: %.2f, pow: %.2f\n",
442 shooter_.absolute_position(),
Alex Perrycb7da4b2019-08-28 19:35:56 -0700443 goal ? PowerToPosition(goal->shot_power())
Austin Schuhf257f3c2019-10-27 21:00:43 -0700444 : ::std::numeric_limits<double>::quiet_NaN());
Brian Silverman17f503e2015-08-02 18:17:18 -0700445 if (goal &&
446 ::std::abs(shooter_.absolute_position() -
Alex Perrycb7da4b2019-08-28 19:35:56 -0700447 PowerToPosition(goal->shot_power())) < 0.001 &&
Brian Silverman17f503e2015-08-02 18:17:18 -0700448 ::std::abs(shooter_.absolute_velocity()) < 0.005) {
449 // We are there, set the brake and move on.
450 latch_piston_ = true;
451 brake_piston_ = true;
Austin Schuh9fe68f72019-08-10 19:32:03 -0700452 shooter_brake_set_time_ = monotonic_now + kShooterBrakeSetTime;
Brian Silverman17f503e2015-08-02 18:17:18 -0700453 state_ = STATE_READY;
454 } else {
455 latch_piston_ = true;
456 brake_piston_ = false;
457 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700458 if (goal && goal->unload_requested()) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700459 Unload(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700460 }
461 break;
462 case STATE_READY:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700463 AOS_LOG(DEBUG, "In ready\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700464 // Wait until the brake is set, and a shot is requested or the shot power
465 // is changed.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700466 if (monotonic_now > shooter_brake_set_time_) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700467 status_ready = true;
Brian Silverman17f503e2015-08-02 18:17:18 -0700468 // We have waited long enough for the brake to set, turn the shooter
469 // control loop off.
470 shooter_loop_disable = true;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700471 AOS_LOG(DEBUG, "Brake is now set\n");
Alex Perrycb7da4b2019-08-28 19:35:56 -0700472 if (goal && goal->shot_requested() && !disabled) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700473 AOS_LOG(DEBUG, "Shooting now\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700474 shooter_loop_disable = true;
Austin Schuh9fe68f72019-08-10 19:32:03 -0700475 shot_end_time_ = monotonic_now + kShotEndTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700476 firing_starting_position_ = shooter_.absolute_position();
477 state_ = STATE_FIRE;
478 }
479 }
480 if (state_ == STATE_READY && goal &&
481 ::std::abs(shooter_.absolute_position() -
Alex Perrycb7da4b2019-08-28 19:35:56 -0700482 PowerToPosition(goal->shot_power())) > 0.002) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700483 // TODO(austin): Add a state to release the brake.
484
485 // TODO(austin): Do we want to set the brake here or after shooting?
486 // Depends on air usage.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700487 status_ready = false;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700488 AOS_LOG(DEBUG, "Preparing shot again.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700489 state_ = STATE_PREPARE_SHOT;
490 }
491
492 if (goal) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700493 shooter_.SetGoalPosition(PowerToPosition(goal->shot_power()), 0.0);
Brian Silverman17f503e2015-08-02 18:17:18 -0700494 }
495
496 latch_piston_ = true;
497 brake_piston_ = true;
498
Alex Perrycb7da4b2019-08-28 19:35:56 -0700499 if (goal && goal->unload_requested()) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700500 Unload(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700501 }
502 break;
503
504 case STATE_FIRE:
505 if (disabled) {
506 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700507 if (position->plunger()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700508 // If disabled and the plunger is still back there, reset the
509 // timeout.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700510 shot_end_time_ = monotonic_now + kShotEndTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700511 }
512 }
513 }
514 shooter_loop_disable = true;
515 // Count the number of contiguous cycles during which we haven't moved.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700516 if (::std::abs(last_position_position_ - shooter_.absolute_position()) <
Brian Silverman17f503e2015-08-02 18:17:18 -0700517 0.0005) {
518 ++cycles_not_moved_;
519 } else {
520 cycles_not_moved_ = 0;
521 }
522
523 // If we have moved any amount since the start and the shooter has now
524 // been still for a couple cycles, the shot finished.
525 // Also move on if it times out.
Austin Schuh829e6ad2015-11-08 14:10:37 -0800526 if (((::std::abs(firing_starting_position_ -
527 shooter_.absolute_position()) > 0.0005 &&
Austin Schuhadf2cde2015-11-08 20:35:16 -0800528 cycles_not_moved_ > 6) ||
Austin Schuh9fe68f72019-08-10 19:32:03 -0700529 monotonic_now > shot_end_time_) &&
Alex Perrycb7da4b2019-08-28 19:35:56 -0700530 robot_state().voltage_battery() > 10.5) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700531 state_ = STATE_REQUEST_LOAD;
532 ++shot_count_;
533 }
534 latch_piston_ = false;
535 brake_piston_ = true;
536 break;
537 case STATE_UNLOAD:
538 // Reset the timeouts.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700539 if (disabled) Unload(monotonic_now);
Brian Silverman17f503e2015-08-02 18:17:18 -0700540
541 // If it is latched and the plunger is back, move the pusher back to catch
542 // the plunger.
543 bool all_back;
544 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700545 all_back = position->plunger() && position->latch();
Brian Silverman17f503e2015-08-02 18:17:18 -0700546 } else {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700547 all_back = last_position_plunger_ && last_position_latch_;
Brian Silverman17f503e2015-08-02 18:17:18 -0700548 }
549
550 if (all_back) {
551 // Pull back to 0, 0.
552 shooter_.SetGoalPosition(0.0, 0.0);
553 if (shooter_.absolute_position() < 0.005) {
554 // When we are close enough, 'fire'.
555 latch_piston_ = false;
556 } else {
557 latch_piston_ = true;
558
559 if (position) {
560 CheckCalibrations(position);
561 }
562 }
563 } else {
564 // The plunger isn't all the way back, or it is and it is unlatched, so
565 // we can now unload.
566 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
567 latch_piston_ = false;
568 state_ = STATE_UNLOAD_MOVE;
Austin Schuh9fe68f72019-08-10 19:32:03 -0700569 unload_timeout_ = monotonic_now + kUnloadTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700570 }
571
Austin Schuh9fe68f72019-08-10 19:32:03 -0700572 if (monotonic_now > unload_timeout_) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700573 // We have been stuck trying to unload for way too long, give up and
574 // turn everything off.
575 state_ = STATE_ESTOP;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700576 AOS_LOG(ERROR, "Estopping because took too long to unload.\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700577 }
578
579 brake_piston_ = false;
580 break;
581 case STATE_UNLOAD_MOVE: {
582 if (disabled) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700583 unload_timeout_ = monotonic_now + kUnloadTimeout;
Brian Silverman17f503e2015-08-02 18:17:18 -0700584 shooter_.SetGoalPosition(shooter_.absolute_position(), 0.0);
585 }
586 cap_goal = true;
587 shooter_.set_max_voltage(6.0);
588
589 // Slowly move back until we hit the upper limit.
590 // If we were at the limit last cycle, we are done unloading.
591 // This is because if we saturate, we might hit the limit before we are
592 // actually there.
593 if (shooter_.goal_position() >= values.shooter.upper_limit) {
594 shooter_.SetGoalPosition(values.shooter.upper_limit, 0.0);
595 // We don't want the loop fighting the spring when we are unloaded.
596 // Turn it off.
597 shooter_loop_disable = true;
598 state_ = STATE_READY_UNLOAD;
599 } else {
600 shooter_.SetGoalPosition(
601 ::std::min(
602 values.shooter.upper_limit,
Austin Schuh0e997732015-11-08 15:14:53 -0800603 shooter_.goal_position() + values.shooter.unload_speed * kDt),
Brian Silverman17f503e2015-08-02 18:17:18 -0700604 values.shooter.unload_speed);
605 }
606
607 latch_piston_ = false;
608 brake_piston_ = false;
609 } break;
610 case STATE_READY_UNLOAD:
Alex Perrycb7da4b2019-08-28 19:35:56 -0700611 if (goal && goal->load_requested()) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700612 state_ = STATE_REQUEST_LOAD;
613 }
614 // If we are ready to load again,
615 shooter_loop_disable = true;
616
617 latch_piston_ = false;
618 brake_piston_ = false;
619 break;
620
621 case STATE_ESTOP:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700622 AOS_LOG(WARNING, "estopped\n");
Brian Silverman17f503e2015-08-02 18:17:18 -0700623 // Totally lost, go to a safe state.
624 shooter_loop_disable = true;
625 latch_piston_ = true;
626 brake_piston_ = true;
627 break;
628 }
629
630 if (!shooter_loop_disable) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700631 if (!cap_goal) {
632 shooter_.set_max_voltage(12.0);
633 }
634 shooter_.Update(output == NULL);
635 if (cap_goal) {
636 shooter_.CapGoal();
637 }
638 // We don't really want to output anything if we went through everything
639 // assuming the motors weren't working.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700640 if (output) output_struct.voltage = shooter_.voltage();
Brian Silverman17f503e2015-08-02 18:17:18 -0700641 } else {
642 shooter_.Update(true);
643 shooter_.ZeroPower();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700644 if (output) output_struct.voltage = 0.0;
Brian Silverman17f503e2015-08-02 18:17:18 -0700645 }
646
Alex Perrycb7da4b2019-08-28 19:35:56 -0700647 Status::Builder status_builder = status->MakeBuilder<Status>();
648
649 status_builder.add_ready(status_ready);
650 status_builder.add_hard_stop_power(
651 PositionToPower(shooter_.absolute_position()));
Brian Silverman17f503e2015-08-02 18:17:18 -0700652
653 if (output) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700654 output_struct.latch_piston = latch_piston_;
655 output_struct.brake_piston = brake_piston_;
656
milind1f1dca32021-07-03 13:50:07 -0700657 output->CheckOk(output->Send(Output::Pack(*output->fbb(), &output_struct)));
Brian Silverman17f503e2015-08-02 18:17:18 -0700658 }
659
660 if (position) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700661 last_position_latch_ = position->latch();
662 last_position_plunger_ = position->plunger();
663 last_position_position_ = position->position();
Brian Silverman17f503e2015-08-02 18:17:18 -0700664
Alex Perrycb7da4b2019-08-28 19:35:56 -0700665 last_distal_posedge_count_ = position->pusher_distal()->posedge_count();
666 last_proximal_posedge_count_ = position->pusher_proximal()->posedge_count();
667 last_distal_current_ = position->pusher_distal()->current();
668 last_proximal_current_ = position->pusher_proximal()->current();
Brian Silverman17f503e2015-08-02 18:17:18 -0700669 }
670
Alex Perrycb7da4b2019-08-28 19:35:56 -0700671 status_builder.add_absolute_position(shooter_.absolute_position());
672 status_builder.add_absolute_velocity(shooter_.absolute_velocity());
673 status_builder.add_state(state_);
Austin Schuhadf2cde2015-11-08 20:35:16 -0800674
Alex Perrycb7da4b2019-08-28 19:35:56 -0700675 status_builder.add_shots(shot_count_);
676
milind1f1dca32021-07-03 13:50:07 -0700677 (void)status->Send(status_builder.Finish());
Brian Silverman17f503e2015-08-02 18:17:18 -0700678}
679
Alex Perrycb7da4b2019-08-28 19:35:56 -0700680flatbuffers::Offset<Output> ShooterMotor::Zero(
681 aos::Sender<Output>::Builder *output) {
682 Output::Builder output_builder = output->MakeBuilder<Output>();
683 output_builder.add_voltage(0.0);
684 output_builder.add_latch_piston(latch_piston_);
685 output_builder.add_brake_piston(brake_piston_);
686 return output_builder.Finish();
Brian Silverman17f503e2015-08-02 18:17:18 -0700687}
688
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800689} // namespace y2014::control_loops::shooter