blob: 8f36085a3aeff40127f1e46c472dd5ce6694f118 [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"
John Park33858a32018-09-28 23:05:48 -07009#include "aos/commonmath.h"
Austin Schuhd5ccb862017-03-11 22:06:36 -080010#include "frc971/constants.h"
11#include "frc971/control_loops/profiled_subsystem.h"
12#include "frc971/control_loops/state_feedback_loop.h"
13#include "y2017/control_loops/superstructure/column/column_integral_plant.h"
14#include "y2017/control_loops/superstructure/column/stuck_column_integral_plant.h"
15
16namespace y2017 {
17namespace control_loops {
18namespace superstructure {
19namespace column {
20
21namespace chrono = ::std::chrono;
22using ::aos::monotonic_clock;
23using ::frc971::zeroing::PulseIndexZeroingEstimator;
24
25namespace {
26constexpr double kTolerance = 10.0;
Austin Schuh4af3ac12017-04-09 18:34:01 -070027constexpr double kIndexerAcceleration = 50.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -080028constexpr chrono::milliseconds kForwardTimeout{500};
Austin Schuh4af3ac12017-04-09 18:34:01 -070029constexpr chrono::milliseconds kReverseTimeout{1000};
30constexpr chrono::milliseconds kReverseMinTimeout{500};
Austin Schuhd5ccb862017-03-11 22:06:36 -080031} // namespace
32
33constexpr double Column::kZeroingVoltage;
34constexpr double Column::kOperatingVoltage;
35constexpr double Column::kIntakeZeroingMinDistance;
36constexpr double Column::kIntakeTolerance;
37constexpr double Column::kStuckZeroingTrackingError;
38
39ColumnProfiledSubsystem::ColumnProfiledSubsystem(
40 ::std::unique_ptr<
41 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>
42 loop,
43 const ::y2017::constants::Values::Column &zeroing_constants,
44 const ::frc971::constants::Range &range, double default_velocity,
45 double default_acceleration)
46 : ProfiledSubsystem<6, 1, ColumnZeroingEstimator, 2, 2>(
47 ::std::move(loop), {{zeroing_constants}}),
48 stuck_indexer_detector_(new StateFeedbackLoop<6, 2, 2>(
49 column::MakeStuckIntegralColumnLoop())),
James Kuszmaul61750662021-06-21 21:32:33 -070050 profile_(::frc971::controls::kLoopFrequency),
Austin Schuhd5ccb862017-03-11 22:06:36 -080051 range_(range),
52 default_velocity_(default_velocity),
53 default_acceleration_(default_acceleration) {
54 Y_.setZero();
55 offset_.setZero();
56 X_hat_current_.setZero();
57 stuck_indexer_X_hat_current_.setZero();
58 indexer_history_.fill(0);
59 AdjustProfile(0.0, 0.0);
60}
61
62void ColumnProfiledSubsystem::AddOffset(double indexer_offset_delta,
63 double turret_offset_delta) {
64 UpdateOffset(offset_(0, 0) + indexer_offset_delta,
65 offset_(1, 0) + turret_offset_delta);
66}
67
68void ColumnProfiledSubsystem::UpdateOffset(double indexer_offset,
69 double turret_offset) {
70 const double indexer_doffset = indexer_offset - offset_(0, 0);
71 const double turret_doffset = turret_offset - offset_(1, 0);
72
Austin Schuhf257f3c2019-10-27 21:00:43 -070073 AOS_LOG(INFO, "Adjusting indexer offset from %f to %f\n", offset_(0, 0),
74 indexer_offset);
75 AOS_LOG(INFO, "Adjusting turret offset from %f to %f\n", offset_(1, 0),
76 turret_offset);
Austin Schuhd5ccb862017-03-11 22:06:36 -080077
78 loop_->mutable_X_hat()(0, 0) += indexer_doffset;
79 loop_->mutable_X_hat()(2, 0) += turret_doffset + indexer_doffset;
80
81 stuck_indexer_detector_->mutable_X_hat()(0, 0) += indexer_doffset;
82 stuck_indexer_detector_->mutable_X_hat()(2, 0) +=
83 turret_doffset + indexer_doffset;
84 Y_(0, 0) += indexer_doffset;
85 Y_(1, 0) += turret_doffset;
86 turret_last_position_ += turret_doffset + indexer_doffset;
87 loop_->mutable_R(0, 0) += indexer_doffset;
88 loop_->mutable_R(2, 0) += turret_doffset + indexer_doffset;
89
90 profile_.MoveGoal(turret_doffset + indexer_doffset);
91 offset_(0, 0) = indexer_offset;
92 offset_(1, 0) = turret_offset;
93
94 CapGoal("R", &loop_->mutable_R());
95}
96
97void ColumnProfiledSubsystem::Correct(const ColumnPosition &new_position) {
98 estimators_[0].UpdateEstimate(new_position);
99
100 if (estimators_[0].error()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700101 AOS_LOG(ERROR, "zeroing error\n");
Austin Schuh2669ece2022-03-11 18:30:57 -0800102 X_hat_ = loop_->X_hat();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800103 return;
104 }
105
106 if (!initialized_) {
107 if (estimators_[0].offset_ready()) {
108 UpdateOffset(estimators_[0].indexer_offset(),
109 estimators_[0].turret_offset());
110 initialized_ = true;
111 }
112 }
113
114 if (!zeroed(0) && estimators_[0].zeroed()) {
115 UpdateOffset(estimators_[0].indexer_offset(),
116 estimators_[0].turret_offset());
117 set_zeroed(0, true);
118 }
119
120 turret_last_position_ = turret_position();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700121 Y_ << new_position.indexer()->encoder(), new_position.turret()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800122 Y_ += offset_;
123 loop_->Correct(Y_);
Austin Schuh2669ece2022-03-11 18:30:57 -0800124 X_hat_ = loop_->X_hat();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800125
Alex Perrycb7da4b2019-08-28 19:35:56 -0700126 indexer_history_[indexer_history_position_] =
127 new_position.indexer()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800128 indexer_history_position_ = (indexer_history_position_ + 1) % kHistoryLength;
129
130 indexer_dt_velocity_ =
Alex Perrycb7da4b2019-08-28 19:35:56 -0700131 (new_position.indexer()->encoder() - indexer_last_position_) /
James Kuszmaul61750662021-06-21 21:32:33 -0700132 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700133 indexer_last_position_ = new_position.indexer()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800134
135 stuck_indexer_detector_->Correct(Y_);
136
137 // Compute the oldest point in the history.
138 const int indexer_oldest_history_position =
139 ((indexer_history_position_ == 0) ? kHistoryLength
140 : indexer_history_position_) -
141 1;
142
143 // Compute the distance moved over that time period.
144 indexer_average_angular_velocity_ =
145 (indexer_history_[indexer_oldest_history_position] -
146 indexer_history_[indexer_history_position_]) /
James Kuszmaul61750662021-06-21 21:32:33 -0700147 (::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency) *
Austin Schuhd5ccb862017-03-11 22:06:36 -0800148 static_cast<double>(kHistoryLength - 1));
149
150 // Ready if average angular velocity is close to the goal.
151 indexer_error_ = indexer_average_angular_velocity_ - unprofiled_goal_(1, 0);
152
153 indexer_ready_ =
154 std::abs(indexer_error_) < kTolerance && unprofiled_goal_(1, 0) > 0.1;
155
156 // Pull state from the profiled subsystem.
157 X_hat_current_ = controller().X_hat();
158 stuck_indexer_X_hat_current_ = stuck_indexer_detector_->X_hat();
159 indexer_position_error_ = X_hat_current_(0, 0) - Y(0, 0);
160}
161
162void ColumnProfiledSubsystem::CapGoal(const char *name,
163 Eigen::Matrix<double, 6, 1> *goal) {
164 // Limit the goal to min/max allowable positions.
165 if (zeroed()) {
166 if ((*goal)(2, 0) > range_.upper) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700167 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
168 range_.upper);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800169 (*goal)(2, 0) = range_.upper;
170 }
171 if ((*goal)(2, 0) < range_.lower) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700172 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
173 range_.lower);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800174 (*goal)(2, 0) = range_.lower;
175 }
176 } else {
177 const double kMaxRange = range().upper_hard - range().lower_hard;
178
179 // Limit the goal to min/max allowable positions much less agressively.
180 // We don't know where the limits are, so we have to let the user move far
181 // enough to find them (and the index pulse which might be right next to
182 // one).
183 // Upper - lower hard may be a bit generous, but we are moving slow.
184
185 if ((*goal)(2, 0) > kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700186 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
187 kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800188 (*goal)(2, 0) = kMaxRange;
189 }
190 if ((*goal)(2, 0) < -kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700191 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
192 -kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800193 (*goal)(2, 0) = -kMaxRange;
194 }
195 }
196}
197
198void ColumnProfiledSubsystem::ForceGoal(double goal_velocity, double goal) {
199 set_unprofiled_goal(goal_velocity, goal);
200 loop_->mutable_R() = unprofiled_goal_;
201 loop_->mutable_next_R() = loop_->R();
202
203 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
204 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
205}
206
207void ColumnProfiledSubsystem::set_unprofiled_goal(double goal_velocity,
208 double unprofiled_goal) {
209 unprofiled_goal_(0, 0) = 0.0;
210 unprofiled_goal_(1, 0) = goal_velocity;
211 unprofiled_goal_(2, 0) = unprofiled_goal;
212 unprofiled_goal_(3, 0) = 0.0;
213 unprofiled_goal_(4, 0) = 0.0;
214 unprofiled_goal_(5, 0) = 0.0;
215 CapGoal("unprofiled R", &unprofiled_goal_);
216}
217
218void ColumnProfiledSubsystem::set_indexer_unprofiled_goal(
219 double goal_velocity) {
220 unprofiled_goal_(0, 0) = 0.0;
221 unprofiled_goal_(1, 0) = goal_velocity;
222 unprofiled_goal_(4, 0) = 0.0;
223 CapGoal("unprofiled R", &unprofiled_goal_);
224}
225
226void ColumnProfiledSubsystem::set_turret_unprofiled_goal(
227 double unprofiled_goal) {
228 unprofiled_goal_(2, 0) = unprofiled_goal;
229 unprofiled_goal_(3, 0) = 0.0;
230 unprofiled_goal_(5, 0) = 0.0;
231 CapGoal("unprofiled R", &unprofiled_goal_);
232}
233
234void ColumnProfiledSubsystem::Update(bool disable) {
235 // TODO(austin): If we really need to reset, reset the profiles, etc. That'll
236 // be covered by the layer above when disabled though, so we can get away with
237 // not doing it yet.
238 if (should_reset_) {
239 loop_->mutable_X_hat(0, 0) = Y_(0, 0);
240 loop_->mutable_X_hat(1, 0) = 0.0;
241 loop_->mutable_X_hat(2, 0) = Y_(0, 0) + Y_(1, 0);
242 loop_->mutable_X_hat(3, 0) = 0.0;
243 loop_->mutable_X_hat(4, 0) = 0.0;
244 loop_->mutable_X_hat(5, 0) = 0.0;
245
Austin Schuhf257f3c2019-10-27 21:00:43 -0700246 AOS_LOG(INFO, "Resetting\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800247 stuck_indexer_detector_->mutable_X_hat() = loop_->X_hat();
248 should_reset_ = false;
249 saturated_ = false;
250 }
251
252 if (!disable) {
253 ::Eigen::Matrix<double, 2, 1> goal_state =
254 profile_.Update(unprofiled_goal_(2, 0), unprofiled_goal_(3, 0));
255
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700256 constexpr double kDt =
James Kuszmaul61750662021-06-21 21:32:33 -0700257 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency);
Austin Schuh471d3b12017-03-25 20:49:14 -0700258
Austin Schuhd5ccb862017-03-11 22:06:36 -0800259 loop_->mutable_next_R(0, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700260 // TODO(austin): This might not handle saturation right, but I'm not sure I
261 // really care.
262 loop_->mutable_next_R(1, 0) = ::aos::Clip(
263 unprofiled_goal_(1, 0), loop_->R(1, 0) - kIndexerAcceleration * kDt,
264 loop_->R(1, 0) + kIndexerAcceleration * kDt);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800265 loop_->mutable_next_R(2, 0) = goal_state(0, 0);
266 loop_->mutable_next_R(3, 0) = goal_state(1, 0);
267 loop_->mutable_next_R(4, 0) = 0.0;
268 loop_->mutable_next_R(5, 0) = 0.0;
269 CapGoal("next R", &loop_->mutable_next_R());
270 }
271
272 // If the indexer goal velocity is low, switch to the indexer controller which
273 // won't fight to keep it moving at 0.
274 if (::std::abs(unprofiled_goal_(1, 0)) < 0.1) {
275 loop_->set_index(1);
276 } else {
277 loop_->set_index(0);
278 }
279 loop_->Update(disable);
280
281 if (!disable && loop_->U(1, 0) != loop_->U_uncapped(1, 0)) {
282 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
283 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
284 saturated_ = true;
285 } else {
286 saturated_ = false;
287 }
288
289 // Run the KF predict step.
290 stuck_indexer_detector_->UpdateObserver(loop_->U(),
James Kuszmaul61750662021-06-21 21:32:33 -0700291 ::frc971::controls::kLoopFrequency);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800292}
293
294bool ColumnProfiledSubsystem::CheckHardLimits() {
295 // Returns whether hard limits have been exceeded.
296
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700297 if (turret_position() > range_.upper_hard ||
298 turret_position() < range_.lower_hard) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700299 AOS_LOG(ERROR,
300 "ColumnProfiledSubsystem at %f out of bounds [%f, %f], ESTOPing\n",
301 turret_position(), range_.lower_hard, range_.upper_hard);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800302 return true;
303 }
304
305 return false;
306}
307
308void ColumnProfiledSubsystem::AdjustProfile(
Alex Perrycb7da4b2019-08-28 19:35:56 -0700309 const ::frc971::ProfileParameters *profile_parameters) {
310 AdjustProfile(
311 profile_parameters == nullptr ? 0.0 : profile_parameters->max_velocity(),
312 profile_parameters == nullptr ? 0.0
313 : profile_parameters->max_acceleration());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800314}
315
316void ColumnProfiledSubsystem::AdjustProfile(double max_angular_velocity,
317 double max_angular_acceleration) {
318 profile_.set_maximum_velocity(
319 ::frc971::control_loops::internal::UseUnlessZero(max_angular_velocity,
320 default_velocity_));
321 profile_.set_maximum_acceleration(
322 ::frc971::control_loops::internal::UseUnlessZero(max_angular_acceleration,
323 default_acceleration_));
324}
325
326double ColumnProfiledSubsystem::IndexerStuckVoltage() const {
327 // Compute the voltage from the control loop, excluding the voltage error
328 // term.
329 const double uncapped_applied_voltage =
330 uncapped_indexer_voltage() + X_hat(4, 0);
331 if (uncapped_applied_voltage < 0) {
332 return +stuck_indexer_X_hat_current_(4, 0);
333 } else {
334 return -stuck_indexer_X_hat_current_(4, 0);
335 }
336}
337bool ColumnProfiledSubsystem::IsIndexerStuck() const {
Austin Schuh546a0382017-04-16 19:10:18 -0700338 return IndexerStuckVoltage() > 4.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -0800339}
340
341void ColumnProfiledSubsystem::PartialIndexerReset() {
342 mutable_X_hat(4, 0) = 0.0;
343 stuck_indexer_detector_->mutable_X_hat(4, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700344 // Screw it, we are stuck. Reset the current goal to the current velocity so
345 // we start slewing faster to reverse if we have stopped.
346 loop_->mutable_R(1, 0) = X_hat(1, 0);
347 loop_->mutable_next_R(1, 0) = X_hat(1, 0);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800348}
349
350void ColumnProfiledSubsystem::PartialTurretReset() {
351 mutable_X_hat(5, 0) = 0.0;
352 stuck_indexer_detector_->mutable_X_hat(5, 0) = 0.0;
353}
354
Alex Perrycb7da4b2019-08-28 19:35:56 -0700355void ColumnProfiledSubsystem::PopulateIndexerStatus(
356 IndexerStatus::Builder *status_builder) {
357 status_builder->add_avg_angular_velocity(indexer_average_angular_velocity_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800358
Alex Perrycb7da4b2019-08-28 19:35:56 -0700359 status_builder->add_angular_velocity(X_hat_current_(1, 0));
360 status_builder->add_ready(indexer_ready_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800361
Alex Perrycb7da4b2019-08-28 19:35:56 -0700362 status_builder->add_voltage_error(X_hat_current_(4, 0));
363 status_builder->add_stuck_voltage_error(stuck_indexer_X_hat_current_(4, 0));
364 status_builder->add_position_error(indexer_position_error_);
365 status_builder->add_instantaneous_velocity(indexer_dt_velocity_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800366
Alex Perrycb7da4b2019-08-28 19:35:56 -0700367 status_builder->add_stuck(IsIndexerStuck());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800368
Alex Perrycb7da4b2019-08-28 19:35:56 -0700369 status_builder->add_stuck_voltage(IndexerStuckVoltage());
370}
371
372void ColumnProfiledSubsystem::PopulateTurretStatus(
373 TurretProfiledSubsystemStatus::Builder *status_builder,
374 flatbuffers::Offset<ColumnZeroingEstimator::State> estimator_state_offset) {
375 status_builder->add_zeroed(zeroed());
376
377 status_builder->add_position(X_hat(2, 0));
378 status_builder->add_velocity(X_hat(3, 0));
379 status_builder->add_goal_position(goal(2, 0));
380 status_builder->add_goal_velocity(goal(3, 0));
381 status_builder->add_unprofiled_goal_position(unprofiled_goal(2, 0));
382 status_builder->add_unprofiled_goal_velocity(unprofiled_goal(3, 0));
383 status_builder->add_voltage_error(X_hat(5, 0));
384 status_builder->add_calculated_velocity(
385 (turret_position() - turret_last_position_) /
James Kuszmaul61750662021-06-21 21:32:33 -0700386 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency));
Alex Perrycb7da4b2019-08-28 19:35:56 -0700387
388 status_builder->add_estimator_state(estimator_state_offset);
389
390 Eigen::Matrix<double, 6, 1> error = controller().error();
391 status_builder->add_position_power(controller().controller().K(0, 0) *
392 error(0, 0));
393 status_builder->add_velocity_power(controller().controller().K(0, 1) *
394 error(1, 0));
Austin Schuhd5ccb862017-03-11 22:06:36 -0800395}
396
Austin Schuhb6c5c852019-05-19 20:13:31 -0700397Column::Column(::aos::EventLoop *event_loop)
398 : vision_time_adjuster_(event_loop),
399 profiled_subsystem_(
Austin Schuhd5ccb862017-03-11 22:06:36 -0800400 ::std::unique_ptr<
401 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>(
402 new ::frc971::control_loops::SimpleCappedStateFeedbackLoop<
403 6, 2, 2>(MakeIntegralColumnLoop())),
404 constants::GetValues().column, constants::Values::kTurretRange, 7.0,
Austin Schuh25db1262017-04-05 19:39:55 -0700405 50.0),
406 vision_error_(constants::GetValues().vision_error) {}
Austin Schuhd5ccb862017-03-11 22:06:36 -0800407
408void Column::Reset() {
409 state_ = State::UNINITIALIZED;
410 indexer_state_ = IndexerState::RUNNING;
411 profiled_subsystem_.Reset();
412 // intake will automatically clear the minimum position on reset, so we don't
413 // need to do it here.
414 freeze_ = false;
415}
416
Alex Perrycb7da4b2019-08-28 19:35:56 -0700417std::pair<flatbuffers::Offset<IndexerStatus>,
418 flatbuffers::Offset<TurretProfiledSubsystemStatus>>
419Column::Iterate(const ::aos::monotonic_clock::time_point monotonic_now,
420 const IndexerGoalT *unsafe_indexer_goal,
421 const TurretGoal *unsafe_turret_goal,
422 const ColumnPosition *position,
423 const vision::VisionStatus *vision_status,
424 double *indexer_output, double *turret_output,
425 flatbuffers::FlatBufferBuilder *fbb, intake::Intake *intake) {
Austin Schuhd5ccb862017-03-11 22:06:36 -0800426 bool disable = turret_output == nullptr || indexer_output == nullptr;
427 profiled_subsystem_.Correct(*position);
428
Austin Schuh92715362019-07-07 20:47:45 -0700429 vision_time_adjuster_.Tick(monotonic_now, profiled_subsystem_.X_hat(2, 0),
430 vision_status);
Austin Schuhac76bb32017-03-22 22:34:26 -0700431
Austin Schuhd5ccb862017-03-11 22:06:36 -0800432 switch (state_) {
433 case State::UNINITIALIZED:
434 // Wait in the uninitialized state until the turret is initialized.
435 // Set the goals to where we are now so when we start back up, we don't
436 // jump.
437 profiled_subsystem_.ForceGoal(0.0, profiled_subsystem_.turret_position());
438 state_ = State::ZEROING_UNINITIALIZED;
439
440 // Fall through so we can start the zeroing process immediately.
James Kuszmaul3ae42262019-11-08 12:33:41 -0800441 [[fallthrough]];
Austin Schuhd5ccb862017-03-11 22:06:36 -0800442
443 case State::ZEROING_UNINITIALIZED:
444 // Set up the profile to be the zeroing profile.
445 profiled_subsystem_.AdjustProfile(0.50, 3);
446
447 // Force the intake out.
448 intake->set_min_position(kIntakeZeroingMinDistance);
449
450 if (disable) {
451 // If we are disabled, we want to reset the turret to stay where it
452 // currently is.
453 profiled_subsystem_.ForceGoal(0.0,
454 profiled_subsystem_.turret_position());
455 } else if (profiled_subsystem_.initialized()) {
456 // If we are initialized, there's no value in continuing to move so stop
457 // and wait on the intake.
458 profiled_subsystem_.set_indexer_unprofiled_goal(0.0);
459 } else {
460 // Spin slowly backwards.
461 profiled_subsystem_.set_indexer_unprofiled_goal(2.0);
462 }
463
464 // See if we are zeroed or initialized and far enough out and execute the
465 // proper state transition.
466 if (profiled_subsystem_.zeroed()) {
467 intake->clear_min_position();
468 state_ = State::RUNNING;
469 } else if (profiled_subsystem_.initialized() &&
470 intake->position() >
471 kIntakeZeroingMinDistance - kIntakeTolerance) {
472 if (profiled_subsystem_.turret_position() > 0) {
473 // We need to move in the negative direction.
474 state_ = State::ZEROING_NEGATIVE;
475 } else {
476 // We need to move in the positive direction.
477 state_ = State::ZEROING_POSITIVE;
478 }
479 }
480 break;
481
482 case State::ZEROING_POSITIVE:
483 // We are now going to be moving in the positive direction towards 0. If
484 // we get close enough, we'll zero.
485 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
486 intake->set_min_position(kIntakeZeroingMinDistance);
487
488 if (profiled_subsystem_.zeroed()) {
489 intake->clear_min_position();
490 state_ = State::RUNNING;
491 } else if (disable) {
492 // We are disabled, so pick back up from the current position.
493 profiled_subsystem_.ForceGoal(0.0,
494 profiled_subsystem_.turret_position());
495 } else if (profiled_subsystem_.turret_position() <
496 profiled_subsystem_.goal(2, 0) -
497 kStuckZeroingTrackingError ||
498 profiled_subsystem_.saturated()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700499 AOS_LOG(
500 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800501 "Turret stuck going positive, switching directions. At %f, goal "
502 "%f\n",
503 profiled_subsystem_.turret_position(),
504 profiled_subsystem_.goal(2, 0));
505 // The turret got too far behind. Declare it stuck and reverse.
506 profiled_subsystem_.AddOffset(0.0, 2.0 * M_PI);
507 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
508 profiled_subsystem_.ForceGoal(0.0,
509 profiled_subsystem_.turret_position());
510 profiled_subsystem_.PartialTurretReset();
511 profiled_subsystem_.PartialIndexerReset();
512 state_ = State::ZEROING_NEGATIVE;
513 }
514 break;
515
516 case State::ZEROING_NEGATIVE:
517 // We are now going to be moving in the negative direction towards 0. If
518 // we get close enough, we'll zero.
519 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
520 intake->set_min_position(kIntakeZeroingMinDistance);
521
522 if (profiled_subsystem_.zeroed()) {
523 intake->clear_min_position();
524 state_ = State::RUNNING;
525 } else if (disable) {
526 // We are disabled, so pick back up from the current position.
527 profiled_subsystem_.ForceGoal(0.0,
528 profiled_subsystem_.turret_position());
529 } else if (profiled_subsystem_.turret_position() >
530 profiled_subsystem_.goal(2, 0) +
531 kStuckZeroingTrackingError ||
532 profiled_subsystem_.saturated()) {
533 // The turret got too far behind. Declare it stuck and reverse.
Austin Schuhf257f3c2019-10-27 21:00:43 -0700534 AOS_LOG(
535 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800536 "Turret stuck going negative, switching directions. At %f, goal "
537 "%f\n",
538 profiled_subsystem_.turret_position(),
539 profiled_subsystem_.goal(2, 0));
540 profiled_subsystem_.AddOffset(0.0, -2.0 * M_PI);
541 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
542 profiled_subsystem_.ForceGoal(0.0,
543 profiled_subsystem_.turret_position());
544 profiled_subsystem_.PartialTurretReset();
545 profiled_subsystem_.PartialIndexerReset();
546 state_ = State::ZEROING_POSITIVE;
547 }
548 break;
549
550 case State::RUNNING: {
551 double starting_goal_angle = profiled_subsystem_.goal(2, 0);
552 if (disable) {
553 // Reset the profile to the current position so it starts from here when
554 // we get re-enabled.
555 profiled_subsystem_.ForceGoal(0.0,
556 profiled_subsystem_.turret_position());
557 }
558
559 if (unsafe_turret_goal && unsafe_indexer_goal) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700560 profiled_subsystem_.AdjustProfile(unsafe_turret_goal->profile_params());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800561 profiled_subsystem_.set_unprofiled_goal(
James Kuszmaul61750662021-06-21 21:32:33 -0700562 unsafe_indexer_goal->angular_velocity, unsafe_turret_goal->angle());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800563
Alex Perrycb7da4b2019-08-28 19:35:56 -0700564 if (unsafe_turret_goal->track()) {
Austin Schuhac76bb32017-03-22 22:34:26 -0700565 if (vision_time_adjuster_.valid()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700566 AOS_LOG(INFO, "Vision aligning to %f\n",
567 vision_time_adjuster_.goal());
Austin Schuhac76bb32017-03-22 22:34:26 -0700568 profiled_subsystem_.set_turret_unprofiled_goal(
Austin Schuh25db1262017-04-05 19:39:55 -0700569 vision_time_adjuster_.goal() + vision_error_);
Austin Schuhac76bb32017-03-22 22:34:26 -0700570 }
Austin Schuhd85c66e2017-04-16 10:50:33 -0700571 } else {
572 vision_time_adjuster_.ResetTime();
Austin Schuhac76bb32017-03-22 22:34:26 -0700573 }
574
Austin Schuhd5ccb862017-03-11 22:06:36 -0800575 if (freeze_) {
576 profiled_subsystem_.ForceGoal(unsafe_indexer_goal->angular_velocity,
577 starting_goal_angle);
578 }
579 }
580
581 // ESTOP if we hit the hard limits.
582 if (profiled_subsystem_.CheckHardLimits() ||
583 profiled_subsystem_.error()) {
584 state_ = State::ESTOP;
585 }
586 } break;
587
588 case State::ESTOP:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700589 AOS_LOG(ERROR, "Estop\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800590 disable = true;
591 break;
592 }
593
594 // Start indexing at the suggested velocity.
595 // If a "stuck" event is detected, reverse. Stay reversed until either
596 // unstuck, or 0.5 seconds have elapsed.
597 // Then, start going forwards. Don't detect stuck for 0.5 seconds.
Austin Schuhd5ccb862017-03-11 22:06:36 -0800598 switch (indexer_state_) {
599 case IndexerState::RUNNING:
600 // The velocity goal is already set above in this case, so leave it
601 // alone.
602
603 // If we are stuck and weren't just reversing, try reversing to unstick
604 // us. We don't want to chatter back and forth too fast if reversing
605 // isn't working.
606 if (profiled_subsystem_.IsIndexerStuck() &&
607 monotonic_now > kForwardTimeout + last_transition_time_) {
608 indexer_state_ = IndexerState::REVERSING;
609 last_transition_time_ = monotonic_now;
610 profiled_subsystem_.PartialIndexerReset();
Austin Schuhf257f3c2019-10-27 21:00:43 -0700611 AOS_LOG(INFO, "Partial indexer reset while going forwards\n");
612 AOS_LOG(INFO, "Indexer RUNNING -> REVERSING\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800613 }
614 break;
615 case IndexerState::REVERSING:
616 // "Reverse" "slowly".
617 profiled_subsystem_.set_indexer_unprofiled_goal(
618 -5.0 * ::aos::sign(profiled_subsystem_.unprofiled_goal(1, 0)));
619
620 // If we've timed out or are no longer stuck, try running again.
621 if ((!profiled_subsystem_.IsIndexerStuck() &&
622 monotonic_now > last_transition_time_ + kReverseMinTimeout) ||
623 monotonic_now > kReverseTimeout + last_transition_time_) {
624 indexer_state_ = IndexerState::RUNNING;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700625 AOS_LOG(INFO, "Indexer REVERSING -> RUNNING, stuck %d\n",
626 profiled_subsystem_.IsIndexerStuck());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800627
628 // Only reset if we got stuck going this way too.
629 if (monotonic_now > kReverseTimeout + last_transition_time_) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700630 AOS_LOG(INFO, "Partial indexer reset while reversing\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800631 profiled_subsystem_.PartialIndexerReset();
632 }
633 last_transition_time_ = monotonic_now;
634 }
635 break;
636 }
637
638 // Set the voltage limits.
639 const double max_voltage =
640 (state_ == State::RUNNING) ? kOperatingVoltage : kZeroingVoltage;
641
642 profiled_subsystem_.set_max_voltage({{max_voltage, max_voltage}});
643
644 // Calculate the loops for a cycle.
645 profiled_subsystem_.Update(disable);
646
647 // Write out all the voltages.
648 if (indexer_output) {
649 *indexer_output = profiled_subsystem_.indexer_voltage();
650 }
651 if (turret_output) {
652 *turret_output = profiled_subsystem_.turret_voltage();
653 }
654
655 // Save debug/internal state.
656 // TODO(austin): Save more.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700657 flatbuffers::Offset<ColumnZeroingEstimator::State>
658 column_estimator_state_offset =
659 profiled_subsystem_.EstimatorState(fbb, 0);
660
661 TurretProfiledSubsystemStatus::Builder turret_status_builder(*fbb);
662 profiled_subsystem_.PopulateTurretStatus(&turret_status_builder,
663 column_estimator_state_offset);
664 turret_status_builder.add_estopped((state_ == State::ESTOP));
665 turret_status_builder.add_state(static_cast<int32_t>(state_));
666 turret_status_builder.add_turret_encoder_angle(
667 profiled_subsystem_.turret_position());
Austin Schuhac76bb32017-03-22 22:34:26 -0700668 if (vision_time_adjuster_.valid()) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700669 turret_status_builder.add_vision_angle(vision_time_adjuster_.goal());
670 turret_status_builder.add_raw_vision_angle(
671 vision_time_adjuster_.most_recent_vision_reading());
672 turret_status_builder.add_vision_tracking(true);
Austin Schuhac76bb32017-03-22 22:34:26 -0700673 } else {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700674 turret_status_builder.add_vision_angle(
675 ::std::numeric_limits<double>::quiet_NaN());
676 turret_status_builder.add_vision_tracking(false);
Austin Schuhac76bb32017-03-22 22:34:26 -0700677 }
Austin Schuhd5ccb862017-03-11 22:06:36 -0800678
Alex Perrycb7da4b2019-08-28 19:35:56 -0700679 flatbuffers::Offset<TurretProfiledSubsystemStatus> turret_status_offset =
680 turret_status_builder.Finish();
681
682 IndexerStatus::Builder indexer_status_builder(*fbb);
683 profiled_subsystem_.PopulateIndexerStatus(&indexer_status_builder);
684
685 indexer_status_builder.add_state(static_cast<int32_t>(indexer_state_));
686
687 flatbuffers::Offset<IndexerStatus> indexer_status_offset =
688 indexer_status_builder.Finish();
689
690 return std::make_pair(indexer_status_offset, turret_status_offset);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800691}
692
693} // namespace column
694} // namespace superstructure
695} // namespace control_loops
696} // namespace y2017