blob: 72ad0d617f392ed1e68e15ee158f8037baf5e259 [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,
38 double *catapult_output, double *altitude_output, double *turret_output,
39 double *retention_roller_output, double /*battery_voltage*/,
Niko Sohmersac4d8872024-02-23 13:55:47 -080040 CollisionAvoidance *collision_avoidance, const double intake_pivot_position,
41 double *max_intake_pivot_position, double *min_intake_pivot_position,
Niko Sohmersc4d2c502024-02-19 19:35:35 -080042 flatbuffers::FlatBufferBuilder *fbb) {
Niko Sohmersc4d2c502024-02-19 19:35:35 -080043 drivetrain_status_fetcher_.Fetch();
Niko Sohmersc4d2c502024-02-19 19:35:35 -080044
45 // If our current is over the minimum current and our velocity is under our
46 // maximum velocity, then set loaded to true. If we are preloaded set it to
47 // true as well.
48 //
49 // TODO(austin): Debounce piece_loaded?
Maxwell Henderson461a8a42024-02-23 17:07:39 -080050 bool piece_loaded = position->catapult_beambreak() ||
51 (shooter_goal != nullptr && shooter_goal->preloaded());
Niko Sohmersc4d2c502024-02-19 19:35:35 -080052
53 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
54 frc971::control_loops::
55 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
56 turret_allocator;
57
58 aos::fbs::Builder<
59 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
60 turret_goal_builder(&turret_allocator);
61
62 aos::fbs::FixedStackAllocator<aos::fbs::Builder<
63 frc971::control_loops::
64 StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
65 altitude_allocator;
66
67 aos::fbs::Builder<
68 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
69 altitude_goal_builder(&altitude_allocator);
70
71 const double distance_to_goal = aimer_.DistanceToGoal();
72
73 // Always retain the game piece if we are enabled.
74 if (retention_roller_output != nullptr) {
75 *retention_roller_output =
76 robot_constants_->common()->retention_roller_voltages()->retaining();
77 }
78
79 bool aiming = false;
80
81 // We don't have the note so we should be ready to intake it.
82 if (shooter_goal == nullptr || !shooter_goal->auto_aim() ||
83 (!piece_loaded && state_ == CatapultState::READY)) {
84 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
85 turret_goal_builder.get(),
86 robot_constants_->common()->turret_loading_position());
87
88 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
89 altitude_goal_builder.get(),
90 robot_constants_->common()->altitude_loading_position());
91 } else {
92 // We have a game piece, lets start aiming.
93 if (drivetrain_status_fetcher_.get() != nullptr) {
94 aiming = true;
95 aimer_.Update(drivetrain_status_fetcher_.get(),
96 frc971::control_loops::aiming::ShotMode::kShootOnTheFly,
97 turret_goal_builder.get());
98 }
99 }
100
101 // We have a game piece and are being asked to aim.
102 constants::Values::ShotParams shot_params;
103 if (piece_loaded && shooter_goal != nullptr && shooter_goal->auto_aim() &&
104 interpolation_table_.GetInRange(distance_to_goal, &shot_params)) {
105 PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
106 altitude_goal_builder.get(), shot_params.shot_altitude_angle);
107 }
108
109 // The builder will contain either the auto-aim goal, or the loading goal. Use
110 // it if we have no goal, or no subsystem goal, or if we are auto-aiming.
111 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
112 *turret_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
113 shooter_goal->has_turret_position())
114 ? shooter_goal->turret_position()
115 : &turret_goal_builder->AsFlatbuffer();
116 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
117 *altitude_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
118 shooter_goal->has_altitude_position())
119 ? shooter_goal->altitude_position()
120 : &altitude_goal_builder->AsFlatbuffer();
121
122 bool subsystems_in_range =
123 (std::abs(turret_.estimated_position() - turret_goal->unsafe_goal()) <
124 kCatapultActivationThreshold &&
125 std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
126 kCatapultActivationThreshold &&
127 altitude_.estimated_position() >
128 robot_constants_->common()->min_altitude_shooting_angle());
129
Niko Sohmersac4d8872024-02-23 13:55:47 -0800130 const bool disabled = turret_.Correct(turret_goal, position->turret(),
131 turret_output == nullptr);
132
133 collision_avoidance->UpdateGoal(
134 {.intake_pivot_position = intake_pivot_position,
135 .turret_position = turret_.estimated_position()},
136 turret_goal->unsafe_goal());
137
138 turret_.set_min_position(collision_avoidance->min_turret_goal());
139 turret_.set_max_position(collision_avoidance->max_turret_goal());
140
141 *max_intake_pivot_position = collision_avoidance->max_intake_pivot_goal();
142 *min_intake_pivot_position = collision_avoidance->min_intake_pivot_goal();
143
144 // Calculate the loops for a cycle.
145 const double voltage = turret_.UpdateController(disabled);
146
147 turret_.UpdateObserver(voltage);
148
149 // Write out all the voltages.
150 if (turret_output) {
151 *turret_output = voltage;
152 }
153
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800154 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Niko Sohmersac4d8872024-02-23 13:55:47 -0800155 turret_status_offset = turret_.MakeStatus(fbb);
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800156
157 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
158 altitude_status_offset = altitude_.Iterate(
159 altitude_goal, position->altitude(), altitude_output, fbb);
160
161 flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
162 catapult_status_offset;
163 {
164 // The catapult will never use a provided goal. We'll always fabricate one
165 // for it.
166 //
167 // Correct handles resetting our state when disabled.
168 const bool disabled = catapult_.Correct(nullptr, position->catapult(),
169 shooter_goal == nullptr);
170
171 catapult_.set_enable_profile(true);
172 // We want a trajectory which accelerates up over the first portion of the
173 // range of motion, holds top speed for a little bit, then decelerates
174 // before it swings too far.
175 //
176 // We can solve for these 3 parameters through the range of motion. Top
177 // speed is goverened by the voltage headroom we want to have for the
178 // controller.
179 //
180 // Accel can be tuned given the distance to accelerate over, and decel can
181 // be solved similarly.
182 //
183 // accel = v^2 / (2 * x)
184 catapult_.mutable_profile()->set_maximum_velocity(
185 catapult::kFreeSpeed * catapult::kOutputRatio * 4.0 / 12.0);
186
187 if (disabled) {
188 state_ = CatapultState::RETRACTING;
189 }
190
191 switch (state_) {
192 case CatapultState::READY:
193 case CatapultState::LOADED: {
194 if (piece_loaded) {
195 state_ = CatapultState::LOADED;
196 } else {
197 state_ = CatapultState::READY;
198 }
199
200 const bool catapult_close = CatapultClose();
201
202 if (subsystems_in_range && shooter_goal != nullptr &&
203 shooter_goal->fire() && catapult_close && piece_loaded) {
204 state_ = CatapultState::FIRING;
205 } else {
206 catapult_.set_controller_index(0);
207 catapult_.mutable_profile()->set_maximum_acceleration(100.0);
208 catapult_.mutable_profile()->set_maximum_deceleration(50.0);
209 catapult_.set_unprofiled_goal(0.0, 0.0);
210
211 if (!catapult_close) {
212 state_ = CatapultState::RETRACTING;
213 }
214 break;
215 }
216 [[fallthrough]];
217 }
218 case CatapultState::FIRING:
219 *retention_roller_output =
220 robot_constants_->common()->retention_roller_voltages()->spitting();
221 catapult_.set_controller_index(0);
222 catapult_.mutable_profile()->set_maximum_acceleration(400.0);
223 catapult_.mutable_profile()->set_maximum_deceleration(600.0);
224 catapult_.set_unprofiled_goal(2.0, 0.0);
225 if (CatapultClose()) {
226 state_ = CatapultState::RETRACTING;
227 } else {
228 break;
229 }
230 [[fallthrough]];
231 case CatapultState::RETRACTING:
232 catapult_.set_controller_index(0);
233 catapult_.mutable_profile()->set_maximum_acceleration(100.0);
234 catapult_.mutable_profile()->set_maximum_deceleration(50.0);
235 catapult_.set_unprofiled_goal(0.0, 0.0);
236
237 if (CatapultClose()) {
238 if (piece_loaded) {
239 state_ = CatapultState::LOADED;
240 } else {
241 state_ = CatapultState::READY;
242 }
243 }
244 break;
245 }
246
247 const double voltage = catapult_.UpdateController(disabled);
248 catapult_.UpdateObserver(voltage);
249 if (catapult_output != nullptr) {
250 *catapult_output = voltage;
251 }
252 catapult_status_offset = catapult_.MakeStatus(fbb);
253 }
254
255 flatbuffers::Offset<AimerStatus> aimer_offset;
256 if (aiming) {
257 aimer_offset = aimer_.PopulateStatus(fbb);
258 }
259
260 y2024::control_loops::superstructure::ShooterStatus::Builder status_builder(
261 *fbb);
262 status_builder.add_turret(turret_status_offset);
263 status_builder.add_altitude(altitude_status_offset);
264 status_builder.add_catapult(catapult_status_offset);
265 status_builder.add_catapult_state(state_);
266 if (aiming) {
267 status_builder.add_aimer(aimer_offset);
268 }
269
Niko Sohmersc4d2c502024-02-19 19:35:35 -0800270 return status_builder.Finish();
271}
272
273} // namespace y2024::control_loops::superstructure