blob: 1cd01912636c03ca73f03387ebea216b1fd43eec [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())),
34 debouncer_(std::chrono::milliseconds(100), std::chrono::milliseconds(8)) {
35}
Niko Sohmersc4d2c502024-02-19 19:35:35 -080036
37flatbuffers::Offset<y2024::control_loops::superstructure::ShooterStatus>
38Shooter::Iterate(
39 const y2024::control_loops::superstructure::Position *position,
40 const y2024::control_loops::superstructure::ShooterGoal *shooter_goal,
Filip Kujawa7a799602024-02-23 12:27:47 -080041 bool fire, double *catapult_output, double *altitude_output,
42 double *turret_output, double *retention_roller_output,
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080043 double *retention_roller_stator_current_limit, double /*battery_voltage*/,
Austin Schuh027fd622024-03-01 21:26:07 -080044 CollisionAvoidance *collision_avoidance, const double extend_position,
45 const double extend_goal, double *max_extend_position,
46 double *min_extend_position, const double intake_pivot_position,
Niko Sohmersac4d8872024-02-23 13:55:47 -080047 double *max_intake_pivot_position, double *min_intake_pivot_position,
Stephan Pleines9f3983a2024-03-13 20:22:38 -070048 NoteGoal requested_note_goal, flatbuffers::FlatBufferBuilder *fbb,
James Kuszmaul42a87452024-04-05 17:30:47 -070049 aos::monotonic_clock::time_point monotonic_now, bool climbing) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -080050 drivetrain_status_fetcher_.Fetch();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080051
52 // If our current is over the minimum current and our velocity is under our
53 // maximum velocity, then set loaded to true. If we are preloaded set it to
54 // true as well.
Maxwell Henderson6b1be312024-02-28 20:15:06 -080055 debouncer_.Update(position->catapult_beambreak() ||
56 (shooter_goal != nullptr && shooter_goal->preloaded()),
57 monotonic_now);
58 const bool piece_loaded = debouncer_.state();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080059
60 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
61 frc971::control_loops::
62 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
63 turret_allocator;
64
65 aos::fbs::Builder<
66 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
67 turret_goal_builder(&turret_allocator);
68
69 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
70 frc971::control_loops::
71 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
Niko Sohmers6adb5b92024-03-16 17:47:54 -070072 auto_aim_allocator;
73
74 aos::fbs::Builder<
75 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
76 auto_aim_goal_builder(&auto_aim_allocator);
77
78 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
79 frc971::control_loops::
80 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
Niko Sohmersc4d2c502024-02-19 19:35:35 -080081 altitude_allocator;
82
83 aos::fbs::Builder<
84 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
85 altitude_goal_builder(&altitude_allocator);
86
87 const double distance_to_goal = aimer_.DistanceToGoal();
88
89 // Always retain the game piece if we are enabled.
90 if (retention_roller_output != nullptr) {
91 *retention_roller_output =
92 robot_constants_->common()->retention_roller_voltages()->retaining();
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080093
94 if (piece_loaded) {
95 *retention_roller_stator_current_limit =
96 robot_constants_->common()
97 ->current_limits()
98 ->slower_retention_roller_stator_current_limit();
99 } else {
100 *retention_roller_stator_current_limit =
101 robot_constants_->common()
102 ->current_limits()
103 ->retention_roller_stator_current_limit();
104 }
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800105 }
106
107 bool aiming = false;
108
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700109 if (requested_note_goal == NoteGoal::AMP ||
110 requested_note_goal == NoteGoal::TRAP) {
Stephan Pleines9f3983a2024-03-13 20:22:38 -0700111 // Being asked to amp, lift the altitude up.
112 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
113 turret_goal_builder.get(),
114 robot_constants_->common()->turret_loading_position());
115
116 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700117 altitude_goal_builder.get(),
118 robot_constants_->common()->altitude_avoid_extend_collision_position());
Stephan Pleines9f3983a2024-03-13 20:22:38 -0700119 } else if (shooter_goal == nullptr || !shooter_goal->auto_aim() ||
120 (!piece_loaded && state_ == CatapultState::READY)) {
Filip Kujawa7a799602024-02-23 12:27:47 -0800121 // We don't have the note so we should be ready to intake it.
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800122 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
123 turret_goal_builder.get(),
124 robot_constants_->common()->turret_loading_position());
125
126 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
127 altitude_goal_builder.get(),
128 robot_constants_->common()->altitude_loading_position());
129 } else {
130 // We have a game piece, lets start aiming.
131 if (drivetrain_status_fetcher_.get() != nullptr) {
132 aiming = true;
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800133 }
134 }
135
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700136 // Auto aim builder is a dummy so we get a status when we aren't aiming.
137 aimer_.Update(
138 drivetrain_status_fetcher_.get(),
139 frc971::control_loops::aiming::ShotMode::kShootOnTheFly,
140 aiming ? turret_goal_builder.get() : auto_aim_goal_builder.get());
141
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800142 // We have a game piece and are being asked to aim.
143 constants::Values::ShotParams shot_params;
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700144 if ((piece_loaded || state_ == CatapultState::FIRING) &&
145 shooter_goal != nullptr && shooter_goal->auto_aim() &&
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800146 interpolation_table_.GetInRange(distance_to_goal, &shot_params)) {
147 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
148 altitude_goal_builder.get(), shot_params.shot_altitude_angle);
149 }
150
151 // The builder will contain either the auto-aim goal, or the loading goal. Use
152 // it if we have no goal, or no subsystem goal, or if we are auto-aiming.
Filip Kujawa7a799602024-02-23 12:27:47 -0800153
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800154 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
James Kuszmaul42a87452024-04-05 17:30:47 -0700155 *turret_goal =
156 (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
157 (piece_loaded || state_ == CatapultState::FIRING || climbing) &&
158 shooter_goal->has_turret_position())
159 ? shooter_goal->turret_position()
160 : &turret_goal_builder->AsFlatbuffer();
Filip Kujawa7a799602024-02-23 12:27:47 -0800161
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800162 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
163 *altitude_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700164 (piece_loaded || state_ == CatapultState::FIRING) &&
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800165 shooter_goal->has_altitude_position())
166 ? shooter_goal->altitude_position()
167 : &altitude_goal_builder->AsFlatbuffer();
168
James Kuszmaulc27f2c92024-04-05 17:33:55 -0700169 // TODO(austin): goal limit...
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800170 const bool turret_in_range =
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800171 (std::abs(turret_.estimated_position() - turret_goal->unsafe_goal()) <
Maxwell Hendersonf9779a72024-03-24 12:52:01 -0700172 kCatapultActivationTurretThreshold) &&
James Kuszmaulc27f2c92024-04-05 17:33:55 -0700173 (std::abs(turret_goal->goal_velocity()) < 0.2) &&
174 (std::abs(turret_.estimated_velocity()) < 1.0);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800175 const bool altitude_in_range =
176 (std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
James Kuszmaulc27f2c92024-04-05 17:33:55 -0700177 (altitude_goal->unsafe_goal() > 0.75
178 ? (5 * kCatapultActivationAltitudeThreshold)
179 : kCatapultActivationAltitudeThreshold)) &&
180 (std::abs(altitude_.estimated_velocity()) < 0.4);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800181 const bool altitude_above_min_angle =
182 (altitude_.estimated_position() >
183 robot_constants_->common()->min_altitude_shooting_angle());
184
185 bool subsystems_in_range =
186 (turret_in_range && altitude_in_range && altitude_above_min_angle);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800187
Niko Sohmersac4d8872024-02-23 13:55:47 -0800188 const bool disabled = turret_.Correct(turret_goal, position->turret(),
189 turret_output == nullptr);
190
Niko Sohmers77bf1fd2024-03-16 23:22:57 -0700191 // Zero out extend goal and position if "disable_extend" is true
Niko Sohmersac4d8872024-02-23 13:55:47 -0800192 collision_avoidance->UpdateGoal(
193 {.intake_pivot_position = intake_pivot_position,
Austin Schuh027fd622024-03-01 21:26:07 -0800194 .turret_position = turret_.estimated_position(),
Niko Sohmers77bf1fd2024-03-16 23:22:57 -0700195 .extend_position =
Niko Sohmers58461f52024-03-20 20:12:10 -0700196 ((!robot_constants_->robot()->disable_extend()) ? extend_position
197 : 0.0)},
Niko Sohmers77bf1fd2024-03-16 23:22:57 -0700198 turret_goal->unsafe_goal(),
Niko Sohmers58461f52024-03-20 20:12:10 -0700199 ((!robot_constants_->robot()->disable_extend()) ? extend_goal : 0.0));
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700200
201 if (!CatapultRetracted()) {
202 altitude_.set_min_position(
203 robot_constants_->common()->min_altitude_shooting_angle());
204 } else {
205 altitude_.clear_min_position();
206 }
207
Niko Sohmersac4d8872024-02-23 13:55:47 -0800208 turret_.set_min_position(collision_avoidance->min_turret_goal());
209 turret_.set_max_position(collision_avoidance->max_turret_goal());
210
211 *max_intake_pivot_position = collision_avoidance->max_intake_pivot_goal();
212 *min_intake_pivot_position = collision_avoidance->min_intake_pivot_goal();
213
Austin Schuh027fd622024-03-01 21:26:07 -0800214 *max_extend_position = collision_avoidance->max_extend_goal();
215 *min_extend_position = collision_avoidance->min_extend_goal();
216
Niko Sohmersac4d8872024-02-23 13:55:47 -0800217 // Calculate the loops for a cycle.
218 const double voltage = turret_.UpdateController(disabled);
219
220 turret_.UpdateObserver(voltage);
221
222 // Write out all the voltages.
223 if (turret_output) {
224 *turret_output = voltage;
225 }
226
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800227 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Niko Sohmersac4d8872024-02-23 13:55:47 -0800228 turret_status_offset = turret_.MakeStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800229
230 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
231 altitude_status_offset = altitude_.Iterate(
232 altitude_goal, position->altitude(), altitude_output, fbb);
233
234 flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
235 catapult_status_offset;
236 {
237 // The catapult will never use a provided goal. We'll always fabricate one
238 // for it.
239 //
240 // Correct handles resetting our state when disabled.
241 const bool disabled = catapult_.Correct(nullptr, position->catapult(),
Maxwell Henderson3d68e142024-02-25 09:58:11 -0800242 catapult_output == nullptr);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800243
244 catapult_.set_enable_profile(true);
245 // We want a trajectory which accelerates up over the first portion of the
246 // range of motion, holds top speed for a little bit, then decelerates
247 // before it swings too far.
248 //
249 // We can solve for these 3 parameters through the range of motion. Top
250 // speed is goverened by the voltage headroom we want to have for the
251 // controller.
252 //
253 // Accel can be tuned given the distance to accelerate over, and decel can
254 // be solved similarly.
255 //
256 // accel = v^2 / (2 * x)
257 catapult_.mutable_profile()->set_maximum_velocity(
Austin Schuh7cea29d2024-03-17 18:20:14 -0700258 catapult::kFreeSpeed * catapult::kOutputRatio * 5.5 / 12.0);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800259
260 if (disabled) {
261 state_ = CatapultState::RETRACTING;
262 }
263
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700264 constexpr double kLoadingAcceleration = 40.0;
265 constexpr double kLoadingDeceleration = 20.0;
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800266
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800267 switch (state_) {
268 case CatapultState::READY:
Filip Kujawa7a799602024-02-23 12:27:47 -0800269 [[fallthrough]];
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800270 case CatapultState::LOADED: {
271 if (piece_loaded) {
272 state_ = CatapultState::LOADED;
273 } else {
274 state_ = CatapultState::READY;
275 }
276
277 const bool catapult_close = CatapultClose();
278
Filip Kujawa7a799602024-02-23 12:27:47 -0800279 if (subsystems_in_range && shooter_goal != nullptr && fire &&
280 catapult_close && piece_loaded) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800281 state_ = CatapultState::FIRING;
Austin Schuh7cea29d2024-03-17 18:20:14 -0700282 max_catapult_goal_velocity_ = catapult_.goal(1);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800283 } else {
284 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800285 catapult_.mutable_profile()->set_maximum_acceleration(
286 kLoadingAcceleration);
287 catapult_.mutable_profile()->set_maximum_deceleration(
288 kLoadingDeceleration);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800289 catapult_.set_unprofiled_goal(0.0, 0.0);
290
291 if (!catapult_close) {
292 state_ = CatapultState::RETRACTING;
293 }
294 break;
295 }
296 [[fallthrough]];
297 }
298 case CatapultState::FIRING:
299 *retention_roller_output =
300 robot_constants_->common()->retention_roller_voltages()->spitting();
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800301 *retention_roller_stator_current_limit =
302 robot_constants_->common()
303 ->current_limits()
304 ->shooting_retention_roller_stator_current_limit();
Austin Schuh7cea29d2024-03-17 18:20:14 -0700305 max_catapult_goal_velocity_ =
306 std::max(max_catapult_goal_velocity_, catapult_.goal(1));
307
308 if (max_catapult_goal_velocity_ > catapult_.goal(1) + 0.1) {
309 catapult_.set_controller_index(2);
310 } else {
311 catapult_.set_controller_index(1);
312 }
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800313 catapult_.mutable_profile()->set_maximum_acceleration(400.0);
Austin Schuh7cea29d2024-03-17 18:20:14 -0700314 catapult_.mutable_profile()->set_maximum_deceleration(1000.0);
315 catapult_.set_unprofiled_goal(2.45, 0.0);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800316 if (CatapultClose()) {
317 state_ = CatapultState::RETRACTING;
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700318 ++shot_count_;
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800319 } else {
320 break;
321 }
322 [[fallthrough]];
323 case CatapultState::RETRACTING:
324 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800325 catapult_.mutable_profile()->set_maximum_acceleration(
326 kLoadingAcceleration);
327 catapult_.mutable_profile()->set_maximum_deceleration(
328 kLoadingDeceleration);
329 // TODO: catapult_return_position
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800330 catapult_.set_unprofiled_goal(0.0, 0.0);
331
332 if (CatapultClose()) {
333 if (piece_loaded) {
334 state_ = CatapultState::LOADED;
335 } else {
336 state_ = CatapultState::READY;
337 }
338 }
339 break;
340 }
341
342 const double voltage = catapult_.UpdateController(disabled);
343 catapult_.UpdateObserver(voltage);
344 if (catapult_output != nullptr) {
345 *catapult_output = voltage;
346 }
347 catapult_status_offset = catapult_.MakeStatus(fbb);
348 }
349
350 flatbuffers::Offset<AimerStatus> aimer_offset;
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700351 aimer_offset = aimer_.PopulateStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800352
353 y2024::control_loops::superstructure::ShooterStatus::Builder status_builder(
354 *fbb);
355 status_builder.add_turret(turret_status_offset);
356 status_builder.add_altitude(altitude_status_offset);
357 status_builder.add_catapult(catapult_status_offset);
358 status_builder.add_catapult_state(state_);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800359 status_builder.add_turret_in_range(turret_in_range);
360 status_builder.add_altitude_in_range(altitude_in_range);
361 status_builder.add_altitude_above_min_angle(altitude_above_min_angle);
Niko Sohmers6adb5b92024-03-16 17:47:54 -0700362 status_builder.add_auto_aiming(aiming);
363 status_builder.add_aimer(aimer_offset);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800364
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800365 return status_builder.Finish();
366}
367
368} // namespace y2024::control_loops::superstructure