blob: 532109ec60d1a5b392297768f8b90cb10a6b3717 [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(
32 robot_constants_->common()->shooter_interpolation_table())) {}
33
34flatbuffers::Offset<y2024::control_loops::superstructure::ShooterStatus>
35Shooter::Iterate(
36 const y2024::control_loops::superstructure::Position *position,
37 const y2024::control_loops::superstructure::ShooterGoal *shooter_goal,
Filip Kujawa7a799602024-02-23 12:27:47 -080038 bool fire, double *catapult_output, double *altitude_output,
39 double *turret_output, double *retention_roller_output,
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080040 double *retention_roller_stator_current_limit, double /*battery_voltage*/,
Niko Sohmersac4d8872024-02-23 13:55:47 -080041 CollisionAvoidance *collision_avoidance, const double intake_pivot_position,
42 double *max_intake_pivot_position, double *min_intake_pivot_position,
Filip Kujawa7a799602024-02-23 12:27:47 -080043 bool standby, flatbuffers::FlatBufferBuilder *fbb) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -080044 drivetrain_status_fetcher_.Fetch();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080045
46 // If our current is over the minimum current and our velocity is under our
47 // maximum velocity, then set loaded to true. If we are preloaded set it to
48 // true as well.
49 //
50 // TODO(austin): Debounce piece_loaded?
Maxwell Henderson461a8a42024-02-23 17:07:39 -080051 bool piece_loaded = position->catapult_beambreak() ||
52 (shooter_goal != nullptr && shooter_goal->preloaded());
Niko Sohmersc4d2c502024-02-19 19:35:35 -080053
54 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
55 frc971::control_loops::
56 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
57 turret_allocator;
58
59 aos::fbs::Builder<
60 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
61 turret_goal_builder(&turret_allocator);
62
63 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
64 frc971::control_loops::
65 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
66 altitude_allocator;
67
68 aos::fbs::Builder<
69 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
70 altitude_goal_builder(&altitude_allocator);
71
72 const double distance_to_goal = aimer_.DistanceToGoal();
73
74 // Always retain the game piece if we are enabled.
75 if (retention_roller_output != nullptr) {
76 *retention_roller_output =
77 robot_constants_->common()->retention_roller_voltages()->retaining();
Maxwell Hendersond5bf47a2024-02-23 17:16:48 -080078
79 if (piece_loaded) {
80 *retention_roller_stator_current_limit =
81 robot_constants_->common()
82 ->current_limits()
83 ->slower_retention_roller_stator_current_limit();
84 } else {
85 *retention_roller_stator_current_limit =
86 robot_constants_->common()
87 ->current_limits()
88 ->retention_roller_stator_current_limit();
89 }
Niko Sohmersc4d2c502024-02-19 19:35:35 -080090 }
91
92 bool aiming = false;
93
Filip Kujawa7a799602024-02-23 12:27:47 -080094 if (standby) {
95 // Note is going to AMP or TRAP, move turret to extend
96 // collision avoidance position.
97 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
98 turret_goal_builder.get(),
99 robot_constants_->common()->turret_avoid_extend_collision_position());
100
101 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
102 altitude_goal_builder.get(),
103 robot_constants_->common()->altitude_loading_position());
104 } else if (shooter_goal == nullptr || !shooter_goal->auto_aim() ||
105 (!piece_loaded && state_ == CatapultState::READY)) {
106 // We don't have the note so we should be ready to intake it.
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800107 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
108 turret_goal_builder.get(),
109 robot_constants_->common()->turret_loading_position());
110
111 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
112 altitude_goal_builder.get(),
113 robot_constants_->common()->altitude_loading_position());
Filip Kujawa7a799602024-02-23 12:27:47 -0800114
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800115 } else {
116 // We have a game piece, lets start aiming.
117 if (drivetrain_status_fetcher_.get() != nullptr) {
118 aiming = true;
119 aimer_.Update(drivetrain_status_fetcher_.get(),
120 frc971::control_loops::aiming::ShotMode::kShootOnTheFly,
121 turret_goal_builder.get());
122 }
123 }
124
125 // We have a game piece and are being asked to aim.
126 constants::Values::ShotParams shot_params;
127 if (piece_loaded && shooter_goal != nullptr && shooter_goal->auto_aim() &&
128 interpolation_table_.GetInRange(distance_to_goal, &shot_params)) {
129 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
130 altitude_goal_builder.get(), shot_params.shot_altitude_angle);
131 }
132
133 // The builder will contain either the auto-aim goal, or the loading goal. Use
134 // it if we have no goal, or no subsystem goal, or if we are auto-aiming.
Filip Kujawa7a799602024-02-23 12:27:47 -0800135
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800136 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
137 *turret_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
Filip Kujawa7a799602024-02-23 12:27:47 -0800138 !standby && shooter_goal->has_turret_position())
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800139 ? shooter_goal->turret_position()
140 : &turret_goal_builder->AsFlatbuffer();
Filip Kujawa7a799602024-02-23 12:27:47 -0800141
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800142 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
143 *altitude_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
144 shooter_goal->has_altitude_position())
145 ? shooter_goal->altitude_position()
146 : &altitude_goal_builder->AsFlatbuffer();
147
148 bool subsystems_in_range =
149 (std::abs(turret_.estimated_position() - turret_goal->unsafe_goal()) <
150 kCatapultActivationThreshold &&
151 std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
152 kCatapultActivationThreshold &&
153 altitude_.estimated_position() >
154 robot_constants_->common()->min_altitude_shooting_angle());
155
Niko Sohmersac4d8872024-02-23 13:55:47 -0800156 const bool disabled = turret_.Correct(turret_goal, position->turret(),
157 turret_output == nullptr);
158
159 collision_avoidance->UpdateGoal(
160 {.intake_pivot_position = intake_pivot_position,
161 .turret_position = turret_.estimated_position()},
162 turret_goal->unsafe_goal());
163
164 turret_.set_min_position(collision_avoidance->min_turret_goal());
165 turret_.set_max_position(collision_avoidance->max_turret_goal());
166
167 *max_intake_pivot_position = collision_avoidance->max_intake_pivot_goal();
168 *min_intake_pivot_position = collision_avoidance->min_intake_pivot_goal();
169
170 // Calculate the loops for a cycle.
171 const double voltage = turret_.UpdateController(disabled);
172
173 turret_.UpdateObserver(voltage);
174
175 // Write out all the voltages.
176 if (turret_output) {
177 *turret_output = voltage;
178 }
179
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800180 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Niko Sohmersac4d8872024-02-23 13:55:47 -0800181 turret_status_offset = turret_.MakeStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800182
183 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
184 altitude_status_offset = altitude_.Iterate(
185 altitude_goal, position->altitude(), altitude_output, fbb);
186
187 flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
188 catapult_status_offset;
189 {
190 // The catapult will never use a provided goal. We'll always fabricate one
191 // for it.
192 //
193 // Correct handles resetting our state when disabled.
194 const bool disabled = catapult_.Correct(nullptr, position->catapult(),
Maxwell Henderson3d68e142024-02-25 09:58:11 -0800195 catapult_output == nullptr);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800196
197 catapult_.set_enable_profile(true);
198 // We want a trajectory which accelerates up over the first portion of the
199 // range of motion, holds top speed for a little bit, then decelerates
200 // before it swings too far.
201 //
202 // We can solve for these 3 parameters through the range of motion. Top
203 // speed is goverened by the voltage headroom we want to have for the
204 // controller.
205 //
206 // Accel can be tuned given the distance to accelerate over, and decel can
207 // be solved similarly.
208 //
209 // accel = v^2 / (2 * x)
210 catapult_.mutable_profile()->set_maximum_velocity(
211 catapult::kFreeSpeed * catapult::kOutputRatio * 4.0 / 12.0);
212
213 if (disabled) {
214 state_ = CatapultState::RETRACTING;
215 }
216
217 switch (state_) {
218 case CatapultState::READY:
Filip Kujawa7a799602024-02-23 12:27:47 -0800219 [[fallthrough]];
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800220 case CatapultState::LOADED: {
221 if (piece_loaded) {
222 state_ = CatapultState::LOADED;
223 } else {
224 state_ = CatapultState::READY;
225 }
226
227 const bool catapult_close = CatapultClose();
228
Filip Kujawa7a799602024-02-23 12:27:47 -0800229 if (subsystems_in_range && shooter_goal != nullptr && fire &&
230 catapult_close && piece_loaded) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800231 state_ = CatapultState::FIRING;
232 } else {
233 catapult_.set_controller_index(0);
234 catapult_.mutable_profile()->set_maximum_acceleration(100.0);
235 catapult_.mutable_profile()->set_maximum_deceleration(50.0);
236 catapult_.set_unprofiled_goal(0.0, 0.0);
237
238 if (!catapult_close) {
239 state_ = CatapultState::RETRACTING;
240 }
241 break;
242 }
243 [[fallthrough]];
244 }
245 case CatapultState::FIRING:
246 *retention_roller_output =
247 robot_constants_->common()->retention_roller_voltages()->spitting();
248 catapult_.set_controller_index(0);
249 catapult_.mutable_profile()->set_maximum_acceleration(400.0);
250 catapult_.mutable_profile()->set_maximum_deceleration(600.0);
251 catapult_.set_unprofiled_goal(2.0, 0.0);
252 if (CatapultClose()) {
253 state_ = CatapultState::RETRACTING;
254 } else {
255 break;
256 }
257 [[fallthrough]];
258 case CatapultState::RETRACTING:
259 catapult_.set_controller_index(0);
260 catapult_.mutable_profile()->set_maximum_acceleration(100.0);
261 catapult_.mutable_profile()->set_maximum_deceleration(50.0);
262 catapult_.set_unprofiled_goal(0.0, 0.0);
263
264 if (CatapultClose()) {
265 if (piece_loaded) {
266 state_ = CatapultState::LOADED;
267 } else {
268 state_ = CatapultState::READY;
269 }
270 }
271 break;
272 }
273
274 const double voltage = catapult_.UpdateController(disabled);
275 catapult_.UpdateObserver(voltage);
276 if (catapult_output != nullptr) {
277 *catapult_output = voltage;
278 }
279 catapult_status_offset = catapult_.MakeStatus(fbb);
280 }
281
282 flatbuffers::Offset<AimerStatus> aimer_offset;
283 if (aiming) {
284 aimer_offset = aimer_.PopulateStatus(fbb);
285 }
286
287 y2024::control_loops::superstructure::ShooterStatus::Builder status_builder(
288 *fbb);
289 status_builder.add_turret(turret_status_offset);
290 status_builder.add_altitude(altitude_status_offset);
291 status_builder.add_catapult(catapult_status_offset);
292 status_builder.add_catapult_state(state_);
293 if (aiming) {
294 status_builder.add_aimer(aimer_offset);
295 }
296
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800297 return status_builder.Finish();
298}
299
300} // namespace y2024::control_loops::superstructure