blob: e57a646764b6213f89e9bc48f4b44e497e62d2f1 [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
10#include "aos/common/commonmath.h"
11#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 Schuh471d3b12017-03-25 20:49:14 -070028constexpr double kIndexerAcceleration = 100.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -080029constexpr chrono::milliseconds kForwardTimeout{500};
30constexpr chrono::milliseconds kReverseTimeout{500};
31constexpr chrono::milliseconds kReverseMinTimeout{100};
32} // 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
74 LOG(INFO, "Adjusting indexer offset from %f to %f\n", offset_(0, 0),
75 indexer_offset);
76 LOG(INFO, "Adjusting turret offset from %f to %f\n", offset_(1, 0),
77 turret_offset);
78
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()) {
102 LOG(ERROR, "zeroing error\n");
103 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();
121 Y_ << new_position.indexer.position, new_position.turret.position;
122 Y_ += offset_;
123 loop_->Correct(Y_);
124
125 indexer_history_[indexer_history_position_] = new_position.indexer.position;
126 indexer_history_position_ = (indexer_history_position_ + 1) % kHistoryLength;
127
128 indexer_dt_velocity_ =
129 (new_position.indexer.position - indexer_last_position_) /
130 chrono::duration_cast<chrono::duration<double>>(
131 ::aos::controls::kLoopFrequency)
132 .count();
133 indexer_last_position_ = new_position.indexer.position;
134
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_]) /
147 (chrono::duration_cast<chrono::duration<double>>(
148 ::aos::controls::kLoopFrequency)
149 .count() *
150 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) {
169 LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
170 range_.upper);
171 (*goal)(2, 0) = range_.upper;
172 }
173 if ((*goal)(2, 0) < range_.lower) {
174 LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
175 range_.lower);
176 (*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) {
188 LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
189 kMaxRange);
190 (*goal)(2, 0) = kMaxRange;
191 }
192 if ((*goal)(2, 0) < -kMaxRange) {
193 LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
194 -kMaxRange);
195 (*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
248 LOG(INFO, "Resetting\n");
249 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
Austin Schuh471d3b12017-03-25 20:49:14 -0700258 constexpr double kDt = chrono::duration_cast<chrono::duration<double>>(
259 ::aos::controls::kLoopFrequency)
260 .count();
261
Austin Schuhd5ccb862017-03-11 22:06:36 -0800262 loop_->mutable_next_R(0, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700263 // TODO(austin): This might not handle saturation right, but I'm not sure I
264 // really care.
265 loop_->mutable_next_R(1, 0) = ::aos::Clip(
266 unprofiled_goal_(1, 0), loop_->R(1, 0) - kIndexerAcceleration * kDt,
267 loop_->R(1, 0) + kIndexerAcceleration * kDt);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800268 loop_->mutable_next_R(2, 0) = goal_state(0, 0);
269 loop_->mutable_next_R(3, 0) = goal_state(1, 0);
270 loop_->mutable_next_R(4, 0) = 0.0;
271 loop_->mutable_next_R(5, 0) = 0.0;
272 CapGoal("next R", &loop_->mutable_next_R());
273 }
274
275 // If the indexer goal velocity is low, switch to the indexer controller which
276 // won't fight to keep it moving at 0.
277 if (::std::abs(unprofiled_goal_(1, 0)) < 0.1) {
278 loop_->set_index(1);
279 } else {
280 loop_->set_index(0);
281 }
282 loop_->Update(disable);
283
284 if (!disable && loop_->U(1, 0) != loop_->U_uncapped(1, 0)) {
285 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
286 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
287 saturated_ = true;
288 } else {
289 saturated_ = false;
290 }
291
292 // Run the KF predict step.
293 stuck_indexer_detector_->UpdateObserver(loop_->U(),
294 ::aos::controls::kLoopFrequency);
295}
296
297bool ColumnProfiledSubsystem::CheckHardLimits() {
298 // Returns whether hard limits have been exceeded.
299
300 if (turret_position() > range_.upper_hard || turret_position() < range_.lower_hard) {
301 LOG(ERROR,
302 "ColumnProfiledSubsystem at %f out of bounds [%f, %f], ESTOPing\n",
303 turret_position(), range_.lower_hard, range_.upper_hard);
304 return true;
305 }
306
307 return false;
308}
309
310void ColumnProfiledSubsystem::AdjustProfile(
311 const ::frc971::ProfileParameters &profile_parameters) {
312 AdjustProfile(profile_parameters.max_velocity,
313 profile_parameters.max_acceleration);
314}
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 {
338 return IndexerStuckVoltage() > 4.0;
339}
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
355void ColumnProfiledSubsystem::PopulateIndexerStatus(IndexerStatus *status) {
356 status->avg_angular_velocity = indexer_average_angular_velocity_;
357
358 status->angular_velocity = X_hat_current_(1, 0);
359 status->ready = indexer_ready_;
360
361 status->voltage_error = X_hat_current_(4, 0);
362 status->stuck_voltage_error = stuck_indexer_X_hat_current_(4, 0);
363 status->position_error = indexer_position_error_;
364 status->instantaneous_velocity = indexer_dt_velocity_;
365
366 status->stuck = IsIndexerStuck();
367
368 status->stuck_voltage = IndexerStuckVoltage();
369}
370
371Column::Column()
372 : profiled_subsystem_(
373 ::std::unique_ptr<
374 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>(
375 new ::frc971::control_loops::SimpleCappedStateFeedbackLoop<
376 6, 2, 2>(MakeIntegralColumnLoop())),
377 constants::GetValues().column, constants::Values::kTurretRange, 7.0,
378 50.0) {}
379
380void Column::Reset() {
381 state_ = State::UNINITIALIZED;
382 indexer_state_ = IndexerState::RUNNING;
383 profiled_subsystem_.Reset();
384 // intake will automatically clear the minimum position on reset, so we don't
385 // need to do it here.
386 freeze_ = false;
387}
388
389void Column::Iterate(const control_loops::IndexerGoal *unsafe_indexer_goal,
390 const control_loops::TurretGoal *unsafe_turret_goal,
391 const ColumnPosition *position, double *indexer_output,
392 double *turret_output, IndexerStatus *indexer_status,
393 TurretProfiledSubsystemStatus *turret_status,
394 intake::Intake *intake) {
395 bool disable = turret_output == nullptr || indexer_output == nullptr;
396 profiled_subsystem_.Correct(*position);
397
Austin Schuhac76bb32017-03-22 22:34:26 -0700398 vision_time_adjuster_.Tick(::aos::monotonic_clock::now(),
399 profiled_subsystem_.X_hat(2, 0));
400
Austin Schuhd5ccb862017-03-11 22:06:36 -0800401 switch (state_) {
402 case State::UNINITIALIZED:
403 // Wait in the uninitialized state until the turret is initialized.
404 // Set the goals to where we are now so when we start back up, we don't
405 // jump.
406 profiled_subsystem_.ForceGoal(0.0, profiled_subsystem_.turret_position());
407 state_ = State::ZEROING_UNINITIALIZED;
408
409 // Fall through so we can start the zeroing process immediately.
410
411 case State::ZEROING_UNINITIALIZED:
412 // Set up the profile to be the zeroing profile.
413 profiled_subsystem_.AdjustProfile(0.50, 3);
414
415 // Force the intake out.
416 intake->set_min_position(kIntakeZeroingMinDistance);
417
418 if (disable) {
419 // If we are disabled, we want to reset the turret to stay where it
420 // currently is.
421 profiled_subsystem_.ForceGoal(0.0,
422 profiled_subsystem_.turret_position());
423 } else if (profiled_subsystem_.initialized()) {
424 // If we are initialized, there's no value in continuing to move so stop
425 // and wait on the intake.
426 profiled_subsystem_.set_indexer_unprofiled_goal(0.0);
427 } else {
428 // Spin slowly backwards.
429 profiled_subsystem_.set_indexer_unprofiled_goal(2.0);
430 }
431
432 // See if we are zeroed or initialized and far enough out and execute the
433 // proper state transition.
434 if (profiled_subsystem_.zeroed()) {
435 intake->clear_min_position();
436 state_ = State::RUNNING;
437 } else if (profiled_subsystem_.initialized() &&
438 intake->position() >
439 kIntakeZeroingMinDistance - kIntakeTolerance) {
440 if (profiled_subsystem_.turret_position() > 0) {
441 // We need to move in the negative direction.
442 state_ = State::ZEROING_NEGATIVE;
443 } else {
444 // We need to move in the positive direction.
445 state_ = State::ZEROING_POSITIVE;
446 }
447 }
448 break;
449
450 case State::ZEROING_POSITIVE:
451 // We are now going to be moving in the positive direction towards 0. If
452 // we get close enough, we'll zero.
453 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
454 intake->set_min_position(kIntakeZeroingMinDistance);
455
456 if (profiled_subsystem_.zeroed()) {
457 intake->clear_min_position();
458 state_ = State::RUNNING;
459 } else if (disable) {
460 // We are disabled, so pick back up from the current position.
461 profiled_subsystem_.ForceGoal(0.0,
462 profiled_subsystem_.turret_position());
463 } else if (profiled_subsystem_.turret_position() <
464 profiled_subsystem_.goal(2, 0) -
465 kStuckZeroingTrackingError ||
466 profiled_subsystem_.saturated()) {
467 LOG(INFO,
468 "Turret stuck going positive, switching directions. At %f, goal "
469 "%f\n",
470 profiled_subsystem_.turret_position(),
471 profiled_subsystem_.goal(2, 0));
472 // The turret got too far behind. Declare it stuck and reverse.
473 profiled_subsystem_.AddOffset(0.0, 2.0 * M_PI);
474 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
475 profiled_subsystem_.ForceGoal(0.0,
476 profiled_subsystem_.turret_position());
477 profiled_subsystem_.PartialTurretReset();
478 profiled_subsystem_.PartialIndexerReset();
479 state_ = State::ZEROING_NEGATIVE;
480 }
481 break;
482
483 case State::ZEROING_NEGATIVE:
484 // We are now going to be moving in the negative 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()) {
500 // The turret got too far behind. Declare it stuck and reverse.
501 LOG(INFO,
502 "Turret stuck going negative, switching directions. At %f, goal "
503 "%f\n",
504 profiled_subsystem_.turret_position(),
505 profiled_subsystem_.goal(2, 0));
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_POSITIVE;
513 }
514 break;
515
516 case State::RUNNING: {
517 double starting_goal_angle = profiled_subsystem_.goal(2, 0);
518 if (disable) {
519 // Reset the profile to the current position so it starts from here when
520 // we get re-enabled.
521 profiled_subsystem_.ForceGoal(0.0,
522 profiled_subsystem_.turret_position());
523 }
524
525 if (unsafe_turret_goal && unsafe_indexer_goal) {
526 profiled_subsystem_.AdjustProfile(unsafe_turret_goal->profile_params);
527 profiled_subsystem_.set_unprofiled_goal(
528 unsafe_indexer_goal->angular_velocity, unsafe_turret_goal->angle);
529
Austin Schuhac76bb32017-03-22 22:34:26 -0700530 if (unsafe_turret_goal->track) {
531 if (vision_time_adjuster_.valid()) {
532 LOG(INFO, "Vision aligning to %f\n", vision_time_adjuster_.goal());
533 profiled_subsystem_.set_turret_unprofiled_goal(
534 vision_time_adjuster_.goal());
535 }
536 }
537
Austin Schuhd5ccb862017-03-11 22:06:36 -0800538 if (freeze_) {
539 profiled_subsystem_.ForceGoal(unsafe_indexer_goal->angular_velocity,
540 starting_goal_angle);
541 }
542 }
543
544 // ESTOP if we hit the hard limits.
545 if (profiled_subsystem_.CheckHardLimits() ||
546 profiled_subsystem_.error()) {
547 state_ = State::ESTOP;
548 }
549 } break;
550
551 case State::ESTOP:
552 LOG(ERROR, "Estop\n");
553 disable = true;
554 break;
555 }
556
557 // Start indexing at the suggested velocity.
558 // If a "stuck" event is detected, reverse. Stay reversed until either
559 // unstuck, or 0.5 seconds have elapsed.
560 // Then, start going forwards. Don't detect stuck for 0.5 seconds.
561
562 monotonic_clock::time_point monotonic_now = monotonic_clock::now();
563 switch (indexer_state_) {
564 case IndexerState::RUNNING:
565 // The velocity goal is already set above in this case, so leave it
566 // alone.
567
568 // If we are stuck and weren't just reversing, try reversing to unstick
569 // us. We don't want to chatter back and forth too fast if reversing
570 // isn't working.
571 if (profiled_subsystem_.IsIndexerStuck() &&
572 monotonic_now > kForwardTimeout + last_transition_time_) {
573 indexer_state_ = IndexerState::REVERSING;
574 last_transition_time_ = monotonic_now;
575 profiled_subsystem_.PartialIndexerReset();
576 LOG(INFO, "Partial indexer reset while going forwards\n");
577 LOG(INFO, "Indexer RUNNING -> REVERSING\n");
578 }
579 break;
580 case IndexerState::REVERSING:
581 // "Reverse" "slowly".
582 profiled_subsystem_.set_indexer_unprofiled_goal(
583 -5.0 * ::aos::sign(profiled_subsystem_.unprofiled_goal(1, 0)));
584
585 // If we've timed out or are no longer stuck, try running again.
586 if ((!profiled_subsystem_.IsIndexerStuck() &&
587 monotonic_now > last_transition_time_ + kReverseMinTimeout) ||
588 monotonic_now > kReverseTimeout + last_transition_time_) {
589 indexer_state_ = IndexerState::RUNNING;
590 LOG(INFO, "Indexer REVERSING -> RUNNING, stuck %d\n",
591 profiled_subsystem_.IsIndexerStuck());
592
593 // Only reset if we got stuck going this way too.
594 if (monotonic_now > kReverseTimeout + last_transition_time_) {
595 LOG(INFO, "Partial indexer reset while reversing\n");
596 profiled_subsystem_.PartialIndexerReset();
597 }
598 last_transition_time_ = monotonic_now;
599 }
600 break;
601 }
602
603 // Set the voltage limits.
604 const double max_voltage =
605 (state_ == State::RUNNING) ? kOperatingVoltage : kZeroingVoltage;
606
607 profiled_subsystem_.set_max_voltage({{max_voltage, max_voltage}});
608
609 // Calculate the loops for a cycle.
610 profiled_subsystem_.Update(disable);
611
612 // Write out all the voltages.
613 if (indexer_output) {
614 *indexer_output = profiled_subsystem_.indexer_voltage();
615 }
616 if (turret_output) {
617 *turret_output = profiled_subsystem_.turret_voltage();
618 }
619
620 // Save debug/internal state.
621 // TODO(austin): Save more.
622 turret_status->zeroed = profiled_subsystem_.zeroed();
623 profiled_subsystem_.PopulateTurretStatus(turret_status);
624 turret_status->estopped = (state_ == State::ESTOP);
625 turret_status->state = static_cast<int32_t>(state_);
Austin Schuhac76bb32017-03-22 22:34:26 -0700626 if (vision_time_adjuster_.valid()) {
627 turret_status->vision_angle = vision_time_adjuster_.goal();
628 turret_status->raw_vision_angle =
629 vision_time_adjuster_.most_recent_vision_reading();
630 turret_status->vision_tracking = true;
631 } else {
632 turret_status->vision_angle = ::std::numeric_limits<double>::quiet_NaN();
633 turret_status->vision_tracking = false;
634 }
Austin Schuhd5ccb862017-03-11 22:06:36 -0800635
636 profiled_subsystem_.PopulateIndexerStatus(indexer_status);
637 indexer_status->state = static_cast<int32_t>(indexer_state_);
638}
639
640} // namespace column
641} // namespace superstructure
642} // namespace control_loops
643} // namespace y2017