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