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