blob: 7fdf748f98b9a82985bf9bb6652557a311cbd79b [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 Sohmersc4d2c502024-02-19 19:35:35 -080013constexpr double kCatapultActivationThreshold = 0.01;
14
15Shooter::Shooter(aos::EventLoop *event_loop, const Constants *robot_constants)
16 : drivetrain_status_fetcher_(
17 event_loop->MakeFetcher<frc971::control_loops::drivetrain::Status>(
18 "/drivetrain")),
Niko Sohmersc4d2c502024-02-19 19:35:35 -080019 robot_constants_(robot_constants),
20 catapult_(
21 robot_constants->common()->catapult(),
22 robot_constants->robot()->catapult_constants()->zeroing_constants()),
23 turret_(
24 robot_constants_->common()->turret(),
25 robot_constants_->robot()->turret_constants()->zeroing_constants()),
26 altitude_(
27 robot_constants_->common()->altitude(),
28 robot_constants_->robot()->altitude_constants()->zeroing_constants()),
29 aimer_(event_loop, robot_constants_),
30 interpolation_table_(
31 y2024::constants::Values::InterpolationTableFromFlatbuffer(
Maxwell Henderson6b1be312024-02-28 20:15:06 -080032 robot_constants_->common()->shooter_interpolation_table())),
33 debouncer_(std::chrono::milliseconds(100), std::chrono::milliseconds(8)) {
34}
Niko Sohmersc4d2c502024-02-19 19:35:35 -080035
36flatbuffers::Offset<y2024::control_loops::superstructure::ShooterStatus>
37Shooter::Iterate(
38 const y2024::control_loops::superstructure::Position *position,
39 const y2024::control_loops::superstructure::ShooterGoal *shooter_goal,
Filip Kujawa7a799602024-02-23 12:27:47 -080040 bool fire, double *catapult_output, double *altitude_output,
41 double *turret_output, double *retention_roller_output,
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080042 double *retention_roller_stator_current_limit, double /*battery_voltage*/,
Austin Schuh027fd622024-03-01 21:26:07 -080043 CollisionAvoidance *collision_avoidance, const double extend_position,
44 const double extend_goal, double *max_extend_position,
45 double *min_extend_position, const double intake_pivot_position,
Niko Sohmersac4d8872024-02-23 13:55:47 -080046 double *max_intake_pivot_position, double *min_intake_pivot_position,
Stephan Pleines9f3983a2024-03-13 20:22:38 -070047 NoteGoal requested_note_goal, flatbuffers::FlatBufferBuilder *fbb,
Maxwell Henderson6b1be312024-02-28 20:15:06 -080048 aos::monotonic_clock::time_point monotonic_now) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -080049 drivetrain_status_fetcher_.Fetch();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080050
51 // If our current is over the minimum current and our velocity is under our
52 // maximum velocity, then set loaded to true. If we are preloaded set it to
53 // true as well.
Maxwell Henderson6b1be312024-02-28 20:15:06 -080054 debouncer_.Update(position->catapult_beambreak() ||
55 (shooter_goal != nullptr && shooter_goal->preloaded()),
56 monotonic_now);
57 const bool piece_loaded = debouncer_.state();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080058
59 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
60 frc971::control_loops::
61 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
62 turret_allocator;
63
64 aos::fbs::Builder<
65 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
66 turret_goal_builder(&turret_allocator);
67
68 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
69 frc971::control_loops::
70 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
71 altitude_allocator;
72
73 aos::fbs::Builder<
74 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
75 altitude_goal_builder(&altitude_allocator);
76
77 const double distance_to_goal = aimer_.DistanceToGoal();
78
79 // Always retain the game piece if we are enabled.
80 if (retention_roller_output != nullptr) {
81 *retention_roller_output =
82 robot_constants_->common()->retention_roller_voltages()->retaining();
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080083
84 if (piece_loaded) {
85 *retention_roller_stator_current_limit =
86 robot_constants_->common()
87 ->current_limits()
88 ->slower_retention_roller_stator_current_limit();
89 } else {
90 *retention_roller_stator_current_limit =
91 robot_constants_->common()
92 ->current_limits()
93 ->retention_roller_stator_current_limit();
94 }
Niko Sohmersc4d2c502024-02-19 19:35:35 -080095 }
96
97 bool aiming = false;
98
Stephan Pleines9f3983a2024-03-13 20:22:38 -070099 if (requested_note_goal == NoteGoal::AMP) {
100 // Being asked to amp, lift the altitude up.
101 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
102 turret_goal_builder.get(),
103 robot_constants_->common()->turret_loading_position());
104
105 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
106 altitude_goal_builder.get(), 0.3);
107 } else if (shooter_goal == nullptr || !shooter_goal->auto_aim() ||
108 (!piece_loaded && state_ == CatapultState::READY)) {
Filip Kujawa7a799602024-02-23 12:27:47 -0800109 // We don't have the note so we should be ready to intake it.
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800110 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
111 turret_goal_builder.get(),
112 robot_constants_->common()->turret_loading_position());
113
114 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
115 altitude_goal_builder.get(),
116 robot_constants_->common()->altitude_loading_position());
117 } else {
118 // We have a game piece, lets start aiming.
119 if (drivetrain_status_fetcher_.get() != nullptr) {
120 aiming = true;
121 aimer_.Update(drivetrain_status_fetcher_.get(),
122 frc971::control_loops::aiming::ShotMode::kShootOnTheFly,
123 turret_goal_builder.get());
124 }
125 }
126
127 // We have a game piece and are being asked to aim.
128 constants::Values::ShotParams shot_params;
129 if (piece_loaded && shooter_goal != nullptr && shooter_goal->auto_aim() &&
130 interpolation_table_.GetInRange(distance_to_goal, &shot_params)) {
131 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
132 altitude_goal_builder.get(), shot_params.shot_altitude_angle);
133 }
134
135 // The builder will contain either the auto-aim goal, or the loading goal. Use
136 // it if we have no goal, or no subsystem goal, or if we are auto-aiming.
Filip Kujawa7a799602024-02-23 12:27:47 -0800137
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800138 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
139 *turret_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
Austin Schuh027fd622024-03-01 21:26:07 -0800140 shooter_goal->has_turret_position())
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800141 ? shooter_goal->turret_position()
142 : &turret_goal_builder->AsFlatbuffer();
Filip Kujawa7a799602024-02-23 12:27:47 -0800143
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800144 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
145 *altitude_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
146 shooter_goal->has_altitude_position())
147 ? shooter_goal->altitude_position()
148 : &altitude_goal_builder->AsFlatbuffer();
149
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800150 const bool turret_in_range =
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800151 (std::abs(turret_.estimated_position() - turret_goal->unsafe_goal()) <
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800152 kCatapultActivationThreshold);
153 const bool altitude_in_range =
154 (std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
155 kCatapultActivationThreshold);
156 const bool altitude_above_min_angle =
157 (altitude_.estimated_position() >
158 robot_constants_->common()->min_altitude_shooting_angle());
159
160 bool subsystems_in_range =
161 (turret_in_range && altitude_in_range && altitude_above_min_angle);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800162
Niko Sohmersac4d8872024-02-23 13:55:47 -0800163 const bool disabled = turret_.Correct(turret_goal, position->turret(),
164 turret_output == nullptr);
165
166 collision_avoidance->UpdateGoal(
167 {.intake_pivot_position = intake_pivot_position,
Austin Schuh027fd622024-03-01 21:26:07 -0800168 .turret_position = turret_.estimated_position(),
169 .extend_position = extend_position},
170 turret_goal->unsafe_goal(), extend_goal);
Niko Sohmersac4d8872024-02-23 13:55:47 -0800171
172 turret_.set_min_position(collision_avoidance->min_turret_goal());
173 turret_.set_max_position(collision_avoidance->max_turret_goal());
174
175 *max_intake_pivot_position = collision_avoidance->max_intake_pivot_goal();
176 *min_intake_pivot_position = collision_avoidance->min_intake_pivot_goal();
177
Austin Schuh027fd622024-03-01 21:26:07 -0800178 *max_extend_position = collision_avoidance->max_extend_goal();
179 *min_extend_position = collision_avoidance->min_extend_goal();
180
Niko Sohmersac4d8872024-02-23 13:55:47 -0800181 // Calculate the loops for a cycle.
182 const double voltage = turret_.UpdateController(disabled);
183
184 turret_.UpdateObserver(voltage);
185
186 // Write out all the voltages.
187 if (turret_output) {
188 *turret_output = voltage;
189 }
190
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800191 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Niko Sohmersac4d8872024-02-23 13:55:47 -0800192 turret_status_offset = turret_.MakeStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800193
194 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
195 altitude_status_offset = altitude_.Iterate(
196 altitude_goal, position->altitude(), altitude_output, fbb);
197
198 flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
199 catapult_status_offset;
200 {
201 // The catapult will never use a provided goal. We'll always fabricate one
202 // for it.
203 //
204 // Correct handles resetting our state when disabled.
205 const bool disabled = catapult_.Correct(nullptr, position->catapult(),
Maxwell Henderson3d68e142024-02-25 09:58:11 -0800206 catapult_output == nullptr);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800207
208 catapult_.set_enable_profile(true);
209 // We want a trajectory which accelerates up over the first portion of the
210 // range of motion, holds top speed for a little bit, then decelerates
211 // before it swings too far.
212 //
213 // We can solve for these 3 parameters through the range of motion. Top
214 // speed is goverened by the voltage headroom we want to have for the
215 // controller.
216 //
217 // Accel can be tuned given the distance to accelerate over, and decel can
218 // be solved similarly.
219 //
220 // accel = v^2 / (2 * x)
221 catapult_.mutable_profile()->set_maximum_velocity(
222 catapult::kFreeSpeed * catapult::kOutputRatio * 4.0 / 12.0);
223
224 if (disabled) {
225 state_ = CatapultState::RETRACTING;
226 }
227
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800228 constexpr double kLoadingAcceleration = 20.0;
229 constexpr double kLoadingDeceleration = 10.0;
230
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800231 switch (state_) {
232 case CatapultState::READY:
Filip Kujawa7a799602024-02-23 12:27:47 -0800233 [[fallthrough]];
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800234 case CatapultState::LOADED: {
235 if (piece_loaded) {
236 state_ = CatapultState::LOADED;
237 } else {
238 state_ = CatapultState::READY;
239 }
240
241 const bool catapult_close = CatapultClose();
242
Filip Kujawa7a799602024-02-23 12:27:47 -0800243 if (subsystems_in_range && shooter_goal != nullptr && fire &&
244 catapult_close && piece_loaded) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800245 state_ = CatapultState::FIRING;
246 } else {
247 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800248 catapult_.mutable_profile()->set_maximum_acceleration(
249 kLoadingAcceleration);
250 catapult_.mutable_profile()->set_maximum_deceleration(
251 kLoadingDeceleration);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800252 catapult_.set_unprofiled_goal(0.0, 0.0);
253
254 if (!catapult_close) {
255 state_ = CatapultState::RETRACTING;
256 }
257 break;
258 }
259 [[fallthrough]];
260 }
261 case CatapultState::FIRING:
262 *retention_roller_output =
263 robot_constants_->common()->retention_roller_voltages()->spitting();
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800264 *retention_roller_stator_current_limit =
265 robot_constants_->common()
266 ->current_limits()
267 ->shooting_retention_roller_stator_current_limit();
268 catapult_.set_controller_index(1);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800269 catapult_.mutable_profile()->set_maximum_acceleration(400.0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800270 catapult_.mutable_profile()->set_maximum_deceleration(500.0);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800271 catapult_.set_unprofiled_goal(2.0, 0.0);
272 if (CatapultClose()) {
273 state_ = CatapultState::RETRACTING;
274 } else {
275 break;
276 }
277 [[fallthrough]];
278 case CatapultState::RETRACTING:
279 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800280 catapult_.mutable_profile()->set_maximum_acceleration(
281 kLoadingAcceleration);
282 catapult_.mutable_profile()->set_maximum_deceleration(
283 kLoadingDeceleration);
284 // TODO: catapult_return_position
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800285 catapult_.set_unprofiled_goal(0.0, 0.0);
286
287 if (CatapultClose()) {
288 if (piece_loaded) {
289 state_ = CatapultState::LOADED;
290 } else {
291 state_ = CatapultState::READY;
292 }
293 }
294 break;
295 }
296
297 const double voltage = catapult_.UpdateController(disabled);
298 catapult_.UpdateObserver(voltage);
299 if (catapult_output != nullptr) {
300 *catapult_output = voltage;
301 }
302 catapult_status_offset = catapult_.MakeStatus(fbb);
303 }
304
305 flatbuffers::Offset<AimerStatus> aimer_offset;
306 if (aiming) {
307 aimer_offset = aimer_.PopulateStatus(fbb);
308 }
309
310 y2024::control_loops::superstructure::ShooterStatus::Builder status_builder(
311 *fbb);
312 status_builder.add_turret(turret_status_offset);
313 status_builder.add_altitude(altitude_status_offset);
314 status_builder.add_catapult(catapult_status_offset);
315 status_builder.add_catapult_state(state_);
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800316 status_builder.add_turret_in_range(turret_in_range);
317 status_builder.add_altitude_in_range(altitude_in_range);
318 status_builder.add_altitude_above_min_angle(altitude_above_min_angle);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800319 if (aiming) {
320 status_builder.add_aimer(aimer_offset);
321 }
322
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800323 return status_builder.Finish();
324}
325
326} // namespace y2024::control_loops::superstructure