blob: 9acd1903cb27da922a72c0a74c8019ab2fcb75a9 [file] [log] [blame]
Austin Schuhd5ccb862017-03-11 22:06:36 -08001#include "y2017/control_loops/superstructure/column/column.h"
2
3#include <array>
4#include <chrono>
5#include <memory>
6#include <utility>
7
8#include "Eigen/Dense"
Philipp Schrader790cb542023-07-05 21:06:52 -07009
John Park33858a32018-09-28 23:05:48 -070010#include "aos/commonmath.h"
Austin Schuhd5ccb862017-03-11 22:06:36 -080011#include "frc971/constants.h"
12#include "frc971/control_loops/profiled_subsystem.h"
13#include "frc971/control_loops/state_feedback_loop.h"
14#include "y2017/control_loops/superstructure/column/column_integral_plant.h"
15#include "y2017/control_loops/superstructure/column/stuck_column_integral_plant.h"
16
17namespace y2017 {
18namespace control_loops {
19namespace superstructure {
20namespace column {
21
22namespace chrono = ::std::chrono;
23using ::aos::monotonic_clock;
24using ::frc971::zeroing::PulseIndexZeroingEstimator;
25
26namespace {
27constexpr double kTolerance = 10.0;
Austin Schuh4af3ac12017-04-09 18:34:01 -070028constexpr double kIndexerAcceleration = 50.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -080029constexpr chrono::milliseconds kForwardTimeout{500};
Austin Schuh4af3ac12017-04-09 18:34:01 -070030constexpr chrono::milliseconds kReverseTimeout{1000};
31constexpr chrono::milliseconds kReverseMinTimeout{500};
Austin Schuhd5ccb862017-03-11 22:06:36 -080032} // namespace
33
34constexpr double Column::kZeroingVoltage;
35constexpr double Column::kOperatingVoltage;
36constexpr double Column::kIntakeZeroingMinDistance;
37constexpr double Column::kIntakeTolerance;
38constexpr double Column::kStuckZeroingTrackingError;
39
40ColumnProfiledSubsystem::ColumnProfiledSubsystem(
41 ::std::unique_ptr<
42 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>
43 loop,
44 const ::y2017::constants::Values::Column &zeroing_constants,
45 const ::frc971::constants::Range &range, double default_velocity,
46 double default_acceleration)
47 : ProfiledSubsystem<6, 1, ColumnZeroingEstimator, 2, 2>(
48 ::std::move(loop), {{zeroing_constants}}),
49 stuck_indexer_detector_(new StateFeedbackLoop<6, 2, 2>(
50 column::MakeStuckIntegralColumnLoop())),
James Kuszmaul61750662021-06-21 21:32:33 -070051 profile_(::frc971::controls::kLoopFrequency),
Austin Schuhd5ccb862017-03-11 22:06:36 -080052 range_(range),
53 default_velocity_(default_velocity),
54 default_acceleration_(default_acceleration) {
55 Y_.setZero();
56 offset_.setZero();
57 X_hat_current_.setZero();
58 stuck_indexer_X_hat_current_.setZero();
59 indexer_history_.fill(0);
60 AdjustProfile(0.0, 0.0);
61}
62
63void ColumnProfiledSubsystem::AddOffset(double indexer_offset_delta,
64 double turret_offset_delta) {
65 UpdateOffset(offset_(0, 0) + indexer_offset_delta,
66 offset_(1, 0) + turret_offset_delta);
67}
68
69void ColumnProfiledSubsystem::UpdateOffset(double indexer_offset,
70 double turret_offset) {
71 const double indexer_doffset = indexer_offset - offset_(0, 0);
72 const double turret_doffset = turret_offset - offset_(1, 0);
73
Austin Schuhf257f3c2019-10-27 21:00:43 -070074 AOS_LOG(INFO, "Adjusting indexer offset from %f to %f\n", offset_(0, 0),
75 indexer_offset);
76 AOS_LOG(INFO, "Adjusting turret offset from %f to %f\n", offset_(1, 0),
77 turret_offset);
Austin Schuhd5ccb862017-03-11 22:06:36 -080078
79 loop_->mutable_X_hat()(0, 0) += indexer_doffset;
80 loop_->mutable_X_hat()(2, 0) += turret_doffset + indexer_doffset;
81
82 stuck_indexer_detector_->mutable_X_hat()(0, 0) += indexer_doffset;
83 stuck_indexer_detector_->mutable_X_hat()(2, 0) +=
84 turret_doffset + indexer_doffset;
85 Y_(0, 0) += indexer_doffset;
86 Y_(1, 0) += turret_doffset;
87 turret_last_position_ += turret_doffset + indexer_doffset;
88 loop_->mutable_R(0, 0) += indexer_doffset;
89 loop_->mutable_R(2, 0) += turret_doffset + indexer_doffset;
90
91 profile_.MoveGoal(turret_doffset + indexer_doffset);
92 offset_(0, 0) = indexer_offset;
93 offset_(1, 0) = turret_offset;
94
95 CapGoal("R", &loop_->mutable_R());
96}
97
98void ColumnProfiledSubsystem::Correct(const ColumnPosition &new_position) {
99 estimators_[0].UpdateEstimate(new_position);
100
101 if (estimators_[0].error()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700102 AOS_LOG(ERROR, "zeroing error\n");
Austin Schuh2669ece2022-03-11 18:30:57 -0800103 X_hat_ = loop_->X_hat();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800104 return;
105 }
106
107 if (!initialized_) {
108 if (estimators_[0].offset_ready()) {
109 UpdateOffset(estimators_[0].indexer_offset(),
110 estimators_[0].turret_offset());
111 initialized_ = true;
112 }
113 }
114
115 if (!zeroed(0) && estimators_[0].zeroed()) {
116 UpdateOffset(estimators_[0].indexer_offset(),
117 estimators_[0].turret_offset());
118 set_zeroed(0, true);
119 }
120
121 turret_last_position_ = turret_position();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700122 Y_ << new_position.indexer()->encoder(), new_position.turret()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800123 Y_ += offset_;
124 loop_->Correct(Y_);
Austin Schuh2669ece2022-03-11 18:30:57 -0800125 X_hat_ = loop_->X_hat();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800126
Alex Perrycb7da4b2019-08-28 19:35:56 -0700127 indexer_history_[indexer_history_position_] =
128 new_position.indexer()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800129 indexer_history_position_ = (indexer_history_position_ + 1) % kHistoryLength;
130
131 indexer_dt_velocity_ =
Alex Perrycb7da4b2019-08-28 19:35:56 -0700132 (new_position.indexer()->encoder() - indexer_last_position_) /
James Kuszmaul61750662021-06-21 21:32:33 -0700133 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700134 indexer_last_position_ = new_position.indexer()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800135
136 stuck_indexer_detector_->Correct(Y_);
137
138 // Compute the oldest point in the history.
139 const int indexer_oldest_history_position =
140 ((indexer_history_position_ == 0) ? kHistoryLength
141 : indexer_history_position_) -
142 1;
143
144 // Compute the distance moved over that time period.
145 indexer_average_angular_velocity_ =
146 (indexer_history_[indexer_oldest_history_position] -
147 indexer_history_[indexer_history_position_]) /
James Kuszmaul61750662021-06-21 21:32:33 -0700148 (::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency) *
Austin Schuhd5ccb862017-03-11 22:06:36 -0800149 static_cast<double>(kHistoryLength - 1));
150
151 // Ready if average angular velocity is close to the goal.
152 indexer_error_ = indexer_average_angular_velocity_ - unprofiled_goal_(1, 0);
153
154 indexer_ready_ =
155 std::abs(indexer_error_) < kTolerance && unprofiled_goal_(1, 0) > 0.1;
156
157 // Pull state from the profiled subsystem.
158 X_hat_current_ = controller().X_hat();
159 stuck_indexer_X_hat_current_ = stuck_indexer_detector_->X_hat();
160 indexer_position_error_ = X_hat_current_(0, 0) - Y(0, 0);
161}
162
163void ColumnProfiledSubsystem::CapGoal(const char *name,
164 Eigen::Matrix<double, 6, 1> *goal) {
165 // Limit the goal to min/max allowable positions.
166 if (zeroed()) {
167 if ((*goal)(2, 0) > range_.upper) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700168 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
169 range_.upper);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800170 (*goal)(2, 0) = range_.upper;
171 }
172 if ((*goal)(2, 0) < range_.lower) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700173 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
174 range_.lower);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800175 (*goal)(2, 0) = range_.lower;
176 }
177 } else {
178 const double kMaxRange = range().upper_hard - range().lower_hard;
179
180 // Limit the goal to min/max allowable positions much less agressively.
181 // We don't know where the limits are, so we have to let the user move far
182 // enough to find them (and the index pulse which might be right next to
183 // one).
184 // Upper - lower hard may be a bit generous, but we are moving slow.
185
186 if ((*goal)(2, 0) > kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700187 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
188 kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800189 (*goal)(2, 0) = kMaxRange;
190 }
191 if ((*goal)(2, 0) < -kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700192 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
193 -kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800194 (*goal)(2, 0) = -kMaxRange;
195 }
196 }
197}
198
199void ColumnProfiledSubsystem::ForceGoal(double goal_velocity, double goal) {
200 set_unprofiled_goal(goal_velocity, goal);
201 loop_->mutable_R() = unprofiled_goal_;
202 loop_->mutable_next_R() = loop_->R();
203
204 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
205 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
206}
207
208void ColumnProfiledSubsystem::set_unprofiled_goal(double goal_velocity,
209 double unprofiled_goal) {
210 unprofiled_goal_(0, 0) = 0.0;
211 unprofiled_goal_(1, 0) = goal_velocity;
212 unprofiled_goal_(2, 0) = unprofiled_goal;
213 unprofiled_goal_(3, 0) = 0.0;
214 unprofiled_goal_(4, 0) = 0.0;
215 unprofiled_goal_(5, 0) = 0.0;
216 CapGoal("unprofiled R", &unprofiled_goal_);
217}
218
219void ColumnProfiledSubsystem::set_indexer_unprofiled_goal(
220 double goal_velocity) {
221 unprofiled_goal_(0, 0) = 0.0;
222 unprofiled_goal_(1, 0) = goal_velocity;
223 unprofiled_goal_(4, 0) = 0.0;
224 CapGoal("unprofiled R", &unprofiled_goal_);
225}
226
227void ColumnProfiledSubsystem::set_turret_unprofiled_goal(
228 double unprofiled_goal) {
229 unprofiled_goal_(2, 0) = unprofiled_goal;
230 unprofiled_goal_(3, 0) = 0.0;
231 unprofiled_goal_(5, 0) = 0.0;
232 CapGoal("unprofiled R", &unprofiled_goal_);
233}
234
235void ColumnProfiledSubsystem::Update(bool disable) {
236 // TODO(austin): If we really need to reset, reset the profiles, etc. That'll
237 // be covered by the layer above when disabled though, so we can get away with
238 // not doing it yet.
239 if (should_reset_) {
240 loop_->mutable_X_hat(0, 0) = Y_(0, 0);
241 loop_->mutable_X_hat(1, 0) = 0.0;
242 loop_->mutable_X_hat(2, 0) = Y_(0, 0) + Y_(1, 0);
243 loop_->mutable_X_hat(3, 0) = 0.0;
244 loop_->mutable_X_hat(4, 0) = 0.0;
245 loop_->mutable_X_hat(5, 0) = 0.0;
246
Austin Schuhf257f3c2019-10-27 21:00:43 -0700247 AOS_LOG(INFO, "Resetting\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800248 stuck_indexer_detector_->mutable_X_hat() = loop_->X_hat();
249 should_reset_ = false;
250 saturated_ = false;
251 }
252
253 if (!disable) {
254 ::Eigen::Matrix<double, 2, 1> goal_state =
255 profile_.Update(unprofiled_goal_(2, 0), unprofiled_goal_(3, 0));
256
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700257 constexpr double kDt =
James Kuszmaul61750662021-06-21 21:32:33 -0700258 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency);
Austin Schuh471d3b12017-03-25 20:49:14 -0700259
Austin Schuhd5ccb862017-03-11 22:06:36 -0800260 loop_->mutable_next_R(0, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700261 // TODO(austin): This might not handle saturation right, but I'm not sure I
262 // really care.
263 loop_->mutable_next_R(1, 0) = ::aos::Clip(
264 unprofiled_goal_(1, 0), loop_->R(1, 0) - kIndexerAcceleration * kDt,
265 loop_->R(1, 0) + kIndexerAcceleration * kDt);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800266 loop_->mutable_next_R(2, 0) = goal_state(0, 0);
267 loop_->mutable_next_R(3, 0) = goal_state(1, 0);
268 loop_->mutable_next_R(4, 0) = 0.0;
269 loop_->mutable_next_R(5, 0) = 0.0;
270 CapGoal("next R", &loop_->mutable_next_R());
271 }
272
273 // If the indexer goal velocity is low, switch to the indexer controller which
274 // won't fight to keep it moving at 0.
275 if (::std::abs(unprofiled_goal_(1, 0)) < 0.1) {
276 loop_->set_index(1);
277 } else {
278 loop_->set_index(0);
279 }
280 loop_->Update(disable);
281
282 if (!disable && loop_->U(1, 0) != loop_->U_uncapped(1, 0)) {
283 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
284 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
285 saturated_ = true;
286 } else {
287 saturated_ = false;
288 }
289
290 // Run the KF predict step.
291 stuck_indexer_detector_->UpdateObserver(loop_->U(),
James Kuszmaul61750662021-06-21 21:32:33 -0700292 ::frc971::controls::kLoopFrequency);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800293}
294
295bool ColumnProfiledSubsystem::CheckHardLimits() {
296 // Returns whether hard limits have been exceeded.
297
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700298 if (turret_position() > range_.upper_hard ||
299 turret_position() < range_.lower_hard) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700300 AOS_LOG(ERROR,
301 "ColumnProfiledSubsystem at %f out of bounds [%f, %f], ESTOPing\n",
302 turret_position(), range_.lower_hard, range_.upper_hard);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800303 return true;
304 }
305
306 return false;
307}
308
309void ColumnProfiledSubsystem::AdjustProfile(
Alex Perrycb7da4b2019-08-28 19:35:56 -0700310 const ::frc971::ProfileParameters *profile_parameters) {
311 AdjustProfile(
312 profile_parameters == nullptr ? 0.0 : profile_parameters->max_velocity(),
313 profile_parameters == nullptr ? 0.0
314 : profile_parameters->max_acceleration());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800315}
316
317void ColumnProfiledSubsystem::AdjustProfile(double max_angular_velocity,
318 double max_angular_acceleration) {
319 profile_.set_maximum_velocity(
320 ::frc971::control_loops::internal::UseUnlessZero(max_angular_velocity,
321 default_velocity_));
322 profile_.set_maximum_acceleration(
323 ::frc971::control_loops::internal::UseUnlessZero(max_angular_acceleration,
324 default_acceleration_));
325}
326
327double ColumnProfiledSubsystem::IndexerStuckVoltage() const {
328 // Compute the voltage from the control loop, excluding the voltage error
329 // term.
330 const double uncapped_applied_voltage =
331 uncapped_indexer_voltage() + X_hat(4, 0);
332 if (uncapped_applied_voltage < 0) {
333 return +stuck_indexer_X_hat_current_(4, 0);
334 } else {
335 return -stuck_indexer_X_hat_current_(4, 0);
336 }
337}
338bool ColumnProfiledSubsystem::IsIndexerStuck() const {
Austin Schuh546a0382017-04-16 19:10:18 -0700339 return IndexerStuckVoltage() > 4.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -0800340}
341
342void ColumnProfiledSubsystem::PartialIndexerReset() {
343 mutable_X_hat(4, 0) = 0.0;
344 stuck_indexer_detector_->mutable_X_hat(4, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700345 // Screw it, we are stuck. Reset the current goal to the current velocity so
346 // we start slewing faster to reverse if we have stopped.
347 loop_->mutable_R(1, 0) = X_hat(1, 0);
348 loop_->mutable_next_R(1, 0) = X_hat(1, 0);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800349}
350
351void ColumnProfiledSubsystem::PartialTurretReset() {
352 mutable_X_hat(5, 0) = 0.0;
353 stuck_indexer_detector_->mutable_X_hat(5, 0) = 0.0;
354}
355
Alex Perrycb7da4b2019-08-28 19:35:56 -0700356void ColumnProfiledSubsystem::PopulateIndexerStatus(
357 IndexerStatus::Builder *status_builder) {
358 status_builder->add_avg_angular_velocity(indexer_average_angular_velocity_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800359
Alex Perrycb7da4b2019-08-28 19:35:56 -0700360 status_builder->add_angular_velocity(X_hat_current_(1, 0));
361 status_builder->add_ready(indexer_ready_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800362
Alex Perrycb7da4b2019-08-28 19:35:56 -0700363 status_builder->add_voltage_error(X_hat_current_(4, 0));
364 status_builder->add_stuck_voltage_error(stuck_indexer_X_hat_current_(4, 0));
365 status_builder->add_position_error(indexer_position_error_);
366 status_builder->add_instantaneous_velocity(indexer_dt_velocity_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800367
Alex Perrycb7da4b2019-08-28 19:35:56 -0700368 status_builder->add_stuck(IsIndexerStuck());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800369
Alex Perrycb7da4b2019-08-28 19:35:56 -0700370 status_builder->add_stuck_voltage(IndexerStuckVoltage());
371}
372
373void ColumnProfiledSubsystem::PopulateTurretStatus(
374 TurretProfiledSubsystemStatus::Builder *status_builder,
375 flatbuffers::Offset<ColumnZeroingEstimator::State> estimator_state_offset) {
376 status_builder->add_zeroed(zeroed());
377
378 status_builder->add_position(X_hat(2, 0));
379 status_builder->add_velocity(X_hat(3, 0));
380 status_builder->add_goal_position(goal(2, 0));
381 status_builder->add_goal_velocity(goal(3, 0));
382 status_builder->add_unprofiled_goal_position(unprofiled_goal(2, 0));
383 status_builder->add_unprofiled_goal_velocity(unprofiled_goal(3, 0));
384 status_builder->add_voltage_error(X_hat(5, 0));
385 status_builder->add_calculated_velocity(
386 (turret_position() - turret_last_position_) /
James Kuszmaul61750662021-06-21 21:32:33 -0700387 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency));
Alex Perrycb7da4b2019-08-28 19:35:56 -0700388
389 status_builder->add_estimator_state(estimator_state_offset);
390
391 Eigen::Matrix<double, 6, 1> error = controller().error();
392 status_builder->add_position_power(controller().controller().K(0, 0) *
393 error(0, 0));
394 status_builder->add_velocity_power(controller().controller().K(0, 1) *
395 error(1, 0));
Austin Schuhd5ccb862017-03-11 22:06:36 -0800396}
397
Austin Schuhb6c5c852019-05-19 20:13:31 -0700398Column::Column(::aos::EventLoop *event_loop)
399 : vision_time_adjuster_(event_loop),
400 profiled_subsystem_(
Austin Schuhd5ccb862017-03-11 22:06:36 -0800401 ::std::unique_ptr<
402 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>(
403 new ::frc971::control_loops::SimpleCappedStateFeedbackLoop<
404 6, 2, 2>(MakeIntegralColumnLoop())),
405 constants::GetValues().column, constants::Values::kTurretRange, 7.0,
Austin Schuh25db1262017-04-05 19:39:55 -0700406 50.0),
407 vision_error_(constants::GetValues().vision_error) {}
Austin Schuhd5ccb862017-03-11 22:06:36 -0800408
409void Column::Reset() {
410 state_ = State::UNINITIALIZED;
411 indexer_state_ = IndexerState::RUNNING;
412 profiled_subsystem_.Reset();
413 // intake will automatically clear the minimum position on reset, so we don't
414 // need to do it here.
415 freeze_ = false;
416}
417
Alex Perrycb7da4b2019-08-28 19:35:56 -0700418std::pair<flatbuffers::Offset<IndexerStatus>,
419 flatbuffers::Offset<TurretProfiledSubsystemStatus>>
420Column::Iterate(const ::aos::monotonic_clock::time_point monotonic_now,
421 const IndexerGoalT *unsafe_indexer_goal,
422 const TurretGoal *unsafe_turret_goal,
423 const ColumnPosition *position,
424 const vision::VisionStatus *vision_status,
425 double *indexer_output, double *turret_output,
426 flatbuffers::FlatBufferBuilder *fbb, intake::Intake *intake) {
Austin Schuhd5ccb862017-03-11 22:06:36 -0800427 bool disable = turret_output == nullptr || indexer_output == nullptr;
428 profiled_subsystem_.Correct(*position);
429
Austin Schuh92715362019-07-07 20:47:45 -0700430 vision_time_adjuster_.Tick(monotonic_now, profiled_subsystem_.X_hat(2, 0),
431 vision_status);
Austin Schuhac76bb32017-03-22 22:34:26 -0700432
Austin Schuhd5ccb862017-03-11 22:06:36 -0800433 switch (state_) {
434 case State::UNINITIALIZED:
435 // Wait in the uninitialized state until the turret is initialized.
436 // Set the goals to where we are now so when we start back up, we don't
437 // jump.
438 profiled_subsystem_.ForceGoal(0.0, profiled_subsystem_.turret_position());
439 state_ = State::ZEROING_UNINITIALIZED;
440
441 // Fall through so we can start the zeroing process immediately.
James Kuszmaul3ae42262019-11-08 12:33:41 -0800442 [[fallthrough]];
Austin Schuhd5ccb862017-03-11 22:06:36 -0800443
444 case State::ZEROING_UNINITIALIZED:
445 // Set up the profile to be the zeroing profile.
446 profiled_subsystem_.AdjustProfile(0.50, 3);
447
448 // Force the intake out.
449 intake->set_min_position(kIntakeZeroingMinDistance);
450
451 if (disable) {
452 // If we are disabled, we want to reset the turret to stay where it
453 // currently is.
454 profiled_subsystem_.ForceGoal(0.0,
455 profiled_subsystem_.turret_position());
456 } else if (profiled_subsystem_.initialized()) {
457 // If we are initialized, there's no value in continuing to move so stop
458 // and wait on the intake.
459 profiled_subsystem_.set_indexer_unprofiled_goal(0.0);
460 } else {
461 // Spin slowly backwards.
462 profiled_subsystem_.set_indexer_unprofiled_goal(2.0);
463 }
464
465 // See if we are zeroed or initialized and far enough out and execute the
466 // proper state transition.
467 if (profiled_subsystem_.zeroed()) {
468 intake->clear_min_position();
469 state_ = State::RUNNING;
470 } else if (profiled_subsystem_.initialized() &&
471 intake->position() >
472 kIntakeZeroingMinDistance - kIntakeTolerance) {
473 if (profiled_subsystem_.turret_position() > 0) {
474 // We need to move in the negative direction.
475 state_ = State::ZEROING_NEGATIVE;
476 } else {
477 // We need to move in the positive direction.
478 state_ = State::ZEROING_POSITIVE;
479 }
480 }
481 break;
482
483 case State::ZEROING_POSITIVE:
484 // We are now going to be moving in the positive direction towards 0. If
485 // we get close enough, we'll zero.
486 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
487 intake->set_min_position(kIntakeZeroingMinDistance);
488
489 if (profiled_subsystem_.zeroed()) {
490 intake->clear_min_position();
491 state_ = State::RUNNING;
492 } else if (disable) {
493 // We are disabled, so pick back up from the current position.
494 profiled_subsystem_.ForceGoal(0.0,
495 profiled_subsystem_.turret_position());
496 } else if (profiled_subsystem_.turret_position() <
497 profiled_subsystem_.goal(2, 0) -
498 kStuckZeroingTrackingError ||
499 profiled_subsystem_.saturated()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700500 AOS_LOG(
501 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800502 "Turret stuck going positive, switching directions. At %f, goal "
503 "%f\n",
504 profiled_subsystem_.turret_position(),
505 profiled_subsystem_.goal(2, 0));
506 // The turret got too far behind. Declare it stuck and reverse.
507 profiled_subsystem_.AddOffset(0.0, 2.0 * M_PI);
508 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
509 profiled_subsystem_.ForceGoal(0.0,
510 profiled_subsystem_.turret_position());
511 profiled_subsystem_.PartialTurretReset();
512 profiled_subsystem_.PartialIndexerReset();
513 state_ = State::ZEROING_NEGATIVE;
514 }
515 break;
516
517 case State::ZEROING_NEGATIVE:
518 // We are now going to be moving in the negative direction towards 0. If
519 // we get close enough, we'll zero.
520 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
521 intake->set_min_position(kIntakeZeroingMinDistance);
522
523 if (profiled_subsystem_.zeroed()) {
524 intake->clear_min_position();
525 state_ = State::RUNNING;
526 } else if (disable) {
527 // We are disabled, so pick back up from the current position.
528 profiled_subsystem_.ForceGoal(0.0,
529 profiled_subsystem_.turret_position());
530 } else if (profiled_subsystem_.turret_position() >
531 profiled_subsystem_.goal(2, 0) +
532 kStuckZeroingTrackingError ||
533 profiled_subsystem_.saturated()) {
534 // The turret got too far behind. Declare it stuck and reverse.
Austin Schuhf257f3c2019-10-27 21:00:43 -0700535 AOS_LOG(
536 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800537 "Turret stuck going negative, switching directions. At %f, goal "
538 "%f\n",
539 profiled_subsystem_.turret_position(),
540 profiled_subsystem_.goal(2, 0));
541 profiled_subsystem_.AddOffset(0.0, -2.0 * M_PI);
542 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
543 profiled_subsystem_.ForceGoal(0.0,
544 profiled_subsystem_.turret_position());
545 profiled_subsystem_.PartialTurretReset();
546 profiled_subsystem_.PartialIndexerReset();
547 state_ = State::ZEROING_POSITIVE;
548 }
549 break;
550
551 case State::RUNNING: {
552 double starting_goal_angle = profiled_subsystem_.goal(2, 0);
553 if (disable) {
554 // Reset the profile to the current position so it starts from here when
555 // we get re-enabled.
556 profiled_subsystem_.ForceGoal(0.0,
557 profiled_subsystem_.turret_position());
558 }
559
560 if (unsafe_turret_goal && unsafe_indexer_goal) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700561 profiled_subsystem_.AdjustProfile(unsafe_turret_goal->profile_params());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800562 profiled_subsystem_.set_unprofiled_goal(
James Kuszmaul61750662021-06-21 21:32:33 -0700563 unsafe_indexer_goal->angular_velocity, unsafe_turret_goal->angle());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800564
Alex Perrycb7da4b2019-08-28 19:35:56 -0700565 if (unsafe_turret_goal->track()) {
Austin Schuhac76bb32017-03-22 22:34:26 -0700566 if (vision_time_adjuster_.valid()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700567 AOS_LOG(INFO, "Vision aligning to %f\n",
568 vision_time_adjuster_.goal());
Austin Schuhac76bb32017-03-22 22:34:26 -0700569 profiled_subsystem_.set_turret_unprofiled_goal(
Austin Schuh25db1262017-04-05 19:39:55 -0700570 vision_time_adjuster_.goal() + vision_error_);
Austin Schuhac76bb32017-03-22 22:34:26 -0700571 }
Austin Schuhd85c66e2017-04-16 10:50:33 -0700572 } else {
573 vision_time_adjuster_.ResetTime();
Austin Schuhac76bb32017-03-22 22:34:26 -0700574 }
575
Austin Schuhd5ccb862017-03-11 22:06:36 -0800576 if (freeze_) {
577 profiled_subsystem_.ForceGoal(unsafe_indexer_goal->angular_velocity,
578 starting_goal_angle);
579 }
580 }
581
582 // ESTOP if we hit the hard limits.
583 if (profiled_subsystem_.CheckHardLimits() ||
584 profiled_subsystem_.error()) {
585 state_ = State::ESTOP;
586 }
587 } break;
588
589 case State::ESTOP:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700590 AOS_LOG(ERROR, "Estop\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800591 disable = true;
592 break;
593 }
594
595 // Start indexing at the suggested velocity.
596 // If a "stuck" event is detected, reverse. Stay reversed until either
597 // unstuck, or 0.5 seconds have elapsed.
598 // Then, start going forwards. Don't detect stuck for 0.5 seconds.
Austin Schuhd5ccb862017-03-11 22:06:36 -0800599 switch (indexer_state_) {
600 case IndexerState::RUNNING:
601 // The velocity goal is already set above in this case, so leave it
602 // alone.
603
604 // If we are stuck and weren't just reversing, try reversing to unstick
605 // us. We don't want to chatter back and forth too fast if reversing
606 // isn't working.
607 if (profiled_subsystem_.IsIndexerStuck() &&
608 monotonic_now > kForwardTimeout + last_transition_time_) {
609 indexer_state_ = IndexerState::REVERSING;
610 last_transition_time_ = monotonic_now;
611 profiled_subsystem_.PartialIndexerReset();
Austin Schuhf257f3c2019-10-27 21:00:43 -0700612 AOS_LOG(INFO, "Partial indexer reset while going forwards\n");
613 AOS_LOG(INFO, "Indexer RUNNING -> REVERSING\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800614 }
615 break;
616 case IndexerState::REVERSING:
617 // "Reverse" "slowly".
618 profiled_subsystem_.set_indexer_unprofiled_goal(
619 -5.0 * ::aos::sign(profiled_subsystem_.unprofiled_goal(1, 0)));
620
621 // If we've timed out or are no longer stuck, try running again.
622 if ((!profiled_subsystem_.IsIndexerStuck() &&
623 monotonic_now > last_transition_time_ + kReverseMinTimeout) ||
624 monotonic_now > kReverseTimeout + last_transition_time_) {
625 indexer_state_ = IndexerState::RUNNING;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700626 AOS_LOG(INFO, "Indexer REVERSING -> RUNNING, stuck %d\n",
627 profiled_subsystem_.IsIndexerStuck());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800628
629 // Only reset if we got stuck going this way too.
630 if (monotonic_now > kReverseTimeout + last_transition_time_) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700631 AOS_LOG(INFO, "Partial indexer reset while reversing\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800632 profiled_subsystem_.PartialIndexerReset();
633 }
634 last_transition_time_ = monotonic_now;
635 }
636 break;
637 }
638
639 // Set the voltage limits.
640 const double max_voltage =
641 (state_ == State::RUNNING) ? kOperatingVoltage : kZeroingVoltage;
642
643 profiled_subsystem_.set_max_voltage({{max_voltage, max_voltage}});
644
645 // Calculate the loops for a cycle.
646 profiled_subsystem_.Update(disable);
647
648 // Write out all the voltages.
649 if (indexer_output) {
650 *indexer_output = profiled_subsystem_.indexer_voltage();
651 }
652 if (turret_output) {
653 *turret_output = profiled_subsystem_.turret_voltage();
654 }
655
656 // Save debug/internal state.
657 // TODO(austin): Save more.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700658 flatbuffers::Offset<ColumnZeroingEstimator::State>
659 column_estimator_state_offset =
660 profiled_subsystem_.EstimatorState(fbb, 0);
661
662 TurretProfiledSubsystemStatus::Builder turret_status_builder(*fbb);
663 profiled_subsystem_.PopulateTurretStatus(&turret_status_builder,
664 column_estimator_state_offset);
665 turret_status_builder.add_estopped((state_ == State::ESTOP));
666 turret_status_builder.add_state(static_cast<int32_t>(state_));
667 turret_status_builder.add_turret_encoder_angle(
668 profiled_subsystem_.turret_position());
Austin Schuhac76bb32017-03-22 22:34:26 -0700669 if (vision_time_adjuster_.valid()) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700670 turret_status_builder.add_vision_angle(vision_time_adjuster_.goal());
671 turret_status_builder.add_raw_vision_angle(
672 vision_time_adjuster_.most_recent_vision_reading());
673 turret_status_builder.add_vision_tracking(true);
Austin Schuhac76bb32017-03-22 22:34:26 -0700674 } else {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700675 turret_status_builder.add_vision_angle(
676 ::std::numeric_limits<double>::quiet_NaN());
677 turret_status_builder.add_vision_tracking(false);
Austin Schuhac76bb32017-03-22 22:34:26 -0700678 }
Austin Schuhd5ccb862017-03-11 22:06:36 -0800679
Alex Perrycb7da4b2019-08-28 19:35:56 -0700680 flatbuffers::Offset<TurretProfiledSubsystemStatus> turret_status_offset =
681 turret_status_builder.Finish();
682
683 IndexerStatus::Builder indexer_status_builder(*fbb);
684 profiled_subsystem_.PopulateIndexerStatus(&indexer_status_builder);
685
686 indexer_status_builder.add_state(static_cast<int32_t>(indexer_state_));
687
688 flatbuffers::Offset<IndexerStatus> indexer_status_offset =
689 indexer_status_builder.Finish();
690
691 return std::make_pair(indexer_status_offset, turret_status_offset);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800692}
693
694} // namespace column
695} // namespace superstructure
696} // namespace control_loops
697} // namespace y2017