blob: b8a12f77254fc361e028e7bde8e705f4cae5b2dd [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"
James Kuszmaulec635d22023-08-12 18:39:24 -070014#include "frc971/zeroing/pulse_index.h"
Austin Schuhd5ccb862017-03-11 22:06:36 -080015#include "y2017/control_loops/superstructure/column/column_integral_plant.h"
16#include "y2017/control_loops/superstructure/column/stuck_column_integral_plant.h"
17
18namespace y2017 {
19namespace control_loops {
20namespace superstructure {
21namespace column {
22
23namespace chrono = ::std::chrono;
24using ::aos::monotonic_clock;
25using ::frc971::zeroing::PulseIndexZeroingEstimator;
26
27namespace {
28constexpr double kTolerance = 10.0;
Austin Schuh4af3ac12017-04-09 18:34:01 -070029constexpr double kIndexerAcceleration = 50.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -080030constexpr chrono::milliseconds kForwardTimeout{500};
Austin Schuh4af3ac12017-04-09 18:34:01 -070031constexpr chrono::milliseconds kReverseTimeout{1000};
32constexpr chrono::milliseconds kReverseMinTimeout{500};
Austin Schuhd5ccb862017-03-11 22:06:36 -080033} // namespace
34
35constexpr double Column::kZeroingVoltage;
36constexpr double Column::kOperatingVoltage;
37constexpr double Column::kIntakeZeroingMinDistance;
38constexpr double Column::kIntakeTolerance;
39constexpr double Column::kStuckZeroingTrackingError;
40
41ColumnProfiledSubsystem::ColumnProfiledSubsystem(
42 ::std::unique_ptr<
43 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>
44 loop,
45 const ::y2017::constants::Values::Column &zeroing_constants,
46 const ::frc971::constants::Range &range, double default_velocity,
47 double default_acceleration)
48 : ProfiledSubsystem<6, 1, ColumnZeroingEstimator, 2, 2>(
49 ::std::move(loop), {{zeroing_constants}}),
50 stuck_indexer_detector_(new StateFeedbackLoop<6, 2, 2>(
51 column::MakeStuckIntegralColumnLoop())),
James Kuszmaul61750662021-06-21 21:32:33 -070052 profile_(::frc971::controls::kLoopFrequency),
Austin Schuhd5ccb862017-03-11 22:06:36 -080053 range_(range),
54 default_velocity_(default_velocity),
55 default_acceleration_(default_acceleration) {
56 Y_.setZero();
57 offset_.setZero();
58 X_hat_current_.setZero();
59 stuck_indexer_X_hat_current_.setZero();
60 indexer_history_.fill(0);
61 AdjustProfile(0.0, 0.0);
62}
63
64void ColumnProfiledSubsystem::AddOffset(double indexer_offset_delta,
65 double turret_offset_delta) {
66 UpdateOffset(offset_(0, 0) + indexer_offset_delta,
67 offset_(1, 0) + turret_offset_delta);
68}
69
70void ColumnProfiledSubsystem::UpdateOffset(double indexer_offset,
71 double turret_offset) {
72 const double indexer_doffset = indexer_offset - offset_(0, 0);
73 const double turret_doffset = turret_offset - offset_(1, 0);
74
Austin Schuhf257f3c2019-10-27 21:00:43 -070075 AOS_LOG(INFO, "Adjusting indexer offset from %f to %f\n", offset_(0, 0),
76 indexer_offset);
77 AOS_LOG(INFO, "Adjusting turret offset from %f to %f\n", offset_(1, 0),
78 turret_offset);
Austin Schuhd5ccb862017-03-11 22:06:36 -080079
80 loop_->mutable_X_hat()(0, 0) += indexer_doffset;
81 loop_->mutable_X_hat()(2, 0) += turret_doffset + indexer_doffset;
82
83 stuck_indexer_detector_->mutable_X_hat()(0, 0) += indexer_doffset;
84 stuck_indexer_detector_->mutable_X_hat()(2, 0) +=
85 turret_doffset + indexer_doffset;
86 Y_(0, 0) += indexer_doffset;
87 Y_(1, 0) += turret_doffset;
88 turret_last_position_ += turret_doffset + indexer_doffset;
89 loop_->mutable_R(0, 0) += indexer_doffset;
90 loop_->mutable_R(2, 0) += turret_doffset + indexer_doffset;
91
92 profile_.MoveGoal(turret_doffset + indexer_doffset);
93 offset_(0, 0) = indexer_offset;
94 offset_(1, 0) = turret_offset;
95
96 CapGoal("R", &loop_->mutable_R());
97}
98
99void ColumnProfiledSubsystem::Correct(const ColumnPosition &new_position) {
100 estimators_[0].UpdateEstimate(new_position);
101
102 if (estimators_[0].error()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700103 AOS_LOG(ERROR, "zeroing error\n");
Austin Schuh2669ece2022-03-11 18:30:57 -0800104 X_hat_ = loop_->X_hat();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800105 return;
106 }
107
108 if (!initialized_) {
109 if (estimators_[0].offset_ready()) {
110 UpdateOffset(estimators_[0].indexer_offset(),
111 estimators_[0].turret_offset());
112 initialized_ = true;
113 }
114 }
115
116 if (!zeroed(0) && estimators_[0].zeroed()) {
117 UpdateOffset(estimators_[0].indexer_offset(),
118 estimators_[0].turret_offset());
119 set_zeroed(0, true);
120 }
121
122 turret_last_position_ = turret_position();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700123 Y_ << new_position.indexer()->encoder(), new_position.turret()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800124 Y_ += offset_;
125 loop_->Correct(Y_);
Austin Schuh2669ece2022-03-11 18:30:57 -0800126 X_hat_ = loop_->X_hat();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800127
Alex Perrycb7da4b2019-08-28 19:35:56 -0700128 indexer_history_[indexer_history_position_] =
129 new_position.indexer()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800130 indexer_history_position_ = (indexer_history_position_ + 1) % kHistoryLength;
131
132 indexer_dt_velocity_ =
Alex Perrycb7da4b2019-08-28 19:35:56 -0700133 (new_position.indexer()->encoder() - indexer_last_position_) /
James Kuszmaul61750662021-06-21 21:32:33 -0700134 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700135 indexer_last_position_ = new_position.indexer()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800136
137 stuck_indexer_detector_->Correct(Y_);
138
139 // Compute the oldest point in the history.
140 const int indexer_oldest_history_position =
141 ((indexer_history_position_ == 0) ? kHistoryLength
142 : indexer_history_position_) -
143 1;
144
145 // Compute the distance moved over that time period.
146 indexer_average_angular_velocity_ =
147 (indexer_history_[indexer_oldest_history_position] -
148 indexer_history_[indexer_history_position_]) /
James Kuszmaul61750662021-06-21 21:32:33 -0700149 (::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency) *
Austin Schuhd5ccb862017-03-11 22:06:36 -0800150 static_cast<double>(kHistoryLength - 1));
151
152 // Ready if average angular velocity is close to the goal.
153 indexer_error_ = indexer_average_angular_velocity_ - unprofiled_goal_(1, 0);
154
155 indexer_ready_ =
156 std::abs(indexer_error_) < kTolerance && unprofiled_goal_(1, 0) > 0.1;
157
158 // Pull state from the profiled subsystem.
159 X_hat_current_ = controller().X_hat();
160 stuck_indexer_X_hat_current_ = stuck_indexer_detector_->X_hat();
161 indexer_position_error_ = X_hat_current_(0, 0) - Y(0, 0);
162}
163
164void ColumnProfiledSubsystem::CapGoal(const char *name,
165 Eigen::Matrix<double, 6, 1> *goal) {
166 // Limit the goal to min/max allowable positions.
167 if (zeroed()) {
168 if ((*goal)(2, 0) > range_.upper) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700169 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
170 range_.upper);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800171 (*goal)(2, 0) = range_.upper;
172 }
173 if ((*goal)(2, 0) < range_.lower) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700174 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
175 range_.lower);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800176 (*goal)(2, 0) = range_.lower;
177 }
178 } else {
179 const double kMaxRange = range().upper_hard - range().lower_hard;
180
181 // Limit the goal to min/max allowable positions much less agressively.
182 // We don't know where the limits are, so we have to let the user move far
183 // enough to find them (and the index pulse which might be right next to
184 // one).
185 // Upper - lower hard may be a bit generous, but we are moving slow.
186
187 if ((*goal)(2, 0) > kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700188 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
189 kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800190 (*goal)(2, 0) = kMaxRange;
191 }
192 if ((*goal)(2, 0) < -kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700193 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
194 -kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800195 (*goal)(2, 0) = -kMaxRange;
196 }
197 }
198}
199
200void ColumnProfiledSubsystem::ForceGoal(double goal_velocity, double goal) {
201 set_unprofiled_goal(goal_velocity, goal);
202 loop_->mutable_R() = unprofiled_goal_;
203 loop_->mutable_next_R() = loop_->R();
204
205 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
206 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
207}
208
209void ColumnProfiledSubsystem::set_unprofiled_goal(double goal_velocity,
210 double unprofiled_goal) {
211 unprofiled_goal_(0, 0) = 0.0;
212 unprofiled_goal_(1, 0) = goal_velocity;
213 unprofiled_goal_(2, 0) = unprofiled_goal;
214 unprofiled_goal_(3, 0) = 0.0;
215 unprofiled_goal_(4, 0) = 0.0;
216 unprofiled_goal_(5, 0) = 0.0;
217 CapGoal("unprofiled R", &unprofiled_goal_);
218}
219
220void ColumnProfiledSubsystem::set_indexer_unprofiled_goal(
221 double goal_velocity) {
222 unprofiled_goal_(0, 0) = 0.0;
223 unprofiled_goal_(1, 0) = goal_velocity;
224 unprofiled_goal_(4, 0) = 0.0;
225 CapGoal("unprofiled R", &unprofiled_goal_);
226}
227
228void ColumnProfiledSubsystem::set_turret_unprofiled_goal(
229 double unprofiled_goal) {
230 unprofiled_goal_(2, 0) = unprofiled_goal;
231 unprofiled_goal_(3, 0) = 0.0;
232 unprofiled_goal_(5, 0) = 0.0;
233 CapGoal("unprofiled R", &unprofiled_goal_);
234}
235
236void ColumnProfiledSubsystem::Update(bool disable) {
237 // TODO(austin): If we really need to reset, reset the profiles, etc. That'll
238 // be covered by the layer above when disabled though, so we can get away with
239 // not doing it yet.
240 if (should_reset_) {
241 loop_->mutable_X_hat(0, 0) = Y_(0, 0);
242 loop_->mutable_X_hat(1, 0) = 0.0;
243 loop_->mutable_X_hat(2, 0) = Y_(0, 0) + Y_(1, 0);
244 loop_->mutable_X_hat(3, 0) = 0.0;
245 loop_->mutable_X_hat(4, 0) = 0.0;
246 loop_->mutable_X_hat(5, 0) = 0.0;
247
Austin Schuhf257f3c2019-10-27 21:00:43 -0700248 AOS_LOG(INFO, "Resetting\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800249 stuck_indexer_detector_->mutable_X_hat() = loop_->X_hat();
250 should_reset_ = false;
251 saturated_ = false;
252 }
253
254 if (!disable) {
255 ::Eigen::Matrix<double, 2, 1> goal_state =
256 profile_.Update(unprofiled_goal_(2, 0), unprofiled_goal_(3, 0));
257
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700258 constexpr double kDt =
James Kuszmaul61750662021-06-21 21:32:33 -0700259 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency);
Austin Schuh471d3b12017-03-25 20:49:14 -0700260
Austin Schuhd5ccb862017-03-11 22:06:36 -0800261 loop_->mutable_next_R(0, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700262 // TODO(austin): This might not handle saturation right, but I'm not sure I
263 // really care.
264 loop_->mutable_next_R(1, 0) = ::aos::Clip(
265 unprofiled_goal_(1, 0), loop_->R(1, 0) - kIndexerAcceleration * kDt,
266 loop_->R(1, 0) + kIndexerAcceleration * kDt);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800267 loop_->mutable_next_R(2, 0) = goal_state(0, 0);
268 loop_->mutable_next_R(3, 0) = goal_state(1, 0);
269 loop_->mutable_next_R(4, 0) = 0.0;
270 loop_->mutable_next_R(5, 0) = 0.0;
271 CapGoal("next R", &loop_->mutable_next_R());
272 }
273
274 // If the indexer goal velocity is low, switch to the indexer controller which
275 // won't fight to keep it moving at 0.
276 if (::std::abs(unprofiled_goal_(1, 0)) < 0.1) {
277 loop_->set_index(1);
278 } else {
279 loop_->set_index(0);
280 }
281 loop_->Update(disable);
282
283 if (!disable && loop_->U(1, 0) != loop_->U_uncapped(1, 0)) {
284 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
285 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
286 saturated_ = true;
287 } else {
288 saturated_ = false;
289 }
290
291 // Run the KF predict step.
292 stuck_indexer_detector_->UpdateObserver(loop_->U(),
James Kuszmaul61750662021-06-21 21:32:33 -0700293 ::frc971::controls::kLoopFrequency);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800294}
295
296bool ColumnProfiledSubsystem::CheckHardLimits() {
297 // Returns whether hard limits have been exceeded.
298
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700299 if (turret_position() > range_.upper_hard ||
300 turret_position() < range_.lower_hard) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700301 AOS_LOG(ERROR,
302 "ColumnProfiledSubsystem at %f out of bounds [%f, %f], ESTOPing\n",
303 turret_position(), range_.lower_hard, range_.upper_hard);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800304 return true;
305 }
306
307 return false;
308}
309
310void ColumnProfiledSubsystem::AdjustProfile(
Alex Perrycb7da4b2019-08-28 19:35:56 -0700311 const ::frc971::ProfileParameters *profile_parameters) {
312 AdjustProfile(
313 profile_parameters == nullptr ? 0.0 : profile_parameters->max_velocity(),
314 profile_parameters == nullptr ? 0.0
315 : profile_parameters->max_acceleration());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800316}
317
318void ColumnProfiledSubsystem::AdjustProfile(double max_angular_velocity,
319 double max_angular_acceleration) {
320 profile_.set_maximum_velocity(
321 ::frc971::control_loops::internal::UseUnlessZero(max_angular_velocity,
322 default_velocity_));
323 profile_.set_maximum_acceleration(
324 ::frc971::control_loops::internal::UseUnlessZero(max_angular_acceleration,
325 default_acceleration_));
326}
327
328double ColumnProfiledSubsystem::IndexerStuckVoltage() const {
329 // Compute the voltage from the control loop, excluding the voltage error
330 // term.
331 const double uncapped_applied_voltage =
332 uncapped_indexer_voltage() + X_hat(4, 0);
333 if (uncapped_applied_voltage < 0) {
334 return +stuck_indexer_X_hat_current_(4, 0);
335 } else {
336 return -stuck_indexer_X_hat_current_(4, 0);
337 }
338}
339bool ColumnProfiledSubsystem::IsIndexerStuck() const {
Austin Schuh546a0382017-04-16 19:10:18 -0700340 return IndexerStuckVoltage() > 4.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -0800341}
342
343void ColumnProfiledSubsystem::PartialIndexerReset() {
344 mutable_X_hat(4, 0) = 0.0;
345 stuck_indexer_detector_->mutable_X_hat(4, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700346 // Screw it, we are stuck. Reset the current goal to the current velocity so
347 // we start slewing faster to reverse if we have stopped.
348 loop_->mutable_R(1, 0) = X_hat(1, 0);
349 loop_->mutable_next_R(1, 0) = X_hat(1, 0);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800350}
351
352void ColumnProfiledSubsystem::PartialTurretReset() {
353 mutable_X_hat(5, 0) = 0.0;
354 stuck_indexer_detector_->mutable_X_hat(5, 0) = 0.0;
355}
356
Alex Perrycb7da4b2019-08-28 19:35:56 -0700357void ColumnProfiledSubsystem::PopulateIndexerStatus(
358 IndexerStatus::Builder *status_builder) {
359 status_builder->add_avg_angular_velocity(indexer_average_angular_velocity_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800360
Alex Perrycb7da4b2019-08-28 19:35:56 -0700361 status_builder->add_angular_velocity(X_hat_current_(1, 0));
362 status_builder->add_ready(indexer_ready_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800363
Alex Perrycb7da4b2019-08-28 19:35:56 -0700364 status_builder->add_voltage_error(X_hat_current_(4, 0));
365 status_builder->add_stuck_voltage_error(stuck_indexer_X_hat_current_(4, 0));
366 status_builder->add_position_error(indexer_position_error_);
367 status_builder->add_instantaneous_velocity(indexer_dt_velocity_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800368
Alex Perrycb7da4b2019-08-28 19:35:56 -0700369 status_builder->add_stuck(IsIndexerStuck());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800370
Alex Perrycb7da4b2019-08-28 19:35:56 -0700371 status_builder->add_stuck_voltage(IndexerStuckVoltage());
372}
373
374void ColumnProfiledSubsystem::PopulateTurretStatus(
375 TurretProfiledSubsystemStatus::Builder *status_builder,
376 flatbuffers::Offset<ColumnZeroingEstimator::State> estimator_state_offset) {
377 status_builder->add_zeroed(zeroed());
378
379 status_builder->add_position(X_hat(2, 0));
380 status_builder->add_velocity(X_hat(3, 0));
381 status_builder->add_goal_position(goal(2, 0));
382 status_builder->add_goal_velocity(goal(3, 0));
383 status_builder->add_unprofiled_goal_position(unprofiled_goal(2, 0));
384 status_builder->add_unprofiled_goal_velocity(unprofiled_goal(3, 0));
385 status_builder->add_voltage_error(X_hat(5, 0));
386 status_builder->add_calculated_velocity(
387 (turret_position() - turret_last_position_) /
James Kuszmaul61750662021-06-21 21:32:33 -0700388 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency));
Alex Perrycb7da4b2019-08-28 19:35:56 -0700389
390 status_builder->add_estimator_state(estimator_state_offset);
391
392 Eigen::Matrix<double, 6, 1> error = controller().error();
393 status_builder->add_position_power(controller().controller().K(0, 0) *
394 error(0, 0));
395 status_builder->add_velocity_power(controller().controller().K(0, 1) *
396 error(1, 0));
Austin Schuhd5ccb862017-03-11 22:06:36 -0800397}
398
Austin Schuhb6c5c852019-05-19 20:13:31 -0700399Column::Column(::aos::EventLoop *event_loop)
400 : vision_time_adjuster_(event_loop),
401 profiled_subsystem_(
Austin Schuhd5ccb862017-03-11 22:06:36 -0800402 ::std::unique_ptr<
403 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>(
404 new ::frc971::control_loops::SimpleCappedStateFeedbackLoop<
405 6, 2, 2>(MakeIntegralColumnLoop())),
406 constants::GetValues().column, constants::Values::kTurretRange, 7.0,
Austin Schuh25db1262017-04-05 19:39:55 -0700407 50.0),
408 vision_error_(constants::GetValues().vision_error) {}
Austin Schuhd5ccb862017-03-11 22:06:36 -0800409
410void Column::Reset() {
411 state_ = State::UNINITIALIZED;
412 indexer_state_ = IndexerState::RUNNING;
413 profiled_subsystem_.Reset();
414 // intake will automatically clear the minimum position on reset, so we don't
415 // need to do it here.
416 freeze_ = false;
417}
418
Alex Perrycb7da4b2019-08-28 19:35:56 -0700419std::pair<flatbuffers::Offset<IndexerStatus>,
420 flatbuffers::Offset<TurretProfiledSubsystemStatus>>
421Column::Iterate(const ::aos::monotonic_clock::time_point monotonic_now,
422 const IndexerGoalT *unsafe_indexer_goal,
423 const TurretGoal *unsafe_turret_goal,
424 const ColumnPosition *position,
425 const vision::VisionStatus *vision_status,
426 double *indexer_output, double *turret_output,
427 flatbuffers::FlatBufferBuilder *fbb, intake::Intake *intake) {
Austin Schuhd5ccb862017-03-11 22:06:36 -0800428 bool disable = turret_output == nullptr || indexer_output == nullptr;
429 profiled_subsystem_.Correct(*position);
430
Austin Schuh92715362019-07-07 20:47:45 -0700431 vision_time_adjuster_.Tick(monotonic_now, profiled_subsystem_.X_hat(2, 0),
432 vision_status);
Austin Schuhac76bb32017-03-22 22:34:26 -0700433
Austin Schuhd5ccb862017-03-11 22:06:36 -0800434 switch (state_) {
435 case State::UNINITIALIZED:
436 // Wait in the uninitialized state until the turret is initialized.
437 // Set the goals to where we are now so when we start back up, we don't
438 // jump.
439 profiled_subsystem_.ForceGoal(0.0, profiled_subsystem_.turret_position());
440 state_ = State::ZEROING_UNINITIALIZED;
441
442 // Fall through so we can start the zeroing process immediately.
James Kuszmaul3ae42262019-11-08 12:33:41 -0800443 [[fallthrough]];
Austin Schuhd5ccb862017-03-11 22:06:36 -0800444
445 case State::ZEROING_UNINITIALIZED:
446 // Set up the profile to be the zeroing profile.
447 profiled_subsystem_.AdjustProfile(0.50, 3);
448
449 // Force the intake out.
450 intake->set_min_position(kIntakeZeroingMinDistance);
451
452 if (disable) {
453 // If we are disabled, we want to reset the turret to stay where it
454 // currently is.
455 profiled_subsystem_.ForceGoal(0.0,
456 profiled_subsystem_.turret_position());
457 } else if (profiled_subsystem_.initialized()) {
458 // If we are initialized, there's no value in continuing to move so stop
459 // and wait on the intake.
460 profiled_subsystem_.set_indexer_unprofiled_goal(0.0);
461 } else {
462 // Spin slowly backwards.
463 profiled_subsystem_.set_indexer_unprofiled_goal(2.0);
464 }
465
466 // See if we are zeroed or initialized and far enough out and execute the
467 // proper state transition.
468 if (profiled_subsystem_.zeroed()) {
469 intake->clear_min_position();
470 state_ = State::RUNNING;
471 } else if (profiled_subsystem_.initialized() &&
472 intake->position() >
473 kIntakeZeroingMinDistance - kIntakeTolerance) {
474 if (profiled_subsystem_.turret_position() > 0) {
475 // We need to move in the negative direction.
476 state_ = State::ZEROING_NEGATIVE;
477 } else {
478 // We need to move in the positive direction.
479 state_ = State::ZEROING_POSITIVE;
480 }
481 }
482 break;
483
484 case State::ZEROING_POSITIVE:
485 // We are now going to be moving in the positive direction towards 0. If
486 // we get close enough, we'll zero.
487 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
488 intake->set_min_position(kIntakeZeroingMinDistance);
489
490 if (profiled_subsystem_.zeroed()) {
491 intake->clear_min_position();
492 state_ = State::RUNNING;
493 } else if (disable) {
494 // We are disabled, so pick back up from the current position.
495 profiled_subsystem_.ForceGoal(0.0,
496 profiled_subsystem_.turret_position());
497 } else if (profiled_subsystem_.turret_position() <
498 profiled_subsystem_.goal(2, 0) -
499 kStuckZeroingTrackingError ||
500 profiled_subsystem_.saturated()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700501 AOS_LOG(
502 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800503 "Turret stuck going positive, switching directions. At %f, goal "
504 "%f\n",
505 profiled_subsystem_.turret_position(),
506 profiled_subsystem_.goal(2, 0));
507 // The turret got too far behind. Declare it stuck and reverse.
508 profiled_subsystem_.AddOffset(0.0, 2.0 * M_PI);
509 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
510 profiled_subsystem_.ForceGoal(0.0,
511 profiled_subsystem_.turret_position());
512 profiled_subsystem_.PartialTurretReset();
513 profiled_subsystem_.PartialIndexerReset();
514 state_ = State::ZEROING_NEGATIVE;
515 }
516 break;
517
518 case State::ZEROING_NEGATIVE:
519 // We are now going to be moving in the negative direction towards 0. If
520 // we get close enough, we'll zero.
521 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
522 intake->set_min_position(kIntakeZeroingMinDistance);
523
524 if (profiled_subsystem_.zeroed()) {
525 intake->clear_min_position();
526 state_ = State::RUNNING;
527 } else if (disable) {
528 // We are disabled, so pick back up from the current position.
529 profiled_subsystem_.ForceGoal(0.0,
530 profiled_subsystem_.turret_position());
531 } else if (profiled_subsystem_.turret_position() >
532 profiled_subsystem_.goal(2, 0) +
533 kStuckZeroingTrackingError ||
534 profiled_subsystem_.saturated()) {
535 // The turret got too far behind. Declare it stuck and reverse.
Austin Schuhf257f3c2019-10-27 21:00:43 -0700536 AOS_LOG(
537 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800538 "Turret stuck going negative, switching directions. At %f, goal "
539 "%f\n",
540 profiled_subsystem_.turret_position(),
541 profiled_subsystem_.goal(2, 0));
542 profiled_subsystem_.AddOffset(0.0, -2.0 * M_PI);
543 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
544 profiled_subsystem_.ForceGoal(0.0,
545 profiled_subsystem_.turret_position());
546 profiled_subsystem_.PartialTurretReset();
547 profiled_subsystem_.PartialIndexerReset();
548 state_ = State::ZEROING_POSITIVE;
549 }
550 break;
551
552 case State::RUNNING: {
553 double starting_goal_angle = profiled_subsystem_.goal(2, 0);
554 if (disable) {
555 // Reset the profile to the current position so it starts from here when
556 // we get re-enabled.
557 profiled_subsystem_.ForceGoal(0.0,
558 profiled_subsystem_.turret_position());
559 }
560
561 if (unsafe_turret_goal && unsafe_indexer_goal) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700562 profiled_subsystem_.AdjustProfile(unsafe_turret_goal->profile_params());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800563 profiled_subsystem_.set_unprofiled_goal(
James Kuszmaul61750662021-06-21 21:32:33 -0700564 unsafe_indexer_goal->angular_velocity, unsafe_turret_goal->angle());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800565
Alex Perrycb7da4b2019-08-28 19:35:56 -0700566 if (unsafe_turret_goal->track()) {
Austin Schuhac76bb32017-03-22 22:34:26 -0700567 if (vision_time_adjuster_.valid()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700568 AOS_LOG(INFO, "Vision aligning to %f\n",
569 vision_time_adjuster_.goal());
Austin Schuhac76bb32017-03-22 22:34:26 -0700570 profiled_subsystem_.set_turret_unprofiled_goal(
Austin Schuh25db1262017-04-05 19:39:55 -0700571 vision_time_adjuster_.goal() + vision_error_);
Austin Schuhac76bb32017-03-22 22:34:26 -0700572 }
Austin Schuhd85c66e2017-04-16 10:50:33 -0700573 } else {
574 vision_time_adjuster_.ResetTime();
Austin Schuhac76bb32017-03-22 22:34:26 -0700575 }
576
Austin Schuhd5ccb862017-03-11 22:06:36 -0800577 if (freeze_) {
578 profiled_subsystem_.ForceGoal(unsafe_indexer_goal->angular_velocity,
579 starting_goal_angle);
580 }
581 }
582
583 // ESTOP if we hit the hard limits.
584 if (profiled_subsystem_.CheckHardLimits() ||
585 profiled_subsystem_.error()) {
586 state_ = State::ESTOP;
587 }
588 } break;
589
590 case State::ESTOP:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700591 AOS_LOG(ERROR, "Estop\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800592 disable = true;
593 break;
594 }
595
596 // Start indexing at the suggested velocity.
597 // If a "stuck" event is detected, reverse. Stay reversed until either
598 // unstuck, or 0.5 seconds have elapsed.
599 // Then, start going forwards. Don't detect stuck for 0.5 seconds.
Austin Schuhd5ccb862017-03-11 22:06:36 -0800600 switch (indexer_state_) {
601 case IndexerState::RUNNING:
602 // The velocity goal is already set above in this case, so leave it
603 // alone.
604
605 // If we are stuck and weren't just reversing, try reversing to unstick
606 // us. We don't want to chatter back and forth too fast if reversing
607 // isn't working.
608 if (profiled_subsystem_.IsIndexerStuck() &&
609 monotonic_now > kForwardTimeout + last_transition_time_) {
610 indexer_state_ = IndexerState::REVERSING;
611 last_transition_time_ = monotonic_now;
612 profiled_subsystem_.PartialIndexerReset();
Austin Schuhf257f3c2019-10-27 21:00:43 -0700613 AOS_LOG(INFO, "Partial indexer reset while going forwards\n");
614 AOS_LOG(INFO, "Indexer RUNNING -> REVERSING\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800615 }
616 break;
617 case IndexerState::REVERSING:
618 // "Reverse" "slowly".
619 profiled_subsystem_.set_indexer_unprofiled_goal(
620 -5.0 * ::aos::sign(profiled_subsystem_.unprofiled_goal(1, 0)));
621
622 // If we've timed out or are no longer stuck, try running again.
623 if ((!profiled_subsystem_.IsIndexerStuck() &&
624 monotonic_now > last_transition_time_ + kReverseMinTimeout) ||
625 monotonic_now > kReverseTimeout + last_transition_time_) {
626 indexer_state_ = IndexerState::RUNNING;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700627 AOS_LOG(INFO, "Indexer REVERSING -> RUNNING, stuck %d\n",
628 profiled_subsystem_.IsIndexerStuck());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800629
630 // Only reset if we got stuck going this way too.
631 if (monotonic_now > kReverseTimeout + last_transition_time_) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700632 AOS_LOG(INFO, "Partial indexer reset while reversing\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800633 profiled_subsystem_.PartialIndexerReset();
634 }
635 last_transition_time_ = monotonic_now;
636 }
637 break;
638 }
639
640 // Set the voltage limits.
641 const double max_voltage =
642 (state_ == State::RUNNING) ? kOperatingVoltage : kZeroingVoltage;
643
644 profiled_subsystem_.set_max_voltage({{max_voltage, max_voltage}});
645
646 // Calculate the loops for a cycle.
647 profiled_subsystem_.Update(disable);
648
649 // Write out all the voltages.
650 if (indexer_output) {
651 *indexer_output = profiled_subsystem_.indexer_voltage();
652 }
653 if (turret_output) {
654 *turret_output = profiled_subsystem_.turret_voltage();
655 }
656
657 // Save debug/internal state.
658 // TODO(austin): Save more.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700659 flatbuffers::Offset<ColumnZeroingEstimator::State>
660 column_estimator_state_offset =
661 profiled_subsystem_.EstimatorState(fbb, 0);
662
663 TurretProfiledSubsystemStatus::Builder turret_status_builder(*fbb);
664 profiled_subsystem_.PopulateTurretStatus(&turret_status_builder,
665 column_estimator_state_offset);
666 turret_status_builder.add_estopped((state_ == State::ESTOP));
667 turret_status_builder.add_state(static_cast<int32_t>(state_));
668 turret_status_builder.add_turret_encoder_angle(
669 profiled_subsystem_.turret_position());
Austin Schuhac76bb32017-03-22 22:34:26 -0700670 if (vision_time_adjuster_.valid()) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700671 turret_status_builder.add_vision_angle(vision_time_adjuster_.goal());
672 turret_status_builder.add_raw_vision_angle(
673 vision_time_adjuster_.most_recent_vision_reading());
674 turret_status_builder.add_vision_tracking(true);
Austin Schuhac76bb32017-03-22 22:34:26 -0700675 } else {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700676 turret_status_builder.add_vision_angle(
677 ::std::numeric_limits<double>::quiet_NaN());
678 turret_status_builder.add_vision_tracking(false);
Austin Schuhac76bb32017-03-22 22:34:26 -0700679 }
Austin Schuhd5ccb862017-03-11 22:06:36 -0800680
Alex Perrycb7da4b2019-08-28 19:35:56 -0700681 flatbuffers::Offset<TurretProfiledSubsystemStatus> turret_status_offset =
682 turret_status_builder.Finish();
683
684 IndexerStatus::Builder indexer_status_builder(*fbb);
685 profiled_subsystem_.PopulateIndexerStatus(&indexer_status_builder);
686
687 indexer_status_builder.add_state(static_cast<int32_t>(indexer_state_));
688
689 flatbuffers::Offset<IndexerStatus> indexer_status_offset =
690 indexer_status_builder.Finish();
691
692 return std::make_pair(indexer_status_offset, turret_status_offset);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800693}
694
695} // namespace column
696} // namespace superstructure
697} // namespace control_loops
698} // namespace y2017