blob: ab1ff7241576e38e7f004c593337bfa711854c15 [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"
9
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())),
51 profile_(::aos::controls::kLoopFrequency),
52 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 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();
Lee Mracek598a2452019-01-07 00:50:44 -0800121 Y_ << new_position.indexer.encoder, new_position.turret.encoder;
Austin Schuhd5ccb862017-03-11 22:06:36 -0800122 Y_ += offset_;
123 loop_->Correct(Y_);
124
Lee Mracek598a2452019-01-07 00:50:44 -0800125 indexer_history_[indexer_history_position_] = new_position.indexer.encoder;
Austin Schuhd5ccb862017-03-11 22:06:36 -0800126 indexer_history_position_ = (indexer_history_position_ + 1) % kHistoryLength;
127
128 indexer_dt_velocity_ =
Lee Mracek598a2452019-01-07 00:50:44 -0800129 (new_position.indexer.encoder - indexer_last_position_) /
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700130 ::aos::time::DurationInSeconds(::aos::controls::kLoopFrequency);
Lee Mracek598a2452019-01-07 00:50:44 -0800131 indexer_last_position_ = new_position.indexer.encoder;
Austin Schuhd5ccb862017-03-11 22:06:36 -0800132
133 stuck_indexer_detector_->Correct(Y_);
134
135 // Compute the oldest point in the history.
136 const int indexer_oldest_history_position =
137 ((indexer_history_position_ == 0) ? kHistoryLength
138 : indexer_history_position_) -
139 1;
140
141 // Compute the distance moved over that time period.
142 indexer_average_angular_velocity_ =
143 (indexer_history_[indexer_oldest_history_position] -
144 indexer_history_[indexer_history_position_]) /
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700145 (::aos::time::DurationInSeconds(::aos::controls::kLoopFrequency) *
Austin Schuhd5ccb862017-03-11 22:06:36 -0800146 static_cast<double>(kHistoryLength - 1));
147
148 // Ready if average angular velocity is close to the goal.
149 indexer_error_ = indexer_average_angular_velocity_ - unprofiled_goal_(1, 0);
150
151 indexer_ready_ =
152 std::abs(indexer_error_) < kTolerance && unprofiled_goal_(1, 0) > 0.1;
153
154 // Pull state from the profiled subsystem.
155 X_hat_current_ = controller().X_hat();
156 stuck_indexer_X_hat_current_ = stuck_indexer_detector_->X_hat();
157 indexer_position_error_ = X_hat_current_(0, 0) - Y(0, 0);
158}
159
160void ColumnProfiledSubsystem::CapGoal(const char *name,
161 Eigen::Matrix<double, 6, 1> *goal) {
162 // Limit the goal to min/max allowable positions.
163 if (zeroed()) {
164 if ((*goal)(2, 0) > range_.upper) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700165 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
166 range_.upper);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800167 (*goal)(2, 0) = range_.upper;
168 }
169 if ((*goal)(2, 0) < range_.lower) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700170 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
171 range_.lower);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800172 (*goal)(2, 0) = range_.lower;
173 }
174 } else {
175 const double kMaxRange = range().upper_hard - range().lower_hard;
176
177 // Limit the goal to min/max allowable positions much less agressively.
178 // We don't know where the limits are, so we have to let the user move far
179 // enough to find them (and the index pulse which might be right next to
180 // one).
181 // Upper - lower hard may be a bit generous, but we are moving slow.
182
183 if ((*goal)(2, 0) > kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700184 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
185 kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800186 (*goal)(2, 0) = kMaxRange;
187 }
188 if ((*goal)(2, 0) < -kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700189 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
190 -kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800191 (*goal)(2, 0) = -kMaxRange;
192 }
193 }
194}
195
196void ColumnProfiledSubsystem::ForceGoal(double goal_velocity, double goal) {
197 set_unprofiled_goal(goal_velocity, goal);
198 loop_->mutable_R() = unprofiled_goal_;
199 loop_->mutable_next_R() = loop_->R();
200
201 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
202 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
203}
204
205void ColumnProfiledSubsystem::set_unprofiled_goal(double goal_velocity,
206 double unprofiled_goal) {
207 unprofiled_goal_(0, 0) = 0.0;
208 unprofiled_goal_(1, 0) = goal_velocity;
209 unprofiled_goal_(2, 0) = unprofiled_goal;
210 unprofiled_goal_(3, 0) = 0.0;
211 unprofiled_goal_(4, 0) = 0.0;
212 unprofiled_goal_(5, 0) = 0.0;
213 CapGoal("unprofiled R", &unprofiled_goal_);
214}
215
216void ColumnProfiledSubsystem::set_indexer_unprofiled_goal(
217 double goal_velocity) {
218 unprofiled_goal_(0, 0) = 0.0;
219 unprofiled_goal_(1, 0) = goal_velocity;
220 unprofiled_goal_(4, 0) = 0.0;
221 CapGoal("unprofiled R", &unprofiled_goal_);
222}
223
224void ColumnProfiledSubsystem::set_turret_unprofiled_goal(
225 double unprofiled_goal) {
226 unprofiled_goal_(2, 0) = unprofiled_goal;
227 unprofiled_goal_(3, 0) = 0.0;
228 unprofiled_goal_(5, 0) = 0.0;
229 CapGoal("unprofiled R", &unprofiled_goal_);
230}
231
232void ColumnProfiledSubsystem::Update(bool disable) {
233 // TODO(austin): If we really need to reset, reset the profiles, etc. That'll
234 // be covered by the layer above when disabled though, so we can get away with
235 // not doing it yet.
236 if (should_reset_) {
237 loop_->mutable_X_hat(0, 0) = Y_(0, 0);
238 loop_->mutable_X_hat(1, 0) = 0.0;
239 loop_->mutable_X_hat(2, 0) = Y_(0, 0) + Y_(1, 0);
240 loop_->mutable_X_hat(3, 0) = 0.0;
241 loop_->mutable_X_hat(4, 0) = 0.0;
242 loop_->mutable_X_hat(5, 0) = 0.0;
243
Austin Schuhf257f3c2019-10-27 21:00:43 -0700244 AOS_LOG(INFO, "Resetting\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800245 stuck_indexer_detector_->mutable_X_hat() = loop_->X_hat();
246 should_reset_ = false;
247 saturated_ = false;
248 }
249
250 if (!disable) {
251 ::Eigen::Matrix<double, 2, 1> goal_state =
252 profile_.Update(unprofiled_goal_(2, 0), unprofiled_goal_(3, 0));
253
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700254 constexpr double kDt =
255 ::aos::time::DurationInSeconds(::aos::controls::kLoopFrequency);
Austin Schuh471d3b12017-03-25 20:49:14 -0700256
Austin Schuhd5ccb862017-03-11 22:06:36 -0800257 loop_->mutable_next_R(0, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700258 // TODO(austin): This might not handle saturation right, but I'm not sure I
259 // really care.
260 loop_->mutable_next_R(1, 0) = ::aos::Clip(
261 unprofiled_goal_(1, 0), loop_->R(1, 0) - kIndexerAcceleration * kDt,
262 loop_->R(1, 0) + kIndexerAcceleration * kDt);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800263 loop_->mutable_next_R(2, 0) = goal_state(0, 0);
264 loop_->mutable_next_R(3, 0) = goal_state(1, 0);
265 loop_->mutable_next_R(4, 0) = 0.0;
266 loop_->mutable_next_R(5, 0) = 0.0;
267 CapGoal("next R", &loop_->mutable_next_R());
268 }
269
270 // If the indexer goal velocity is low, switch to the indexer controller which
271 // won't fight to keep it moving at 0.
272 if (::std::abs(unprofiled_goal_(1, 0)) < 0.1) {
273 loop_->set_index(1);
274 } else {
275 loop_->set_index(0);
276 }
277 loop_->Update(disable);
278
279 if (!disable && loop_->U(1, 0) != loop_->U_uncapped(1, 0)) {
280 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
281 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
282 saturated_ = true;
283 } else {
284 saturated_ = false;
285 }
286
287 // Run the KF predict step.
288 stuck_indexer_detector_->UpdateObserver(loop_->U(),
289 ::aos::controls::kLoopFrequency);
290}
291
292bool ColumnProfiledSubsystem::CheckHardLimits() {
293 // Returns whether hard limits have been exceeded.
294
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700295 if (turret_position() > range_.upper_hard ||
296 turret_position() < range_.lower_hard) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700297 AOS_LOG(ERROR,
298 "ColumnProfiledSubsystem at %f out of bounds [%f, %f], ESTOPing\n",
299 turret_position(), range_.lower_hard, range_.upper_hard);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800300 return true;
301 }
302
303 return false;
304}
305
306void ColumnProfiledSubsystem::AdjustProfile(
307 const ::frc971::ProfileParameters &profile_parameters) {
308 AdjustProfile(profile_parameters.max_velocity,
309 profile_parameters.max_acceleration);
310}
311
312void ColumnProfiledSubsystem::AdjustProfile(double max_angular_velocity,
313 double max_angular_acceleration) {
314 profile_.set_maximum_velocity(
315 ::frc971::control_loops::internal::UseUnlessZero(max_angular_velocity,
316 default_velocity_));
317 profile_.set_maximum_acceleration(
318 ::frc971::control_loops::internal::UseUnlessZero(max_angular_acceleration,
319 default_acceleration_));
320}
321
322double ColumnProfiledSubsystem::IndexerStuckVoltage() const {
323 // Compute the voltage from the control loop, excluding the voltage error
324 // term.
325 const double uncapped_applied_voltage =
326 uncapped_indexer_voltage() + X_hat(4, 0);
327 if (uncapped_applied_voltage < 0) {
328 return +stuck_indexer_X_hat_current_(4, 0);
329 } else {
330 return -stuck_indexer_X_hat_current_(4, 0);
331 }
332}
333bool ColumnProfiledSubsystem::IsIndexerStuck() const {
Austin Schuh546a0382017-04-16 19:10:18 -0700334 return IndexerStuckVoltage() > 4.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -0800335}
336
337void ColumnProfiledSubsystem::PartialIndexerReset() {
338 mutable_X_hat(4, 0) = 0.0;
339 stuck_indexer_detector_->mutable_X_hat(4, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700340 // Screw it, we are stuck. Reset the current goal to the current velocity so
341 // we start slewing faster to reverse if we have stopped.
342 loop_->mutable_R(1, 0) = X_hat(1, 0);
343 loop_->mutable_next_R(1, 0) = X_hat(1, 0);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800344}
345
346void ColumnProfiledSubsystem::PartialTurretReset() {
347 mutable_X_hat(5, 0) = 0.0;
348 stuck_indexer_detector_->mutable_X_hat(5, 0) = 0.0;
349}
350
351void ColumnProfiledSubsystem::PopulateIndexerStatus(IndexerStatus *status) {
352 status->avg_angular_velocity = indexer_average_angular_velocity_;
353
354 status->angular_velocity = X_hat_current_(1, 0);
355 status->ready = indexer_ready_;
356
357 status->voltage_error = X_hat_current_(4, 0);
358 status->stuck_voltage_error = stuck_indexer_X_hat_current_(4, 0);
359 status->position_error = indexer_position_error_;
360 status->instantaneous_velocity = indexer_dt_velocity_;
361
362 status->stuck = IsIndexerStuck();
363
364 status->stuck_voltage = IndexerStuckVoltage();
365}
366
Austin Schuhb6c5c852019-05-19 20:13:31 -0700367Column::Column(::aos::EventLoop *event_loop)
368 : vision_time_adjuster_(event_loop),
369 profiled_subsystem_(
Austin Schuhd5ccb862017-03-11 22:06:36 -0800370 ::std::unique_ptr<
371 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>(
372 new ::frc971::control_loops::SimpleCappedStateFeedbackLoop<
373 6, 2, 2>(MakeIntegralColumnLoop())),
374 constants::GetValues().column, constants::Values::kTurretRange, 7.0,
Austin Schuh25db1262017-04-05 19:39:55 -0700375 50.0),
376 vision_error_(constants::GetValues().vision_error) {}
Austin Schuhd5ccb862017-03-11 22:06:36 -0800377
378void Column::Reset() {
379 state_ = State::UNINITIALIZED;
380 indexer_state_ = IndexerState::RUNNING;
381 profiled_subsystem_.Reset();
382 // intake will automatically clear the minimum position on reset, so we don't
383 // need to do it here.
384 freeze_ = false;
385}
386
Austin Schuh92715362019-07-07 20:47:45 -0700387void Column::Iterate(const ::aos::monotonic_clock::time_point monotonic_now,
388 const control_loops::IndexerGoal *unsafe_indexer_goal,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800389 const control_loops::TurretGoal *unsafe_turret_goal,
Philipp Schrader8e3ac0f2017-04-09 23:36:17 +0000390 const ColumnPosition *position,
391 const vision::VisionStatus *vision_status,
392 double *indexer_output, double *turret_output,
393 IndexerStatus *indexer_status,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800394 TurretProfiledSubsystemStatus *turret_status,
395 intake::Intake *intake) {
396 bool disable = turret_output == nullptr || indexer_output == nullptr;
397 profiled_subsystem_.Correct(*position);
398
Austin Schuh92715362019-07-07 20:47:45 -0700399 vision_time_adjuster_.Tick(monotonic_now, profiled_subsystem_.X_hat(2, 0),
400 vision_status);
Austin Schuhac76bb32017-03-22 22:34:26 -0700401
Austin Schuhd5ccb862017-03-11 22:06:36 -0800402 switch (state_) {
403 case State::UNINITIALIZED:
404 // Wait in the uninitialized state until the turret is initialized.
405 // Set the goals to where we are now so when we start back up, we don't
406 // jump.
407 profiled_subsystem_.ForceGoal(0.0, profiled_subsystem_.turret_position());
408 state_ = State::ZEROING_UNINITIALIZED;
409
410 // Fall through so we can start the zeroing process immediately.
411
412 case State::ZEROING_UNINITIALIZED:
413 // Set up the profile to be the zeroing profile.
414 profiled_subsystem_.AdjustProfile(0.50, 3);
415
416 // Force the intake out.
417 intake->set_min_position(kIntakeZeroingMinDistance);
418
419 if (disable) {
420 // If we are disabled, we want to reset the turret to stay where it
421 // currently is.
422 profiled_subsystem_.ForceGoal(0.0,
423 profiled_subsystem_.turret_position());
424 } else if (profiled_subsystem_.initialized()) {
425 // If we are initialized, there's no value in continuing to move so stop
426 // and wait on the intake.
427 profiled_subsystem_.set_indexer_unprofiled_goal(0.0);
428 } else {
429 // Spin slowly backwards.
430 profiled_subsystem_.set_indexer_unprofiled_goal(2.0);
431 }
432
433 // See if we are zeroed or initialized and far enough out and execute the
434 // proper state transition.
435 if (profiled_subsystem_.zeroed()) {
436 intake->clear_min_position();
437 state_ = State::RUNNING;
438 } else if (profiled_subsystem_.initialized() &&
439 intake->position() >
440 kIntakeZeroingMinDistance - kIntakeTolerance) {
441 if (profiled_subsystem_.turret_position() > 0) {
442 // We need to move in the negative direction.
443 state_ = State::ZEROING_NEGATIVE;
444 } else {
445 // We need to move in the positive direction.
446 state_ = State::ZEROING_POSITIVE;
447 }
448 }
449 break;
450
451 case State::ZEROING_POSITIVE:
452 // We are now going to be moving in the positive direction towards 0. If
453 // we get close enough, we'll zero.
454 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
455 intake->set_min_position(kIntakeZeroingMinDistance);
456
457 if (profiled_subsystem_.zeroed()) {
458 intake->clear_min_position();
459 state_ = State::RUNNING;
460 } else if (disable) {
461 // We are disabled, so pick back up from the current position.
462 profiled_subsystem_.ForceGoal(0.0,
463 profiled_subsystem_.turret_position());
464 } else if (profiled_subsystem_.turret_position() <
465 profiled_subsystem_.goal(2, 0) -
466 kStuckZeroingTrackingError ||
467 profiled_subsystem_.saturated()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700468 AOS_LOG(
469 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800470 "Turret stuck going positive, switching directions. At %f, goal "
471 "%f\n",
472 profiled_subsystem_.turret_position(),
473 profiled_subsystem_.goal(2, 0));
474 // The turret got too far behind. Declare it stuck and reverse.
475 profiled_subsystem_.AddOffset(0.0, 2.0 * M_PI);
476 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
477 profiled_subsystem_.ForceGoal(0.0,
478 profiled_subsystem_.turret_position());
479 profiled_subsystem_.PartialTurretReset();
480 profiled_subsystem_.PartialIndexerReset();
481 state_ = State::ZEROING_NEGATIVE;
482 }
483 break;
484
485 case State::ZEROING_NEGATIVE:
486 // We are now going to be moving in the negative direction towards 0. If
487 // we get close enough, we'll zero.
488 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
489 intake->set_min_position(kIntakeZeroingMinDistance);
490
491 if (profiled_subsystem_.zeroed()) {
492 intake->clear_min_position();
493 state_ = State::RUNNING;
494 } else if (disable) {
495 // We are disabled, so pick back up from the current position.
496 profiled_subsystem_.ForceGoal(0.0,
497 profiled_subsystem_.turret_position());
498 } else if (profiled_subsystem_.turret_position() >
499 profiled_subsystem_.goal(2, 0) +
500 kStuckZeroingTrackingError ||
501 profiled_subsystem_.saturated()) {
502 // The turret got too far behind. Declare it stuck and reverse.
Austin Schuhf257f3c2019-10-27 21:00:43 -0700503 AOS_LOG(
504 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800505 "Turret stuck going negative, switching directions. At %f, goal "
506 "%f\n",
507 profiled_subsystem_.turret_position(),
508 profiled_subsystem_.goal(2, 0));
509 profiled_subsystem_.AddOffset(0.0, -2.0 * M_PI);
510 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
511 profiled_subsystem_.ForceGoal(0.0,
512 profiled_subsystem_.turret_position());
513 profiled_subsystem_.PartialTurretReset();
514 profiled_subsystem_.PartialIndexerReset();
515 state_ = State::ZEROING_POSITIVE;
516 }
517 break;
518
519 case State::RUNNING: {
520 double starting_goal_angle = profiled_subsystem_.goal(2, 0);
521 if (disable) {
522 // Reset the profile to the current position so it starts from here when
523 // we get re-enabled.
524 profiled_subsystem_.ForceGoal(0.0,
525 profiled_subsystem_.turret_position());
526 }
527
528 if (unsafe_turret_goal && unsafe_indexer_goal) {
529 profiled_subsystem_.AdjustProfile(unsafe_turret_goal->profile_params);
530 profiled_subsystem_.set_unprofiled_goal(
531 unsafe_indexer_goal->angular_velocity, unsafe_turret_goal->angle);
532
Austin Schuhac76bb32017-03-22 22:34:26 -0700533 if (unsafe_turret_goal->track) {
534 if (vision_time_adjuster_.valid()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700535 AOS_LOG(INFO, "Vision aligning to %f\n",
536 vision_time_adjuster_.goal());
Austin Schuhac76bb32017-03-22 22:34:26 -0700537 profiled_subsystem_.set_turret_unprofiled_goal(
Austin Schuh25db1262017-04-05 19:39:55 -0700538 vision_time_adjuster_.goal() + vision_error_);
Austin Schuhac76bb32017-03-22 22:34:26 -0700539 }
Austin Schuhd85c66e2017-04-16 10:50:33 -0700540 } else {
541 vision_time_adjuster_.ResetTime();
Austin Schuhac76bb32017-03-22 22:34:26 -0700542 }
543
Austin Schuhd5ccb862017-03-11 22:06:36 -0800544 if (freeze_) {
545 profiled_subsystem_.ForceGoal(unsafe_indexer_goal->angular_velocity,
546 starting_goal_angle);
547 }
548 }
549
550 // ESTOP if we hit the hard limits.
551 if (profiled_subsystem_.CheckHardLimits() ||
552 profiled_subsystem_.error()) {
553 state_ = State::ESTOP;
554 }
555 } break;
556
557 case State::ESTOP:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700558 AOS_LOG(ERROR, "Estop\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800559 disable = true;
560 break;
561 }
562
563 // Start indexing at the suggested velocity.
564 // If a "stuck" event is detected, reverse. Stay reversed until either
565 // unstuck, or 0.5 seconds have elapsed.
566 // Then, start going forwards. Don't detect stuck for 0.5 seconds.
Austin Schuhd5ccb862017-03-11 22:06:36 -0800567 switch (indexer_state_) {
568 case IndexerState::RUNNING:
569 // The velocity goal is already set above in this case, so leave it
570 // alone.
571
572 // If we are stuck and weren't just reversing, try reversing to unstick
573 // us. We don't want to chatter back and forth too fast if reversing
574 // isn't working.
575 if (profiled_subsystem_.IsIndexerStuck() &&
576 monotonic_now > kForwardTimeout + last_transition_time_) {
577 indexer_state_ = IndexerState::REVERSING;
578 last_transition_time_ = monotonic_now;
579 profiled_subsystem_.PartialIndexerReset();
Austin Schuhf257f3c2019-10-27 21:00:43 -0700580 AOS_LOG(INFO, "Partial indexer reset while going forwards\n");
581 AOS_LOG(INFO, "Indexer RUNNING -> REVERSING\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800582 }
583 break;
584 case IndexerState::REVERSING:
585 // "Reverse" "slowly".
586 profiled_subsystem_.set_indexer_unprofiled_goal(
587 -5.0 * ::aos::sign(profiled_subsystem_.unprofiled_goal(1, 0)));
588
589 // If we've timed out or are no longer stuck, try running again.
590 if ((!profiled_subsystem_.IsIndexerStuck() &&
591 monotonic_now > last_transition_time_ + kReverseMinTimeout) ||
592 monotonic_now > kReverseTimeout + last_transition_time_) {
593 indexer_state_ = IndexerState::RUNNING;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700594 AOS_LOG(INFO, "Indexer REVERSING -> RUNNING, stuck %d\n",
595 profiled_subsystem_.IsIndexerStuck());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800596
597 // Only reset if we got stuck going this way too.
598 if (monotonic_now > kReverseTimeout + last_transition_time_) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700599 AOS_LOG(INFO, "Partial indexer reset while reversing\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800600 profiled_subsystem_.PartialIndexerReset();
601 }
602 last_transition_time_ = monotonic_now;
603 }
604 break;
605 }
606
607 // Set the voltage limits.
608 const double max_voltage =
609 (state_ == State::RUNNING) ? kOperatingVoltage : kZeroingVoltage;
610
611 profiled_subsystem_.set_max_voltage({{max_voltage, max_voltage}});
612
613 // Calculate the loops for a cycle.
614 profiled_subsystem_.Update(disable);
615
616 // Write out all the voltages.
617 if (indexer_output) {
618 *indexer_output = profiled_subsystem_.indexer_voltage();
619 }
620 if (turret_output) {
621 *turret_output = profiled_subsystem_.turret_voltage();
622 }
623
624 // Save debug/internal state.
625 // TODO(austin): Save more.
626 turret_status->zeroed = profiled_subsystem_.zeroed();
627 profiled_subsystem_.PopulateTurretStatus(turret_status);
628 turret_status->estopped = (state_ == State::ESTOP);
629 turret_status->state = static_cast<int32_t>(state_);
Austin Schuheb5c22e2017-04-09 18:30:28 -0700630 turret_status->turret_encoder_angle = profiled_subsystem_.turret_position();
Austin Schuhac76bb32017-03-22 22:34:26 -0700631 if (vision_time_adjuster_.valid()) {
632 turret_status->vision_angle = vision_time_adjuster_.goal();
633 turret_status->raw_vision_angle =
634 vision_time_adjuster_.most_recent_vision_reading();
635 turret_status->vision_tracking = true;
636 } else {
637 turret_status->vision_angle = ::std::numeric_limits<double>::quiet_NaN();
638 turret_status->vision_tracking = false;
639 }
Austin Schuhd5ccb862017-03-11 22:06:36 -0800640
641 profiled_subsystem_.PopulateIndexerStatus(indexer_status);
642 indexer_status->state = static_cast<int32_t>(indexer_state_);
643}
644
645} // namespace column
646} // namespace superstructure
647} // namespace control_loops
648} // namespace y2017