blob: 7fb89735dcf8d65f3018fd244dd8b074d88a4da7 [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 =
James Kuszmaul73d68882024-04-07 21:26:17 -070096 robot_constants_->common()->retention_roller_voltages()->intaking();
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080097
98 if (piece_loaded) {
James Kuszmaul73d68882024-04-07 21:26:17 -070099 *retention_roller_output =
100 robot_constants_->common()->retention_roller_voltages()->retaining();
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -0800101 *retention_roller_stator_current_limit =
102 robot_constants_->common()
103 ->current_limits()
104 ->slower_retention_roller_stator_current_limit();
105 } else {
106 *retention_roller_stator_current_limit =
107 robot_constants_->common()
108 ->current_limits()
109 ->retention_roller_stator_current_limit();
110 }
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800111 }
112
113 bool aiming = false;
114
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700115 if (requested_note_goal == NoteGoal::AMP ||
116 requested_note_goal == NoteGoal::TRAP) {
Stephan Pleines9f3983a2024-03-13 20:22:38 -0700117 // Being asked to amp, lift the altitude up.
118 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
119 turret_goal_builder.get(),
120 robot_constants_->common()->turret_loading_position());
121
122 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700123 altitude_goal_builder.get(),
124 robot_constants_->common()->altitude_avoid_extend_collision_position());
Filip Kujawa1286c012024-03-31 22:53:27 -0700125 } else if (shooter_goal == nullptr ||
126 (shooter_goal->auto_aim() == AutoAimMode::NONE) ||
Stephan Pleines9f3983a2024-03-13 20:22:38 -0700127 (!piece_loaded && state_ == CatapultState::READY)) {
Filip Kujawa7a799602024-02-23 12:27:47 -0800128 // We don't have the note so we should be ready to intake it.
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800129 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
130 turret_goal_builder.get(),
131 robot_constants_->common()->turret_loading_position());
132
133 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
134 altitude_goal_builder.get(),
135 robot_constants_->common()->altitude_loading_position());
136 } else {
137 // We have a game piece, lets start aiming.
138 if (drivetrain_status_fetcher_.get() != nullptr) {
139 aiming = true;
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800140 }
141 }
142
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700143 // Auto aim builder is a dummy so we get a status when we aren't aiming.
144 aimer_.Update(
145 drivetrain_status_fetcher_.get(),
146 frc971::control_loops::aiming::ShotMode::kShootOnTheFly,
Filip Kujawa1286c012024-03-31 22:53:27 -0700147 aiming ? turret_goal_builder.get() : auto_aim_goal_builder.get(),
148 shooter_goal != nullptr ? shooter_goal->auto_aim() : AutoAimMode::NONE);
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700149
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800150 // We have a game piece and are being asked to aim.
151 constants::Values::ShotParams shot_params;
Filip Kujawa1286c012024-03-31 22:53:27 -0700152 frc971::shooter_interpolation::InterpolationTable<
153 y2024::constants::Values::ShotParams> *interpolation_table =
154 (shooter_goal != nullptr &&
155 shooter_goal->auto_aim() == AutoAimMode::SHUTTLE)
156 ? &interpolation_table_shuttle_
157 : &interpolation_table_;
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700158 if ((piece_loaded || state_ == CatapultState::FIRING) &&
Filip Kujawa1286c012024-03-31 22:53:27 -0700159 shooter_goal != nullptr &&
160 (shooter_goal->auto_aim() != AutoAimMode::NONE) &&
161 interpolation_table->GetInRange(distance_to_goal, &shot_params)) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800162 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
163 altitude_goal_builder.get(), shot_params.shot_altitude_angle);
164 }
165
166 // The builder will contain either the auto-aim goal, or the loading goal. Use
167 // it if we have no goal, or no subsystem goal, or if we are auto-aiming.
Filip Kujawa7a799602024-02-23 12:27:47 -0800168
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800169 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
James Kuszmaul42a87452024-04-05 17:30:47 -0700170 *turret_goal =
Filip Kujawa1286c012024-03-31 22:53:27 -0700171 (shooter_goal != nullptr &&
172 (shooter_goal->auto_aim() == AutoAimMode::NONE) &&
James Kuszmaul42a87452024-04-05 17:30:47 -0700173 (piece_loaded || state_ == CatapultState::FIRING || climbing) &&
174 shooter_goal->has_turret_position())
175 ? shooter_goal->turret_position()
176 : &turret_goal_builder->AsFlatbuffer();
Filip Kujawa7a799602024-02-23 12:27:47 -0800177
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800178 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
Filip Kujawa1286c012024-03-31 22:53:27 -0700179 *altitude_goal = (shooter_goal != nullptr &&
180 (shooter_goal->auto_aim() == AutoAimMode::NONE) &&
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700181 (piece_loaded || state_ == CatapultState::FIRING) &&
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800182 shooter_goal->has_altitude_position())
183 ? shooter_goal->altitude_position()
184 : &altitude_goal_builder->AsFlatbuffer();
185
James Kuszmaulc27f2c92024-04-05 17:33:55 -0700186 // TODO(austin): goal limit...
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800187 const bool turret_in_range =
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800188 (std::abs(turret_.estimated_position() - turret_goal->unsafe_goal()) <
Maxwell Hendersonf9779a72024-03-24 12:52:01 -0700189 kCatapultActivationTurretThreshold) &&
James Kuszmaulc27f2c92024-04-05 17:33:55 -0700190 (std::abs(turret_goal->goal_velocity()) < 0.2) &&
191 (std::abs(turret_.estimated_velocity()) < 1.0);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800192 const bool altitude_in_range =
193 (std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
James Kuszmaulc27f2c92024-04-05 17:33:55 -0700194 (altitude_goal->unsafe_goal() > 0.75
195 ? (5 * kCatapultActivationAltitudeThreshold)
196 : kCatapultActivationAltitudeThreshold)) &&
197 (std::abs(altitude_.estimated_velocity()) < 0.4);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800198 const bool altitude_above_min_angle =
199 (altitude_.estimated_position() >
200 robot_constants_->common()->min_altitude_shooting_angle());
201
202 bool subsystems_in_range =
203 (turret_in_range && altitude_in_range && altitude_above_min_angle);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800204
Niko Sohmersac4d8872024-02-23 13:55:47 -0800205 const bool disabled = turret_.Correct(turret_goal, position->turret(),
206 turret_output == nullptr);
207
Niko Sohmers77bf1fd2024-03-16 23:22:57 -0700208 // Zero out extend goal and position if "disable_extend" is true
Niko Sohmersac4d8872024-02-23 13:55:47 -0800209 collision_avoidance->UpdateGoal(
210 {.intake_pivot_position = intake_pivot_position,
Austin Schuh027fd622024-03-01 21:26:07 -0800211 .turret_position = turret_.estimated_position(),
Niko Sohmers77bf1fd2024-03-16 23:22:57 -0700212 .extend_position =
Niko Sohmers58461f52024-03-20 20:12:10 -0700213 ((!robot_constants_->robot()->disable_extend()) ? extend_position
214 : 0.0)},
Niko Sohmers77bf1fd2024-03-16 23:22:57 -0700215 turret_goal->unsafe_goal(),
Niko Sohmers58461f52024-03-20 20:12:10 -0700216 ((!robot_constants_->robot()->disable_extend()) ? extend_goal : 0.0));
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700217
218 if (!CatapultRetracted()) {
219 altitude_.set_min_position(
220 robot_constants_->common()->min_altitude_shooting_angle());
221 } else {
222 altitude_.clear_min_position();
223 }
224
Niko Sohmersac4d8872024-02-23 13:55:47 -0800225 turret_.set_min_position(collision_avoidance->min_turret_goal());
226 turret_.set_max_position(collision_avoidance->max_turret_goal());
227
228 *max_intake_pivot_position = collision_avoidance->max_intake_pivot_goal();
229 *min_intake_pivot_position = collision_avoidance->min_intake_pivot_goal();
230
Austin Schuh027fd622024-03-01 21:26:07 -0800231 *max_extend_position = collision_avoidance->max_extend_goal();
232 *min_extend_position = collision_avoidance->min_extend_goal();
233
Niko Sohmersac4d8872024-02-23 13:55:47 -0800234 // Calculate the loops for a cycle.
235 const double voltage = turret_.UpdateController(disabled);
236
237 turret_.UpdateObserver(voltage);
238
239 // Write out all the voltages.
240 if (turret_output) {
241 *turret_output = voltage;
242 }
243
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800244 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Niko Sohmersac4d8872024-02-23 13:55:47 -0800245 turret_status_offset = turret_.MakeStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800246
247 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
248 altitude_status_offset = altitude_.Iterate(
249 altitude_goal, position->altitude(), altitude_output, fbb);
250
251 flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
252 catapult_status_offset;
253 {
254 // The catapult will never use a provided goal. We'll always fabricate one
255 // for it.
256 //
257 // Correct handles resetting our state when disabled.
258 const bool disabled = catapult_.Correct(nullptr, position->catapult(),
Maxwell Henderson3d68e142024-02-25 09:58:11 -0800259 catapult_output == nullptr);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800260
261 catapult_.set_enable_profile(true);
262 // We want a trajectory which accelerates up over the first portion of the
263 // range of motion, holds top speed for a little bit, then decelerates
264 // before it swings too far.
265 //
266 // We can solve for these 3 parameters through the range of motion. Top
267 // speed is goverened by the voltage headroom we want to have for the
268 // controller.
269 //
270 // Accel can be tuned given the distance to accelerate over, and decel can
271 // be solved similarly.
272 //
273 // accel = v^2 / (2 * x)
274 catapult_.mutable_profile()->set_maximum_velocity(
Austin Schuh7cea29d2024-03-17 18:20:14 -0700275 catapult::kFreeSpeed * catapult::kOutputRatio * 5.5 / 12.0);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800276
277 if (disabled) {
278 state_ = CatapultState::RETRACTING;
279 }
280
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700281 constexpr double kLoadingAcceleration = 40.0;
282 constexpr double kLoadingDeceleration = 20.0;
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800283
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800284 switch (state_) {
285 case CatapultState::READY:
Filip Kujawa7a799602024-02-23 12:27:47 -0800286 [[fallthrough]];
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800287 case CatapultState::LOADED: {
James Kuszmaul73d68882024-04-07 21:26:17 -0700288 aimer_.latch_note_current(false);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800289 if (piece_loaded) {
290 state_ = CatapultState::LOADED;
291 } else {
James Kuszmaul73d68882024-04-07 21:26:17 -0700292 aimer_.IndicateReady();
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800293 state_ = CatapultState::READY;
294 }
295
296 const bool catapult_close = CatapultClose();
297
Filip Kujawa7a799602024-02-23 12:27:47 -0800298 if (subsystems_in_range && shooter_goal != nullptr && fire &&
299 catapult_close && piece_loaded) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800300 state_ = CatapultState::FIRING;
Austin Schuh7cea29d2024-03-17 18:20:14 -0700301 max_catapult_goal_velocity_ = catapult_.goal(1);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800302 } else {
303 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800304 catapult_.mutable_profile()->set_maximum_acceleration(
305 kLoadingAcceleration);
306 catapult_.mutable_profile()->set_maximum_deceleration(
307 kLoadingDeceleration);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800308 catapult_.set_unprofiled_goal(0.0, 0.0);
309
310 if (!catapult_close) {
311 state_ = CatapultState::RETRACTING;
312 }
313 break;
314 }
315 [[fallthrough]];
316 }
317 case CatapultState::FIRING:
James Kuszmaul73d68882024-04-07 21:26:17 -0700318 aimer_.latch_note_current(true);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800319 *retention_roller_output =
320 robot_constants_->common()->retention_roller_voltages()->spitting();
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800321 *retention_roller_stator_current_limit =
322 robot_constants_->common()
323 ->current_limits()
324 ->shooting_retention_roller_stator_current_limit();
Austin Schuh7cea29d2024-03-17 18:20:14 -0700325 max_catapult_goal_velocity_ =
326 std::max(max_catapult_goal_velocity_, catapult_.goal(1));
327
328 if (max_catapult_goal_velocity_ > catapult_.goal(1) + 0.1) {
329 catapult_.set_controller_index(2);
330 } else {
331 catapult_.set_controller_index(1);
332 }
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800333 catapult_.mutable_profile()->set_maximum_acceleration(400.0);
Austin Schuh7cea29d2024-03-17 18:20:14 -0700334 catapult_.mutable_profile()->set_maximum_deceleration(1000.0);
335 catapult_.set_unprofiled_goal(2.45, 0.0);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800336 if (CatapultClose()) {
337 state_ = CatapultState::RETRACTING;
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700338 ++shot_count_;
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800339 } else {
340 break;
341 }
342 [[fallthrough]];
343 case CatapultState::RETRACTING:
James Kuszmaul73d68882024-04-07 21:26:17 -0700344 aimer_.latch_note_current(false);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800345 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800346 catapult_.mutable_profile()->set_maximum_acceleration(
347 kLoadingAcceleration);
348 catapult_.mutable_profile()->set_maximum_deceleration(
349 kLoadingDeceleration);
350 // TODO: catapult_return_position
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800351 catapult_.set_unprofiled_goal(0.0, 0.0);
352
353 if (CatapultClose()) {
354 if (piece_loaded) {
355 state_ = CatapultState::LOADED;
356 } else {
357 state_ = CatapultState::READY;
358 }
359 }
360 break;
361 }
362
363 const double voltage = catapult_.UpdateController(disabled);
364 catapult_.UpdateObserver(voltage);
365 if (catapult_output != nullptr) {
366 *catapult_output = voltage;
367 }
368 catapult_status_offset = catapult_.MakeStatus(fbb);
369 }
370
371 flatbuffers::Offset<AimerStatus> aimer_offset;
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700372 aimer_offset = aimer_.PopulateStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800373
374 y2024::control_loops::superstructure::ShooterStatus::Builder status_builder(
375 *fbb);
376 status_builder.add_turret(turret_status_offset);
377 status_builder.add_altitude(altitude_status_offset);
378 status_builder.add_catapult(catapult_status_offset);
379 status_builder.add_catapult_state(state_);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800380 status_builder.add_turret_in_range(turret_in_range);
381 status_builder.add_altitude_in_range(altitude_in_range);
382 status_builder.add_altitude_above_min_angle(altitude_above_min_angle);
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700383 status_builder.add_auto_aiming(aiming);
384 status_builder.add_aimer(aimer_offset);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800385
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800386 return status_builder.Finish();
387}
388
389} // namespace y2024::control_loops::superstructure