blob: aa5eee8493d230b7f5867cc87121941439416f59 [file] [log] [blame]
milind-u086d7262022-01-19 20:44:18 -08001#include "y2022/control_loops/superstructure/superstructure.h"
2
3#include "aos/events/event_loop.h"
Milind Upadhyay225156b2022-02-25 22:42:12 -08004#include "y2022/control_loops/superstructure/collision_avoidance.h"
milind-u086d7262022-01-19 20:44:18 -08005
6namespace y2022 {
7namespace control_loops {
8namespace superstructure {
9
10using frc971::control_loops::AbsoluteEncoderProfiledJointStatus;
11using frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus;
Henry Speiser55aa3ba2022-02-21 23:21:12 -080012using frc971::control_loops::RelativeEncoderProfiledJointStatus;
milind-u086d7262022-01-19 20:44:18 -080013
14Superstructure::Superstructure(::aos::EventLoop *event_loop,
Henry Speiser55aa3ba2022-02-21 23:21:12 -080015 std::shared_ptr<const constants::Values> values,
milind-u086d7262022-01-19 20:44:18 -080016 const ::std::string &name)
17 : frc971::controls::ControlLoop<Goal, Position, Status, Output>(event_loop,
Siddhant Kanwar0e37f592022-02-21 19:26:50 -080018 name),
Austin Schuh39f26f62022-02-24 21:34:46 -080019 values_(values),
20 climber_(values_->climber.subsystem_params),
21 intake_front_(values_->intake_front.subsystem_params),
22 intake_back_(values_->intake_back.subsystem_params),
23 turret_(values_->turret.subsystem_params),
Ravago Jones5da06352022-03-04 20:26:24 -080024 catapult_(*values_),
Henry Speiser55aa3ba2022-02-21 23:21:12 -080025 drivetrain_status_fetcher_(
26 event_loop->MakeFetcher<frc971::control_loops::drivetrain::Status>(
Austin Schuh39f26f62022-02-24 21:34:46 -080027 "/drivetrain")),
Ravago Jones5da06352022-03-04 20:26:24 -080028 can_position_fetcher_(
29 event_loop->MakeFetcher<CANPosition>("/superstructure")) {
milind-u086d7262022-01-19 20:44:18 -080030 event_loop->SetRuntimeRealtimePriority(30);
31}
32
33void Superstructure::RunIteration(const Goal *unsafe_goal,
Siddhant Kanwar0e37f592022-02-21 19:26:50 -080034 const Position *position,
milind-u086d7262022-01-19 20:44:18 -080035 aos::Sender<Output>::Builder *output,
36 aos::Sender<Status>::Builder *status) {
37 if (WasReset()) {
38 AOS_LOG(ERROR, "WPILib reset, restarting\n");
Henry Speiser55aa3ba2022-02-21 23:21:12 -080039 intake_front_.Reset();
40 intake_back_.Reset();
41 turret_.Reset();
42 climber_.Reset();
Austin Schuh39f26f62022-02-24 21:34:46 -080043 catapult_.Reset();
Henry Speiser55aa3ba2022-02-21 23:21:12 -080044 }
45
Ravago Jones5da06352022-03-04 20:26:24 -080046 OutputT output_struct;
Milind Upadhyay225156b2022-02-25 22:42:12 -080047
Ravago Jones5da06352022-03-04 20:26:24 -080048 aos::FlatbufferFixedAllocatorArray<
49 frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal, 64>
50 turret_goal_buffer;
51
52 const aos::monotonic_clock::time_point timestamp =
53 event_loop()->context().monotonic_event_time;
Milind Upadhyay225156b2022-02-25 22:42:12 -080054
Henry Speiser55aa3ba2022-02-21 23:21:12 -080055 drivetrain_status_fetcher_.Fetch();
56 const float velocity = robot_velocity();
57
Ravago Jones5da06352022-03-04 20:26:24 -080058 const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
59 *turret_goal = nullptr;
60 double roller_speed_compensated_front = 0.0;
61 double roller_speed_compensated_back = 0.0;
62 double transfer_roller_speed = 0.0;
63 double flipper_arms_voltage = 0.0;
Henry Speiser55aa3ba2022-02-21 23:21:12 -080064
65 if (unsafe_goal != nullptr) {
Ravago Jones5da06352022-03-04 20:26:24 -080066 turret_goal = unsafe_goal->turret();
67
Henry Speiser55aa3ba2022-02-21 23:21:12 -080068 roller_speed_compensated_front =
69 unsafe_goal->roller_speed_front() +
70 std::max(velocity * unsafe_goal->roller_speed_compensation(), 0.0);
71
72 roller_speed_compensated_back =
73 unsafe_goal->roller_speed_back() -
74 std::min(velocity * unsafe_goal->roller_speed_compensation(), 0.0);
75
76 transfer_roller_speed = unsafe_goal->transfer_roller_speed();
milind-u086d7262022-01-19 20:44:18 -080077 }
78
Ravago Jones5da06352022-03-04 20:26:24 -080079 // TODO: Aimer sets turret_goal here
Austin Schuh39f26f62022-02-24 21:34:46 -080080
Ravago Jones5da06352022-03-04 20:26:24 -080081 // Supersturcture state machine:
82 // 1. IDLE: Wait until an intake beambreak is triggerred, meaning that a ball
83 // is being intaked. This means that the transfer rollers have a ball. If
84 // we've been waiting here for too long without any beambreak triggered, the
85 // ball got lost, so reset.
86 // 2. TRANSFERRING: Until the turret reaches the loading position where the
87 // ball can be transferred into the catapult, wiggle the ball in place.
88 // Once the turret reaches the loading position, send the ball forward with
89 // the transfer rollers until the turret beambreak is triggered.
90 // If we have been in this state for too long, the ball probably got lost so
91 // reset back to IDLE.
92 // 3. LOADING: To load the ball into the catapult, put the flippers at the
93 // feeding speed. Wait for a timeout, and then wait until the ball has gone
94 // past the turret beambreak and the flippers have stopped moving, meaning
95 // that the ball is fully loaded in the catapult.
96 // 4. LOADED: Wait until the user asks us to fire to transition to the
97 // shooting stage. If asked to cancel the shot, reset back to the IDLE state.
98 // 5. SHOOTING: Open the flippers to get ready for the shot. If they don't
99 // open quickly enough, try reseating the ball and going back to the LOADING
100 // stage, which moves the flippers in the opposite direction first.
101 // Now, hold the flippers open and wait until the turret has reached its
102 // aiming goal. Once the turret is ready, tell the catapult to fire.
103 // If the flippers move back for some reason now, it could damage the
104 // catapult, so estop it. Otherwise, wait until the catapult shoots a ball and
105 // goes back to its return position. We have now finished the shot, so return
106 // to IDLE.
107
108 const bool is_spitting = ((intake_state_ == IntakeState::INTAKE_FRONT_BALL &&
109 transfer_roller_speed < 0) ||
110 (intake_state_ == IntakeState::INTAKE_BACK_BALL &&
111 transfer_roller_speed > 0));
112
113 // Intake handling should happen regardless of the turret state
114 if (position->intake_beambreak_front() || position->intake_beambreak_back()) {
115 if (intake_state_ == IntakeState::NO_BALL) {
116 if (position->intake_beambreak_front()) {
117 intake_state_ = IntakeState::INTAKE_FRONT_BALL;
118 } else if (position->intake_beambreak_back()) {
119 intake_state_ = IntakeState::INTAKE_BACK_BALL;
120 }
121 }
122
123 intake_beambreak_timer_ = timestamp;
124 }
125
126 if (intake_state_ != IntakeState::NO_BALL) {
127 // Block intaking in
128 roller_speed_compensated_front = 0.0;
129 roller_speed_compensated_back = 0.0;
130
131 const double wiggle_voltage =
132 (intake_state_ == IntakeState::INTAKE_FRONT_BALL
133 ? constants::Values::kTransferRollerFrontWiggleVoltage()
134 : constants::Values::kTransferRollerBackWiggleVoltage());
135 // Wiggle transfer rollers: send the ball back and forth while waiting
136 // for the turret or waiting for another shot to be completed
137 if ((intake_state_ == IntakeState::INTAKE_FRONT_BALL &&
138 position->intake_beambreak_front()) ||
139 (intake_state_ == IntakeState::INTAKE_BACK_BALL &&
140 position->intake_beambreak_back())) {
141 transfer_roller_speed = -wiggle_voltage;
142 } else {
143 transfer_roller_speed = wiggle_voltage;
144 }
145 }
146
147 switch (state_) {
148 case SuperstructureState::IDLE: {
149 if (timestamp >
150 intake_beambreak_timer_ + constants::Values::kBallLostTime()) {
151 intake_state_ = IntakeState::NO_BALL;
152 }
153
154 if (is_spitting) {
155 intake_state_ = IntakeState::NO_BALL;
156 }
157
158 if (intake_state_ == IntakeState::NO_BALL ||
159 !(position->intake_beambreak_front() ||
160 position->intake_beambreak_back())) {
161 break;
162 }
163
164 state_ = SuperstructureState::TRANSFERRING;
165 transferring_timer_ = timestamp;
166
167 // Save the side the ball is on for later
168
169 break;
170 }
171 case SuperstructureState::TRANSFERRING: {
172 // If we've been transferring for too long, the ball probably got lost
173 if (timestamp >
174 transferring_timer_ + constants::Values::kBallLostTime()) {
175 intake_state_ = IntakeState::NO_BALL;
176 break;
177 }
178
179 if (intake_state_ == IntakeState::NO_BALL) {
180 state_ = SuperstructureState::IDLE;
181 break;
182 }
183
184 double turret_loading_position =
185 (intake_state_ == IntakeState::INTAKE_FRONT_BALL
186 ? constants::Values::kTurretFrontIntakePos()
187 : constants::Values::kTurretBackIntakePos());
188
189 turret_goal_buffer.Finish(
190 frc971::control_loops::
191 CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
192 *turret_goal_buffer.fbb(), turret_loading_position));
193 turret_goal = &turret_goal_buffer.message();
194
195 const bool turret_near_goal =
196 std::abs(turret_.estimated_position() - turret_loading_position) <
197 kTurretGoalThreshold;
198 if (!turret_near_goal) {
199 break; // Wait for turret to reach the chosen intake
200 }
201
202 // Transfer rollers and flipper arm belt on
203 transfer_roller_speed =
204 (intake_state_ == IntakeState::INTAKE_FRONT_BALL
205 ? constants::Values::kTransferRollerFrontVoltage()
206 : constants::Values::kTransferRollerBackVoltage());
207 flipper_arms_voltage = constants::Values::kFlipperFeedVoltage();
208
209 // Ball is in catapult
210 if (position->turret_beambreak()) {
211 intake_state_ = IntakeState::NO_BALL;
212 state_ = SuperstructureState::LOADING;
213 loading_timer_ = timestamp;
214 }
215 break;
216 }
217 case SuperstructureState::LOADING: {
218 flipper_arms_voltage = constants::Values::kFlipperFeedVoltage();
219
220 // Keep feeding for kExtraLoadingTime
221
222 can_position_fetcher_.Fetch();
223 const bool flipper_arm_roller_is_stopped =
224 can_position_fetcher_.get() != nullptr &&
225 std::abs(
226 can_position_fetcher_->flipper_arm_integrated_sensor_velocity()) <
227 0.01;
228
229 const bool reading_is_recent =
230 can_position_fetcher_.get() != nullptr &&
231 (timestamp < can_position_fetcher_.context().monotonic_event_time +
232 constants::Values::kFlipperVelocityValidTime());
233
234 // The ball should go past the turret beambreak to be loaded.
235 // If we got a CAN reading not too long ago, the flippers should have also
236 // stopped.
237 // TODO(milind): maybe it's better to update loading_timer_ as long as the
238 // turret beambreak is triggered.
239 if (timestamp > loading_timer_ + constants::Values::kExtraLoadingTime() &&
240 !position->turret_beambreak() &&
241 (flipper_arm_roller_is_stopped || !reading_is_recent)) {
242 state_ = SuperstructureState::LOADED;
243 reseating_in_catapult_ = false;
244 }
245 break;
246 }
247 case SuperstructureState::LOADED: {
248 if (unsafe_goal != nullptr) {
249 if (unsafe_goal->cancel_shot()) {
250 // Cancel the shot process
251 state_ = SuperstructureState::IDLE;
252 } else if (unsafe_goal->fire()) {
253 // Start if we were asked to and the turret is at goal
254 state_ = SuperstructureState::SHOOTING;
255 prev_shot_count_ = catapult_.shot_count();
256
257 // Reset opening timeout
258 flipper_opening_start_time_ = timestamp;
259 }
260 }
261 break;
262 }
263 case SuperstructureState::SHOOTING: {
264 // Opening flipper arms could fail, wait until they are open using their
265 // potentiometers (the member below is just named encoder).
266 // Be a little more lenient if the flippers were already open in case of
267 // noise or collisions.
268 const double flipper_open_position =
269 (flippers_open_ ? constants::Values::kReseatFlipperPosition()
270 : constants::Values::kFlipperOpenPosition());
271 flippers_open_ =
272 position->flipper_arm_left()->encoder() >= flipper_open_position &&
273 position->flipper_arm_right()->encoder() >= flipper_open_position;
274
275 if (flippers_open_) {
276 // Hold at kFlipperHoldVoltage
277 flipper_arms_voltage = constants::Values::kFlipperHoldVoltage();
278 } else {
279 // Open at kFlipperOpenVoltage
280 flipper_arms_voltage = constants::Values::kFlipperOpenVoltage();
281 }
282
283 if (!flippers_open_ &&
284 timestamp >
285 loading_timer_ + constants::Values::kFlipperOpeningTimeout()) {
286 // Reseat the ball and try again
287 state_ = SuperstructureState::LOADING;
288 loading_timer_ = timestamp;
289 reseating_in_catapult_ = true;
290 break;
291 }
292
293 const bool turret_near_goal =
294 turret_goal != nullptr &&
295 std::abs(turret_goal->unsafe_goal() - turret_.position()) <
296 kTurretGoalThreshold;
297
298 // If the turret reached the aiming goal, fire!
299 if (flippers_open_ && turret_near_goal) {
300 fire_ = true;
301 }
302
303 // If we started firing and the flippers closed a bit, estop to prevent
304 // damage
305 if (fire_ && !flippers_open_) {
306 catapult_.Estop();
307 }
308
309 const bool near_return_position =
310 (unsafe_goal != nullptr && unsafe_goal->has_catapult() &&
311 unsafe_goal->catapult()->has_return_position() &&
312 std::abs(unsafe_goal->catapult()->return_position()->unsafe_goal() -
313 catapult_.estimated_position()) < kCatapultGoalThreshold);
314
315 // Once the shot is complete and the catapult is back to its return
316 // position, go back to IDLE
317 if (catapult_.shot_count() > prev_shot_count_ && near_return_position) {
318 prev_shot_count_ = catapult_.shot_count();
319 fire_ = false;
320 state_ = SuperstructureState::IDLE;
321 }
322
323 break;
324 }
325 }
326
327 collision_avoidance_.UpdateGoal(
328 {.intake_front_position = intake_front_.estimated_position(),
329 .intake_back_position = intake_back_.estimated_position(),
330 .turret_position = turret_.estimated_position()},
331 turret_goal);
332
333 turret_.set_min_position(collision_avoidance_.min_turret_goal());
334 turret_.set_max_position(collision_avoidance_.max_turret_goal());
335 intake_front_.set_min_position(collision_avoidance_.min_intake_front_goal());
336 intake_front_.set_max_position(collision_avoidance_.max_intake_front_goal());
337 intake_back_.set_min_position(collision_avoidance_.min_intake_back_goal());
338 intake_back_.set_max_position(collision_avoidance_.max_intake_back_goal());
339
340 // Disable the catapult if we want to restart to prevent damage with flippers
Austin Schuh39f26f62022-02-24 21:34:46 -0800341 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Ravago Jones5da06352022-03-04 20:26:24 -0800342 catapult_status_offset =
343 catapult_.Iterate(unsafe_goal, position,
344 output != nullptr && !catapult_.estopped()
345 ? &(output_struct.catapult_voltage)
346 : nullptr,
347 fire_, status->fbb());
Siddhant Kanwar0e37f592022-02-21 19:26:50 -0800348
Ravago Jones5da06352022-03-04 20:26:24 -0800349 const flatbuffers::Offset<RelativeEncoderProfiledJointStatus>
Siddhant Kanwar0e37f592022-02-21 19:26:50 -0800350 climber_status_offset = climber_.Iterate(
351 unsafe_goal != nullptr ? unsafe_goal->climber() : nullptr,
Ravago Jones5da06352022-03-04 20:26:24 -0800352 position->climber(), &output_struct.climber_voltage, status->fbb());
Siddhant Kanwar0e37f592022-02-21 19:26:50 -0800353
Ravago Jones5da06352022-03-04 20:26:24 -0800354 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800355 intake_status_offset_front = intake_front_.Iterate(
356 unsafe_goal != nullptr ? unsafe_goal->intake_front() : nullptr,
Ravago Jones5da06352022-03-04 20:26:24 -0800357 position->intake_front(), &output_struct.intake_voltage_front,
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800358 status->fbb());
359
Ravago Jones5da06352022-03-04 20:26:24 -0800360 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800361 intake_status_offset_back = intake_back_.Iterate(
362 unsafe_goal != nullptr ? unsafe_goal->intake_back() : nullptr,
Ravago Jones5da06352022-03-04 20:26:24 -0800363 position->intake_back(), &output_struct.intake_voltage_back,
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800364 status->fbb());
365
Ravago Jones5da06352022-03-04 20:26:24 -0800366 const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
367 turret_status_offset =
368 turret_.Iterate(turret_goal, position->turret(),
369 &output_struct.turret_voltage, status->fbb());
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800370
Siddhant Kanwar0e37f592022-02-21 19:26:50 -0800371 if (output != nullptr) {
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800372 output_struct.roller_voltage_front = roller_speed_compensated_front;
373 output_struct.roller_voltage_back = roller_speed_compensated_back;
374 output_struct.transfer_roller_voltage = transfer_roller_speed;
Ravago Jones5da06352022-03-04 20:26:24 -0800375 output_struct.flipper_arms_voltage = flipper_arms_voltage;
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800376
milind-u086d7262022-01-19 20:44:18 -0800377 output->CheckOk(output->Send(Output::Pack(*output->fbb(), &output_struct)));
378 }
379
380 Status::Builder status_builder = status->MakeBuilder<Status>();
381
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800382 const bool zeroed = intake_front_.zeroed() && intake_back_.zeroed() &&
Ravago Jones5da06352022-03-04 20:26:24 -0800383 turret_.zeroed() && climber_.zeroed() &&
384 catapult_.zeroed();
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800385 const bool estopped = intake_front_.estopped() || intake_back_.estopped() ||
Ravago Jones5da06352022-03-04 20:26:24 -0800386 turret_.estopped() || climber_.estopped() ||
387 catapult_.estopped();
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800388
389 status_builder.add_zeroed(zeroed);
390 status_builder.add_estopped(estopped);
391
392 status_builder.add_intake_front(intake_status_offset_front);
393 status_builder.add_intake_back(intake_status_offset_back);
394 status_builder.add_turret(turret_status_offset);
Siddhant Kanwar0e37f592022-02-21 19:26:50 -0800395 status_builder.add_climber(climber_status_offset);
Ravago Jones5da06352022-03-04 20:26:24 -0800396
Austin Schuh39f26f62022-02-24 21:34:46 -0800397 status_builder.add_catapult(catapult_status_offset);
398 status_builder.add_solve_time(catapult_.solve_time());
Austin Schuh80fc2752022-02-25 13:33:56 -0800399 status_builder.add_shot_count(catapult_.shot_count());
Ravago Jones5da06352022-03-04 20:26:24 -0800400 status_builder.add_mpc_active(catapult_.mpc_active());
401
402 status_builder.add_flippers_open(flippers_open_);
403 status_builder.add_reseating_in_catapult(reseating_in_catapult_);
404 status_builder.add_fire(fire_);
405 status_builder.add_state(state_);
406 status_builder.add_intake_state(intake_state_);
milind-u086d7262022-01-19 20:44:18 -0800407
milind-u086d7262022-01-19 20:44:18 -0800408 (void)status->Send(status_builder.Finish());
409}
410
Henry Speiser55aa3ba2022-02-21 23:21:12 -0800411double Superstructure::robot_velocity() const {
412 return (drivetrain_status_fetcher_.get() != nullptr
413 ? drivetrain_status_fetcher_->robot_speed()
414 : 0.0);
415}
416
milind-u086d7262022-01-19 20:44:18 -0800417} // namespace superstructure
418} // namespace control_loops
419} // namespace y2022