blob: 07b7ce1c649e0aeb4343afbb206afd340ece36b6 [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*/,
Niko Sohmersac4d8872024-02-23 13:55:47 -080043 CollisionAvoidance *collision_avoidance, const double intake_pivot_position,
44 double *max_intake_pivot_position, double *min_intake_pivot_position,
Maxwell Henderson6b1be312024-02-28 20:15:06 -080045 bool standby, flatbuffers::FlatBufferBuilder *fbb,
46 aos::monotonic_clock::time_point monotonic_now) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -080047 drivetrain_status_fetcher_.Fetch();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080048
49 // If our current is over the minimum current and our velocity is under our
50 // maximum velocity, then set loaded to true. If we are preloaded set it to
51 // true as well.
Maxwell Henderson6b1be312024-02-28 20:15:06 -080052 debouncer_.Update(position->catapult_beambreak() ||
53 (shooter_goal != nullptr && shooter_goal->preloaded()),
54 monotonic_now);
55 const bool piece_loaded = debouncer_.state();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080056
57 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
58 frc971::control_loops::
59 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
60 turret_allocator;
61
62 aos::fbs::Builder<
63 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
64 turret_goal_builder(&turret_allocator);
65
66 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
67 frc971::control_loops::
68 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
69 altitude_allocator;
70
71 aos::fbs::Builder<
72 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
73 altitude_goal_builder(&altitude_allocator);
74
75 const double distance_to_goal = aimer_.DistanceToGoal();
76
77 // Always retain the game piece if we are enabled.
78 if (retention_roller_output != nullptr) {
79 *retention_roller_output =
80 robot_constants_->common()->retention_roller_voltages()->retaining();
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080081
82 if (piece_loaded) {
83 *retention_roller_stator_current_limit =
84 robot_constants_->common()
85 ->current_limits()
86 ->slower_retention_roller_stator_current_limit();
87 } else {
88 *retention_roller_stator_current_limit =
89 robot_constants_->common()
90 ->current_limits()
91 ->retention_roller_stator_current_limit();
92 }
Niko Sohmersc4d2c502024-02-19 19:35:35 -080093 }
94
95 bool aiming = false;
96
Filip Kujawa7a799602024-02-23 12:27:47 -080097 if (standby) {
98 // Note is going to AMP or TRAP, move turret to extend
99 // collision avoidance position.
100 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
101 turret_goal_builder.get(),
102 robot_constants_->common()->turret_avoid_extend_collision_position());
103
104 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
105 altitude_goal_builder.get(),
106 robot_constants_->common()->altitude_loading_position());
107 } else if (shooter_goal == nullptr || !shooter_goal->auto_aim() ||
108 (!piece_loaded && state_ == CatapultState::READY)) {
109 // 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());
Filip Kujawa7a799602024-02-23 12:27:47 -0800117
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800118 } else {
119 // We have a game piece, lets start aiming.
120 if (drivetrain_status_fetcher_.get() != nullptr) {
121 aiming = true;
122 aimer_.Update(drivetrain_status_fetcher_.get(),
123 frc971::control_loops::aiming::ShotMode::kShootOnTheFly,
124 turret_goal_builder.get());
125 }
126 }
127
128 // We have a game piece and are being asked to aim.
129 constants::Values::ShotParams shot_params;
130 if (piece_loaded && shooter_goal != nullptr && shooter_goal->auto_aim() &&
131 interpolation_table_.GetInRange(distance_to_goal, &shot_params)) {
132 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
133 altitude_goal_builder.get(), shot_params.shot_altitude_angle);
134 }
135
136 // The builder will contain either the auto-aim goal, or the loading goal. Use
137 // it if we have no goal, or no subsystem goal, or if we are auto-aiming.
Filip Kujawa7a799602024-02-23 12:27:47 -0800138
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800139 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
140 *turret_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
Filip Kujawa7a799602024-02-23 12:27:47 -0800141 !standby && shooter_goal->has_turret_position())
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800142 ? shooter_goal->turret_position()
143 : &turret_goal_builder->AsFlatbuffer();
Filip Kujawa7a799602024-02-23 12:27:47 -0800144
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800145 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
146 *altitude_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
147 shooter_goal->has_altitude_position())
148 ? shooter_goal->altitude_position()
149 : &altitude_goal_builder->AsFlatbuffer();
150
151 bool subsystems_in_range =
152 (std::abs(turret_.estimated_position() - turret_goal->unsafe_goal()) <
153 kCatapultActivationThreshold &&
154 std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
155 kCatapultActivationThreshold &&
156 altitude_.estimated_position() >
157 robot_constants_->common()->min_altitude_shooting_angle());
158
Niko Sohmersac4d8872024-02-23 13:55:47 -0800159 const bool disabled = turret_.Correct(turret_goal, position->turret(),
160 turret_output == nullptr);
161
162 collision_avoidance->UpdateGoal(
163 {.intake_pivot_position = intake_pivot_position,
164 .turret_position = turret_.estimated_position()},
165 turret_goal->unsafe_goal());
166
167 turret_.set_min_position(collision_avoidance->min_turret_goal());
168 turret_.set_max_position(collision_avoidance->max_turret_goal());
169
170 *max_intake_pivot_position = collision_avoidance->max_intake_pivot_goal();
171 *min_intake_pivot_position = collision_avoidance->min_intake_pivot_goal();
172
173 // Calculate the loops for a cycle.
174 const double voltage = turret_.UpdateController(disabled);
175
176 turret_.UpdateObserver(voltage);
177
178 // Write out all the voltages.
179 if (turret_output) {
180 *turret_output = voltage;
181 }
182
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800183 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Niko Sohmersac4d8872024-02-23 13:55:47 -0800184 turret_status_offset = turret_.MakeStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800185
186 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
187 altitude_status_offset = altitude_.Iterate(
188 altitude_goal, position->altitude(), altitude_output, fbb);
189
190 flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
191 catapult_status_offset;
192 {
193 // The catapult will never use a provided goal. We'll always fabricate one
194 // for it.
195 //
196 // Correct handles resetting our state when disabled.
197 const bool disabled = catapult_.Correct(nullptr, position->catapult(),
Maxwell Henderson3d68e142024-02-25 09:58:11 -0800198 catapult_output == nullptr);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800199
200 catapult_.set_enable_profile(true);
201 // We want a trajectory which accelerates up over the first portion of the
202 // range of motion, holds top speed for a little bit, then decelerates
203 // before it swings too far.
204 //
205 // We can solve for these 3 parameters through the range of motion. Top
206 // speed is goverened by the voltage headroom we want to have for the
207 // controller.
208 //
209 // Accel can be tuned given the distance to accelerate over, and decel can
210 // be solved similarly.
211 //
212 // accel = v^2 / (2 * x)
213 catapult_.mutable_profile()->set_maximum_velocity(
214 catapult::kFreeSpeed * catapult::kOutputRatio * 4.0 / 12.0);
215
216 if (disabled) {
217 state_ = CatapultState::RETRACTING;
218 }
219
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800220 constexpr double kLoadingAcceleration = 20.0;
221 constexpr double kLoadingDeceleration = 10.0;
222
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800223 switch (state_) {
224 case CatapultState::READY:
Filip Kujawa7a799602024-02-23 12:27:47 -0800225 [[fallthrough]];
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800226 case CatapultState::LOADED: {
227 if (piece_loaded) {
228 state_ = CatapultState::LOADED;
229 } else {
230 state_ = CatapultState::READY;
231 }
232
233 const bool catapult_close = CatapultClose();
234
Filip Kujawa7a799602024-02-23 12:27:47 -0800235 if (subsystems_in_range && shooter_goal != nullptr && fire &&
236 catapult_close && piece_loaded) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800237 state_ = CatapultState::FIRING;
238 } else {
239 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800240 catapult_.mutable_profile()->set_maximum_acceleration(
241 kLoadingAcceleration);
242 catapult_.mutable_profile()->set_maximum_deceleration(
243 kLoadingDeceleration);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800244 catapult_.set_unprofiled_goal(0.0, 0.0);
245
246 if (!catapult_close) {
247 state_ = CatapultState::RETRACTING;
248 }
249 break;
250 }
251 [[fallthrough]];
252 }
253 case CatapultState::FIRING:
254 *retention_roller_output =
255 robot_constants_->common()->retention_roller_voltages()->spitting();
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800256 *retention_roller_stator_current_limit =
257 robot_constants_->common()
258 ->current_limits()
259 ->shooting_retention_roller_stator_current_limit();
260 catapult_.set_controller_index(1);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800261 catapult_.mutable_profile()->set_maximum_acceleration(400.0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800262 catapult_.mutable_profile()->set_maximum_deceleration(500.0);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800263 catapult_.set_unprofiled_goal(2.0, 0.0);
264 if (CatapultClose()) {
265 state_ = CatapultState::RETRACTING;
266 } else {
267 break;
268 }
269 [[fallthrough]];
270 case CatapultState::RETRACTING:
271 catapult_.set_controller_index(0);
Maxwell Henderson6b1be312024-02-28 20:15:06 -0800272 catapult_.mutable_profile()->set_maximum_acceleration(
273 kLoadingAcceleration);
274 catapult_.mutable_profile()->set_maximum_deceleration(
275 kLoadingDeceleration);
276 // TODO: catapult_return_position
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800277 catapult_.set_unprofiled_goal(0.0, 0.0);
278
279 if (CatapultClose()) {
280 if (piece_loaded) {
281 state_ = CatapultState::LOADED;
282 } else {
283 state_ = CatapultState::READY;
284 }
285 }
286 break;
287 }
288
289 const double voltage = catapult_.UpdateController(disabled);
290 catapult_.UpdateObserver(voltage);
291 if (catapult_output != nullptr) {
292 *catapult_output = voltage;
293 }
294 catapult_status_offset = catapult_.MakeStatus(fbb);
295 }
296
297 flatbuffers::Offset<AimerStatus> aimer_offset;
298 if (aiming) {
299 aimer_offset = aimer_.PopulateStatus(fbb);
300 }
301
302 y2024::control_loops::superstructure::ShooterStatus::Builder status_builder(
303 *fbb);
304 status_builder.add_turret(turret_status_offset);
305 status_builder.add_altitude(altitude_status_offset);
306 status_builder.add_catapult(catapult_status_offset);
307 status_builder.add_catapult_state(state_);
308 if (aiming) {
309 status_builder.add_aimer(aimer_offset);
310 }
311
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800312 return status_builder.Finish();
313}
314
315} // namespace y2024::control_loops::superstructure