blob: 0c523605ea95ff6443ae7280d8cb4ca9aa7cc70d [file] [log] [blame]
Niko Sohmersc4d2c502024-02-19 19:35:35 -08001#include "y2024/control_loops/superstructure/shooter.h"
2
3#include "aos/flatbuffers.h"
4#include "aos/flatbuffers/base.h"
5#include "frc971/control_loops/aiming/aiming.h"
6#include "y2024/control_loops/superstructure/catapult/catapult_plant.h"
Niko Sohmersac4d8872024-02-23 13:55:47 -08007#include "y2024/control_loops/superstructure/collision_avoidance.h"
Niko Sohmersc4d2c502024-02-19 19:35:35 -08008
9namespace y2024::control_loops::superstructure {
10
11using frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus;
12
Niko Sohmers6adb5b92024-03-16 17:47:54 -070013constexpr double kCatapultActivationTurretThreshold = 0.03;
14constexpr double kCatapultActivationAltitudeThreshold = 0.01;
Niko Sohmersc4d2c502024-02-19 19:35:35 -080015
16Shooter::Shooter(aos::EventLoop *event_loop, const Constants *robot_constants)
17 : drivetrain_status_fetcher_(
18 event_loop->MakeFetcher<frc971::control_loops::drivetrain::Status>(
19 "/drivetrain")),
Niko Sohmersc4d2c502024-02-19 19:35:35 -080020 robot_constants_(robot_constants),
21 catapult_(
22 robot_constants->common()->catapult(),
23 robot_constants->robot()->catapult_constants()->zeroing_constants()),
24 turret_(
25 robot_constants_->common()->turret(),
26 robot_constants_->robot()->turret_constants()->zeroing_constants()),
27 altitude_(
28 robot_constants_->common()->altitude(),
29 robot_constants_->robot()->altitude_constants()->zeroing_constants()),
30 aimer_(event_loop, robot_constants_),
31 interpolation_table_(
32 y2024::constants::Values::InterpolationTableFromFlatbuffer(
Maxwell Henderson6b1be312024-02-28 20:15:06 -080033 robot_constants_->common()->shooter_interpolation_table())),
Filip Kujawa1286c012024-03-31 22:53:27 -070034 interpolation_table_shuttle_(
35 y2024::constants::Values::InterpolationTableFromFlatbuffer(
36 robot_constants_->common()
37 ->shooter_shuttle_interpolation_table())),
Maxwell Henderson6b1be312024-02-28 20:15:06 -080038 debouncer_(std::chrono::milliseconds(100), std::chrono::milliseconds(8)) {
39}
Niko Sohmersc4d2c502024-02-19 19:35:35 -080040
41flatbuffers::Offset<y2024::control_loops::superstructure::ShooterStatus>
42Shooter::Iterate(
43 const y2024::control_loops::superstructure::Position *position,
44 const y2024::control_loops::superstructure::ShooterGoal *shooter_goal,
Filip Kujawa7a799602024-02-23 12:27:47 -080045 bool fire, double *catapult_output, double *altitude_output,
46 double *turret_output, double *retention_roller_output,
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080047 double *retention_roller_stator_current_limit, double /*battery_voltage*/,
Austin Schuh027fd622024-03-01 21:26:07 -080048 CollisionAvoidance *collision_avoidance, const double extend_position,
49 const double extend_goal, double *max_extend_position,
50 double *min_extend_position, const double intake_pivot_position,
Niko Sohmersac4d8872024-02-23 13:55:47 -080051 double *max_intake_pivot_position, double *min_intake_pivot_position,
Stephan Pleines9f3983a2024-03-13 20:22:38 -070052 NoteGoal requested_note_goal, flatbuffers::FlatBufferBuilder *fbb,
James Kuszmaul42a87452024-04-05 17:30:47 -070053 aos::monotonic_clock::time_point monotonic_now, bool climbing) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -080054 drivetrain_status_fetcher_.Fetch();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080055
56 // If our current is over the minimum current and our velocity is under our
57 // maximum velocity, then set loaded to true. If we are preloaded set it to
58 // true as well.
Maxwell Henderson6b1be312024-02-28 20:15:06 -080059 debouncer_.Update(position->catapult_beambreak() ||
60 (shooter_goal != nullptr && shooter_goal->preloaded()),
61 monotonic_now);
62 const bool piece_loaded = debouncer_.state();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080063
64 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
65 frc971::control_loops::
66 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
67 turret_allocator;
68
69 aos::fbs::Builder<
70 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
71 turret_goal_builder(&turret_allocator);
72
73 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
74 frc971::control_loops::
75 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
Niko Sohmers6adb5b92024-03-16 17:47:54 -070076 auto_aim_allocator;
77
78 aos::fbs::Builder<
79 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
80 auto_aim_goal_builder(&auto_aim_allocator);
81
82 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
83 frc971::control_loops::
84 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
Niko Sohmersc4d2c502024-02-19 19:35:35 -080085 altitude_allocator;
86
87 aos::fbs::Builder<
88 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
89 altitude_goal_builder(&altitude_allocator);
90
91 const double distance_to_goal = aimer_.DistanceToGoal();
92
93 // Always retain the game piece if we are enabled.
94 if (retention_roller_output != nullptr) {
95 *retention_roller_output =
96 robot_constants_->common()->retention_roller_voltages()->retaining();
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080097
98 if (piece_loaded) {
99 *retention_roller_stator_current_limit =
100 robot_constants_->common()
101 ->current_limits()
102 ->slower_retention_roller_stator_current_limit();
103 } else {
104 *retention_roller_stator_current_limit =
105 robot_constants_->common()
106 ->current_limits()
107 ->retention_roller_stator_current_limit();
108 }
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800109 }
110
111 bool aiming = false;
112
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700113 if (requested_note_goal == NoteGoal::AMP ||
114 requested_note_goal == NoteGoal::TRAP) {
Stephan Pleines9f3983a2024-03-13 20:22:38 -0700115 // Being asked to amp, lift the altitude up.
116 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
117 turret_goal_builder.get(),
118 robot_constants_->common()->turret_loading_position());
119
120 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700121 altitude_goal_builder.get(),
122 robot_constants_->common()->altitude_avoid_extend_collision_position());
Filip Kujawa1286c012024-03-31 22:53:27 -0700123 } else if (shooter_goal == nullptr ||
124 (shooter_goal->auto_aim() == AutoAimMode::NONE) ||
Stephan Pleines9f3983a2024-03-13 20:22:38 -0700125 (!piece_loaded && state_ == CatapultState::READY)) {
Filip Kujawa7a799602024-02-23 12:27:47 -0800126 // We don't have the note so we should be ready to intake it.
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800127 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
128 turret_goal_builder.get(),
129 robot_constants_->common()->turret_loading_position());
130
131 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
132 altitude_goal_builder.get(),
133 robot_constants_->common()->altitude_loading_position());
134 } else {
135 // We have a game piece, lets start aiming.
136 if (drivetrain_status_fetcher_.get() != nullptr) {
137 aiming = true;
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800138 }
139 }
140
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700141 // Auto aim builder is a dummy so we get a status when we aren't aiming.
142 aimer_.Update(
143 drivetrain_status_fetcher_.get(),
144 frc971::control_loops::aiming::ShotMode::kShootOnTheFly,
Filip Kujawa1286c012024-03-31 22:53:27 -0700145 aiming ? turret_goal_builder.get() : auto_aim_goal_builder.get(),
146 shooter_goal != nullptr ? shooter_goal->auto_aim() : AutoAimMode::NONE);
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700147
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800148 // We have a game piece and are being asked to aim.
149 constants::Values::ShotParams shot_params;
Filip Kujawa1286c012024-03-31 22:53:27 -0700150 frc971::shooter_interpolation::InterpolationTable<
151 y2024::constants::Values::ShotParams> *interpolation_table =
152 (shooter_goal != nullptr &&
153 shooter_goal->auto_aim() == AutoAimMode::SHUTTLE)
154 ? &interpolation_table_shuttle_
155 : &interpolation_table_;
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700156 if ((piece_loaded || state_ == CatapultState::FIRING) &&
Filip Kujawa1286c012024-03-31 22:53:27 -0700157 shooter_goal != nullptr &&
158 (shooter_goal->auto_aim() != AutoAimMode::NONE) &&
159 interpolation_table->GetInRange(distance_to_goal, &shot_params)) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800160 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
161 altitude_goal_builder.get(), shot_params.shot_altitude_angle);
162 }
163
164 // The builder will contain either the auto-aim goal, or the loading goal. Use
165 // it if we have no goal, or no subsystem goal, or if we are auto-aiming.
Filip Kujawa7a799602024-02-23 12:27:47 -0800166
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800167 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
James Kuszmaul42a87452024-04-05 17:30:47 -0700168 *turret_goal =
Filip Kujawa1286c012024-03-31 22:53:27 -0700169 (shooter_goal != nullptr &&
170 (shooter_goal->auto_aim() == AutoAimMode::NONE) &&
James Kuszmaul42a87452024-04-05 17:30:47 -0700171 (piece_loaded || state_ == CatapultState::FIRING || climbing) &&
172 shooter_goal->has_turret_position())
173 ? shooter_goal->turret_position()
174 : &turret_goal_builder->AsFlatbuffer();
Filip Kujawa7a799602024-02-23 12:27:47 -0800175
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800176 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
Filip Kujawa1286c012024-03-31 22:53:27 -0700177 *altitude_goal = (shooter_goal != nullptr &&
178 (shooter_goal->auto_aim() == AutoAimMode::NONE) &&
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700179 (piece_loaded || state_ == CatapultState::FIRING) &&
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800180 shooter_goal->has_altitude_position())
181 ? shooter_goal->altitude_position()
182 : &altitude_goal_builder->AsFlatbuffer();
183
James Kuszmaulc27f2c92024-04-05 17:33:55 -0700184 // TODO(austin): goal limit...
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800185 const bool turret_in_range =
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800186 (std::abs(turret_.estimated_position() - turret_goal->unsafe_goal()) <
Maxwell Hendersonf9779a72024-03-24 12:52:01 -0700187 kCatapultActivationTurretThreshold) &&
James Kuszmaulc27f2c92024-04-05 17:33:55 -0700188 (std::abs(turret_goal->goal_velocity()) < 0.2) &&
189 (std::abs(turret_.estimated_velocity()) < 1.0);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800190 const bool altitude_in_range =
191 (std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
James Kuszmaulc27f2c92024-04-05 17:33:55 -0700192 (altitude_goal->unsafe_goal() > 0.75
193 ? (5 * kCatapultActivationAltitudeThreshold)
194 : kCatapultActivationAltitudeThreshold)) &&
195 (std::abs(altitude_.estimated_velocity()) < 0.4);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800196 const bool altitude_above_min_angle =
197 (altitude_.estimated_position() >
198 robot_constants_->common()->min_altitude_shooting_angle());
199
200 bool subsystems_in_range =
201 (turret_in_range && altitude_in_range && altitude_above_min_angle);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800202
Niko Sohmersac4d8872024-02-23 13:55:47 -0800203 const bool disabled = turret_.Correct(turret_goal, position->turret(),
204 turret_output == nullptr);
205
Niko Sohmers77bf1fd2024-03-16 23:22:57 -0700206 // Zero out extend goal and position if "disable_extend" is true
Niko Sohmersac4d8872024-02-23 13:55:47 -0800207 collision_avoidance->UpdateGoal(
208 {.intake_pivot_position = intake_pivot_position,
Austin Schuh027fd622024-03-01 21:26:07 -0800209 .turret_position = turret_.estimated_position(),
Niko Sohmers77bf1fd2024-03-16 23:22:57 -0700210 .extend_position =
Niko Sohmers58461f52024-03-20 20:12:10 -0700211 ((!robot_constants_->robot()->disable_extend()) ? extend_position
212 : 0.0)},
Niko Sohmers77bf1fd2024-03-16 23:22:57 -0700213 turret_goal->unsafe_goal(),
Niko Sohmers58461f52024-03-20 20:12:10 -0700214 ((!robot_constants_->robot()->disable_extend()) ? extend_goal : 0.0));
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700215
216 if (!CatapultRetracted()) {
217 altitude_.set_min_position(
218 robot_constants_->common()->min_altitude_shooting_angle());
219 } else {
220 altitude_.clear_min_position();
221 }
222
Niko Sohmersac4d8872024-02-23 13:55:47 -0800223 turret_.set_min_position(collision_avoidance->min_turret_goal());
224 turret_.set_max_position(collision_avoidance->max_turret_goal());
225
226 *max_intake_pivot_position = collision_avoidance->max_intake_pivot_goal();
227 *min_intake_pivot_position = collision_avoidance->min_intake_pivot_goal();
228
Austin Schuh027fd622024-03-01 21:26:07 -0800229 *max_extend_position = collision_avoidance->max_extend_goal();
230 *min_extend_position = collision_avoidance->min_extend_goal();
231
Niko Sohmersac4d8872024-02-23 13:55:47 -0800232 // Calculate the loops for a cycle.
233 const double voltage = turret_.UpdateController(disabled);
234
235 turret_.UpdateObserver(voltage);
236
237 // Write out all the voltages.
238 if (turret_output) {
239 *turret_output = voltage;
240 }
241
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800242 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Niko Sohmersac4d8872024-02-23 13:55:47 -0800243 turret_status_offset = turret_.MakeStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800244
245 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
246 altitude_status_offset = altitude_.Iterate(
247 altitude_goal, position->altitude(), altitude_output, fbb);
248
249 flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
250 catapult_status_offset;
251 {
252 // The catapult will never use a provided goal. We'll always fabricate one
253 // for it.
254 //
255 // Correct handles resetting our state when disabled.
256 const bool disabled = catapult_.Correct(nullptr, position->catapult(),
Maxwell Henderson3d68e142024-02-25 09:58:11 -0800257 catapult_output == nullptr);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800258
259 catapult_.set_enable_profile(true);
260 // We want a trajectory which accelerates up over the first portion of the
261 // range of motion, holds top speed for a little bit, then decelerates
262 // before it swings too far.
263 //
264 // We can solve for these 3 parameters through the range of motion. Top
265 // speed is goverened by the voltage headroom we want to have for the
266 // controller.
267 //
268 // Accel can be tuned given the distance to accelerate over, and decel can
269 // be solved similarly.
270 //
271 // accel = v^2 / (2 * x)
272 catapult_.mutable_profile()->set_maximum_velocity(
Austin Schuh7cea29d2024-03-17 18:20:14 -0700273 catapult::kFreeSpeed * catapult::kOutputRatio * 5.5 / 12.0);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800274
275 if (disabled) {
276 state_ = CatapultState::RETRACTING;
277 }
278
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700279 constexpr double kLoadingAcceleration = 40.0;
280 constexpr double kLoadingDeceleration = 20.0;
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800281
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800282 switch (state_) {
283 case CatapultState::READY:
Filip Kujawa7a799602024-02-23 12:27:47 -0800284 [[fallthrough]];
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800285 case CatapultState::LOADED: {
286 if (piece_loaded) {
287 state_ = CatapultState::LOADED;
288 } else {
289 state_ = CatapultState::READY;
290 }
291
292 const bool catapult_close = CatapultClose();
293
Filip Kujawa7a799602024-02-23 12:27:47 -0800294 if (subsystems_in_range && shooter_goal != nullptr && fire &&
295 catapult_close && piece_loaded) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800296 state_ = CatapultState::FIRING;
Austin Schuh7cea29d2024-03-17 18:20:14 -0700297 max_catapult_goal_velocity_ = catapult_.goal(1);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800298 } else {
299 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800300 catapult_.mutable_profile()->set_maximum_acceleration(
301 kLoadingAcceleration);
302 catapult_.mutable_profile()->set_maximum_deceleration(
303 kLoadingDeceleration);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800304 catapult_.set_unprofiled_goal(0.0, 0.0);
305
306 if (!catapult_close) {
307 state_ = CatapultState::RETRACTING;
308 }
309 break;
310 }
311 [[fallthrough]];
312 }
313 case CatapultState::FIRING:
314 *retention_roller_output =
315 robot_constants_->common()->retention_roller_voltages()->spitting();
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800316 *retention_roller_stator_current_limit =
317 robot_constants_->common()
318 ->current_limits()
319 ->shooting_retention_roller_stator_current_limit();
Austin Schuh7cea29d2024-03-17 18:20:14 -0700320 max_catapult_goal_velocity_ =
321 std::max(max_catapult_goal_velocity_, catapult_.goal(1));
322
323 if (max_catapult_goal_velocity_ > catapult_.goal(1) + 0.1) {
324 catapult_.set_controller_index(2);
325 } else {
326 catapult_.set_controller_index(1);
327 }
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800328 catapult_.mutable_profile()->set_maximum_acceleration(400.0);
Austin Schuh7cea29d2024-03-17 18:20:14 -0700329 catapult_.mutable_profile()->set_maximum_deceleration(1000.0);
330 catapult_.set_unprofiled_goal(2.45, 0.0);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800331 if (CatapultClose()) {
332 state_ = CatapultState::RETRACTING;
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700333 ++shot_count_;
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800334 } else {
335 break;
336 }
337 [[fallthrough]];
338 case CatapultState::RETRACTING:
339 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800340 catapult_.mutable_profile()->set_maximum_acceleration(
341 kLoadingAcceleration);
342 catapult_.mutable_profile()->set_maximum_deceleration(
343 kLoadingDeceleration);
344 // TODO: catapult_return_position
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800345 catapult_.set_unprofiled_goal(0.0, 0.0);
346
347 if (CatapultClose()) {
348 if (piece_loaded) {
349 state_ = CatapultState::LOADED;
350 } else {
351 state_ = CatapultState::READY;
352 }
353 }
354 break;
355 }
356
357 const double voltage = catapult_.UpdateController(disabled);
358 catapult_.UpdateObserver(voltage);
359 if (catapult_output != nullptr) {
360 *catapult_output = voltage;
361 }
362 catapult_status_offset = catapult_.MakeStatus(fbb);
363 }
364
365 flatbuffers::Offset<AimerStatus> aimer_offset;
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700366 aimer_offset = aimer_.PopulateStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800367
368 y2024::control_loops::superstructure::ShooterStatus::Builder status_builder(
369 *fbb);
370 status_builder.add_turret(turret_status_offset);
371 status_builder.add_altitude(altitude_status_offset);
372 status_builder.add_catapult(catapult_status_offset);
373 status_builder.add_catapult_state(state_);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800374 status_builder.add_turret_in_range(turret_in_range);
375 status_builder.add_altitude_in_range(altitude_in_range);
376 status_builder.add_altitude_above_min_angle(altitude_above_min_angle);
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700377 status_builder.add_auto_aiming(aiming);
378 status_builder.add_aimer(aimer_offset);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800379
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800380 return status_builder.Finish();
381}
382
383} // namespace y2024::control_loops::superstructure