blob: a6b80c872df32f452a93c8a3f7ad1fc8b193b92e [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;
28constexpr chrono::milliseconds kForwardTimeout{500};
29constexpr chrono::milliseconds kReverseTimeout{500};
30constexpr chrono::milliseconds kReverseMinTimeout{100};
31} // 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())),
50 profile_(::aos::controls::kLoopFrequency),
51 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
73 LOG(INFO, "Adjusting indexer offset from %f to %f\n", offset_(0, 0),
74 indexer_offset);
75 LOG(INFO, "Adjusting turret offset from %f to %f\n", offset_(1, 0),
76 turret_offset);
77
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()) {
101 LOG(ERROR, "zeroing error\n");
102 return;
103 }
104
105 if (!initialized_) {
106 if (estimators_[0].offset_ready()) {
107 UpdateOffset(estimators_[0].indexer_offset(),
108 estimators_[0].turret_offset());
109 initialized_ = true;
110 }
111 }
112
113 if (!zeroed(0) && estimators_[0].zeroed()) {
114 UpdateOffset(estimators_[0].indexer_offset(),
115 estimators_[0].turret_offset());
116 set_zeroed(0, true);
117 }
118
119 turret_last_position_ = turret_position();
120 Y_ << new_position.indexer.position, new_position.turret.position;
121 Y_ += offset_;
122 loop_->Correct(Y_);
123
124 indexer_history_[indexer_history_position_] = new_position.indexer.position;
125 indexer_history_position_ = (indexer_history_position_ + 1) % kHistoryLength;
126
127 indexer_dt_velocity_ =
128 (new_position.indexer.position - indexer_last_position_) /
129 chrono::duration_cast<chrono::duration<double>>(
130 ::aos::controls::kLoopFrequency)
131 .count();
132 indexer_last_position_ = new_position.indexer.position;
133
134 stuck_indexer_detector_->Correct(Y_);
135
136 // Compute the oldest point in the history.
137 const int indexer_oldest_history_position =
138 ((indexer_history_position_ == 0) ? kHistoryLength
139 : indexer_history_position_) -
140 1;
141
142 // Compute the distance moved over that time period.
143 indexer_average_angular_velocity_ =
144 (indexer_history_[indexer_oldest_history_position] -
145 indexer_history_[indexer_history_position_]) /
146 (chrono::duration_cast<chrono::duration<double>>(
147 ::aos::controls::kLoopFrequency)
148 .count() *
149 static_cast<double>(kHistoryLength - 1));
150
151 // Ready if average angular velocity is close to the goal.
152 indexer_error_ = indexer_average_angular_velocity_ - unprofiled_goal_(1, 0);
153
154 indexer_ready_ =
155 std::abs(indexer_error_) < kTolerance && unprofiled_goal_(1, 0) > 0.1;
156
157 // Pull state from the profiled subsystem.
158 X_hat_current_ = controller().X_hat();
159 stuck_indexer_X_hat_current_ = stuck_indexer_detector_->X_hat();
160 indexer_position_error_ = X_hat_current_(0, 0) - Y(0, 0);
161}
162
163void ColumnProfiledSubsystem::CapGoal(const char *name,
164 Eigen::Matrix<double, 6, 1> *goal) {
165 // Limit the goal to min/max allowable positions.
166 if (zeroed()) {
167 if ((*goal)(2, 0) > range_.upper) {
168 LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
169 range_.upper);
170 (*goal)(2, 0) = range_.upper;
171 }
172 if ((*goal)(2, 0) < range_.lower) {
173 LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
174 range_.lower);
175 (*goal)(2, 0) = range_.lower;
176 }
177 } else {
178 const double kMaxRange = range().upper_hard - range().lower_hard;
179
180 // Limit the goal to min/max allowable positions much less agressively.
181 // We don't know where the limits are, so we have to let the user move far
182 // enough to find them (and the index pulse which might be right next to
183 // one).
184 // Upper - lower hard may be a bit generous, but we are moving slow.
185
186 if ((*goal)(2, 0) > kMaxRange) {
187 LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
188 kMaxRange);
189 (*goal)(2, 0) = kMaxRange;
190 }
191 if ((*goal)(2, 0) < -kMaxRange) {
192 LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
193 -kMaxRange);
194 (*goal)(2, 0) = -kMaxRange;
195 }
196 }
197}
198
199void ColumnProfiledSubsystem::ForceGoal(double goal_velocity, double goal) {
200 set_unprofiled_goal(goal_velocity, goal);
201 loop_->mutable_R() = unprofiled_goal_;
202 loop_->mutable_next_R() = loop_->R();
203
204 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
205 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
206}
207
208void ColumnProfiledSubsystem::set_unprofiled_goal(double goal_velocity,
209 double unprofiled_goal) {
210 unprofiled_goal_(0, 0) = 0.0;
211 unprofiled_goal_(1, 0) = goal_velocity;
212 unprofiled_goal_(2, 0) = unprofiled_goal;
213 unprofiled_goal_(3, 0) = 0.0;
214 unprofiled_goal_(4, 0) = 0.0;
215 unprofiled_goal_(5, 0) = 0.0;
216 CapGoal("unprofiled R", &unprofiled_goal_);
217}
218
219void ColumnProfiledSubsystem::set_indexer_unprofiled_goal(
220 double goal_velocity) {
221 unprofiled_goal_(0, 0) = 0.0;
222 unprofiled_goal_(1, 0) = goal_velocity;
223 unprofiled_goal_(4, 0) = 0.0;
224 CapGoal("unprofiled R", &unprofiled_goal_);
225}
226
227void ColumnProfiledSubsystem::set_turret_unprofiled_goal(
228 double unprofiled_goal) {
229 unprofiled_goal_(2, 0) = unprofiled_goal;
230 unprofiled_goal_(3, 0) = 0.0;
231 unprofiled_goal_(5, 0) = 0.0;
232 CapGoal("unprofiled R", &unprofiled_goal_);
233}
234
235void ColumnProfiledSubsystem::Update(bool disable) {
236 // TODO(austin): If we really need to reset, reset the profiles, etc. That'll
237 // be covered by the layer above when disabled though, so we can get away with
238 // not doing it yet.
239 if (should_reset_) {
240 loop_->mutable_X_hat(0, 0) = Y_(0, 0);
241 loop_->mutable_X_hat(1, 0) = 0.0;
242 loop_->mutable_X_hat(2, 0) = Y_(0, 0) + Y_(1, 0);
243 loop_->mutable_X_hat(3, 0) = 0.0;
244 loop_->mutable_X_hat(4, 0) = 0.0;
245 loop_->mutable_X_hat(5, 0) = 0.0;
246
247 LOG(INFO, "Resetting\n");
248 stuck_indexer_detector_->mutable_X_hat() = loop_->X_hat();
249 should_reset_ = false;
250 saturated_ = false;
251 }
252
253 if (!disable) {
254 ::Eigen::Matrix<double, 2, 1> goal_state =
255 profile_.Update(unprofiled_goal_(2, 0), unprofiled_goal_(3, 0));
256
257 loop_->mutable_next_R(0, 0) = 0.0;
258 loop_->mutable_next_R(1, 0) = unprofiled_goal_(1, 0);
259 loop_->mutable_next_R(2, 0) = goal_state(0, 0);
260 loop_->mutable_next_R(3, 0) = goal_state(1, 0);
261 loop_->mutable_next_R(4, 0) = 0.0;
262 loop_->mutable_next_R(5, 0) = 0.0;
263 CapGoal("next R", &loop_->mutable_next_R());
264 }
265
266 // If the indexer goal velocity is low, switch to the indexer controller which
267 // won't fight to keep it moving at 0.
268 if (::std::abs(unprofiled_goal_(1, 0)) < 0.1) {
269 loop_->set_index(1);
270 } else {
271 loop_->set_index(0);
272 }
273 loop_->Update(disable);
274
275 if (!disable && loop_->U(1, 0) != loop_->U_uncapped(1, 0)) {
276 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
277 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
278 saturated_ = true;
279 } else {
280 saturated_ = false;
281 }
282
283 // Run the KF predict step.
284 stuck_indexer_detector_->UpdateObserver(loop_->U(),
285 ::aos::controls::kLoopFrequency);
286}
287
288bool ColumnProfiledSubsystem::CheckHardLimits() {
289 // Returns whether hard limits have been exceeded.
290
291 if (turret_position() > range_.upper_hard || turret_position() < range_.lower_hard) {
292 LOG(ERROR,
293 "ColumnProfiledSubsystem at %f out of bounds [%f, %f], ESTOPing\n",
294 turret_position(), range_.lower_hard, range_.upper_hard);
295 return true;
296 }
297
298 return false;
299}
300
301void ColumnProfiledSubsystem::AdjustProfile(
302 const ::frc971::ProfileParameters &profile_parameters) {
303 AdjustProfile(profile_parameters.max_velocity,
304 profile_parameters.max_acceleration);
305}
306
307void ColumnProfiledSubsystem::AdjustProfile(double max_angular_velocity,
308 double max_angular_acceleration) {
309 profile_.set_maximum_velocity(
310 ::frc971::control_loops::internal::UseUnlessZero(max_angular_velocity,
311 default_velocity_));
312 profile_.set_maximum_acceleration(
313 ::frc971::control_loops::internal::UseUnlessZero(max_angular_acceleration,
314 default_acceleration_));
315}
316
317double ColumnProfiledSubsystem::IndexerStuckVoltage() const {
318 // Compute the voltage from the control loop, excluding the voltage error
319 // term.
320 const double uncapped_applied_voltage =
321 uncapped_indexer_voltage() + X_hat(4, 0);
322 if (uncapped_applied_voltage < 0) {
323 return +stuck_indexer_X_hat_current_(4, 0);
324 } else {
325 return -stuck_indexer_X_hat_current_(4, 0);
326 }
327}
328bool ColumnProfiledSubsystem::IsIndexerStuck() const {
329 return IndexerStuckVoltage() > 4.0;
330}
331
332void ColumnProfiledSubsystem::PartialIndexerReset() {
333 mutable_X_hat(4, 0) = 0.0;
334 stuck_indexer_detector_->mutable_X_hat(4, 0) = 0.0;
335}
336
337void ColumnProfiledSubsystem::PartialTurretReset() {
338 mutable_X_hat(5, 0) = 0.0;
339 stuck_indexer_detector_->mutable_X_hat(5, 0) = 0.0;
340}
341
342void ColumnProfiledSubsystem::PopulateIndexerStatus(IndexerStatus *status) {
343 status->avg_angular_velocity = indexer_average_angular_velocity_;
344
345 status->angular_velocity = X_hat_current_(1, 0);
346 status->ready = indexer_ready_;
347
348 status->voltage_error = X_hat_current_(4, 0);
349 status->stuck_voltage_error = stuck_indexer_X_hat_current_(4, 0);
350 status->position_error = indexer_position_error_;
351 status->instantaneous_velocity = indexer_dt_velocity_;
352
353 status->stuck = IsIndexerStuck();
354
355 status->stuck_voltage = IndexerStuckVoltage();
356}
357
358Column::Column()
359 : profiled_subsystem_(
360 ::std::unique_ptr<
361 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>(
362 new ::frc971::control_loops::SimpleCappedStateFeedbackLoop<
363 6, 2, 2>(MakeIntegralColumnLoop())),
364 constants::GetValues().column, constants::Values::kTurretRange, 7.0,
365 50.0) {}
366
367void Column::Reset() {
368 state_ = State::UNINITIALIZED;
369 indexer_state_ = IndexerState::RUNNING;
370 profiled_subsystem_.Reset();
371 // intake will automatically clear the minimum position on reset, so we don't
372 // need to do it here.
373 freeze_ = false;
374}
375
376void Column::Iterate(const control_loops::IndexerGoal *unsafe_indexer_goal,
377 const control_loops::TurretGoal *unsafe_turret_goal,
378 const ColumnPosition *position, double *indexer_output,
379 double *turret_output, IndexerStatus *indexer_status,
380 TurretProfiledSubsystemStatus *turret_status,
381 intake::Intake *intake) {
382 bool disable = turret_output == nullptr || indexer_output == nullptr;
383 profiled_subsystem_.Correct(*position);
384
Austin Schuhac76bb32017-03-22 22:34:26 -0700385 vision_time_adjuster_.Tick(::aos::monotonic_clock::now(),
386 profiled_subsystem_.X_hat(2, 0));
387
Austin Schuhd5ccb862017-03-11 22:06:36 -0800388 switch (state_) {
389 case State::UNINITIALIZED:
390 // Wait in the uninitialized state until the turret is initialized.
391 // Set the goals to where we are now so when we start back up, we don't
392 // jump.
393 profiled_subsystem_.ForceGoal(0.0, profiled_subsystem_.turret_position());
394 state_ = State::ZEROING_UNINITIALIZED;
395
396 // Fall through so we can start the zeroing process immediately.
397
398 case State::ZEROING_UNINITIALIZED:
399 // Set up the profile to be the zeroing profile.
400 profiled_subsystem_.AdjustProfile(0.50, 3);
401
402 // Force the intake out.
403 intake->set_min_position(kIntakeZeroingMinDistance);
404
405 if (disable) {
406 // If we are disabled, we want to reset the turret to stay where it
407 // currently is.
408 profiled_subsystem_.ForceGoal(0.0,
409 profiled_subsystem_.turret_position());
410 } else if (profiled_subsystem_.initialized()) {
411 // If we are initialized, there's no value in continuing to move so stop
412 // and wait on the intake.
413 profiled_subsystem_.set_indexer_unprofiled_goal(0.0);
414 } else {
415 // Spin slowly backwards.
416 profiled_subsystem_.set_indexer_unprofiled_goal(2.0);
417 }
418
419 // See if we are zeroed or initialized and far enough out and execute the
420 // proper state transition.
421 if (profiled_subsystem_.zeroed()) {
422 intake->clear_min_position();
423 state_ = State::RUNNING;
424 } else if (profiled_subsystem_.initialized() &&
425 intake->position() >
426 kIntakeZeroingMinDistance - kIntakeTolerance) {
427 if (profiled_subsystem_.turret_position() > 0) {
428 // We need to move in the negative direction.
429 state_ = State::ZEROING_NEGATIVE;
430 } else {
431 // We need to move in the positive direction.
432 state_ = State::ZEROING_POSITIVE;
433 }
434 }
435 break;
436
437 case State::ZEROING_POSITIVE:
438 // We are now going to be moving in the positive direction towards 0. If
439 // we get close enough, we'll zero.
440 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
441 intake->set_min_position(kIntakeZeroingMinDistance);
442
443 if (profiled_subsystem_.zeroed()) {
444 intake->clear_min_position();
445 state_ = State::RUNNING;
446 } else if (disable) {
447 // We are disabled, so pick back up from the current position.
448 profiled_subsystem_.ForceGoal(0.0,
449 profiled_subsystem_.turret_position());
450 } else if (profiled_subsystem_.turret_position() <
451 profiled_subsystem_.goal(2, 0) -
452 kStuckZeroingTrackingError ||
453 profiled_subsystem_.saturated()) {
454 LOG(INFO,
455 "Turret stuck going positive, switching directions. At %f, goal "
456 "%f\n",
457 profiled_subsystem_.turret_position(),
458 profiled_subsystem_.goal(2, 0));
459 // The turret got too far behind. Declare it stuck and reverse.
460 profiled_subsystem_.AddOffset(0.0, 2.0 * M_PI);
461 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
462 profiled_subsystem_.ForceGoal(0.0,
463 profiled_subsystem_.turret_position());
464 profiled_subsystem_.PartialTurretReset();
465 profiled_subsystem_.PartialIndexerReset();
466 state_ = State::ZEROING_NEGATIVE;
467 }
468 break;
469
470 case State::ZEROING_NEGATIVE:
471 // We are now going to be moving in the negative direction towards 0. If
472 // we get close enough, we'll zero.
473 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
474 intake->set_min_position(kIntakeZeroingMinDistance);
475
476 if (profiled_subsystem_.zeroed()) {
477 intake->clear_min_position();
478 state_ = State::RUNNING;
479 } else if (disable) {
480 // We are disabled, so pick back up from the current position.
481 profiled_subsystem_.ForceGoal(0.0,
482 profiled_subsystem_.turret_position());
483 } else if (profiled_subsystem_.turret_position() >
484 profiled_subsystem_.goal(2, 0) +
485 kStuckZeroingTrackingError ||
486 profiled_subsystem_.saturated()) {
487 // The turret got too far behind. Declare it stuck and reverse.
488 LOG(INFO,
489 "Turret stuck going negative, switching directions. At %f, goal "
490 "%f\n",
491 profiled_subsystem_.turret_position(),
492 profiled_subsystem_.goal(2, 0));
493 profiled_subsystem_.AddOffset(0.0, -2.0 * M_PI);
494 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
495 profiled_subsystem_.ForceGoal(0.0,
496 profiled_subsystem_.turret_position());
497 profiled_subsystem_.PartialTurretReset();
498 profiled_subsystem_.PartialIndexerReset();
499 state_ = State::ZEROING_POSITIVE;
500 }
501 break;
502
503 case State::RUNNING: {
504 double starting_goal_angle = profiled_subsystem_.goal(2, 0);
505 if (disable) {
506 // Reset the profile to the current position so it starts from here when
507 // we get re-enabled.
508 profiled_subsystem_.ForceGoal(0.0,
509 profiled_subsystem_.turret_position());
510 }
511
512 if (unsafe_turret_goal && unsafe_indexer_goal) {
513 profiled_subsystem_.AdjustProfile(unsafe_turret_goal->profile_params);
514 profiled_subsystem_.set_unprofiled_goal(
515 unsafe_indexer_goal->angular_velocity, unsafe_turret_goal->angle);
516
Austin Schuhac76bb32017-03-22 22:34:26 -0700517 if (unsafe_turret_goal->track) {
518 if (vision_time_adjuster_.valid()) {
519 LOG(INFO, "Vision aligning to %f\n", vision_time_adjuster_.goal());
520 profiled_subsystem_.set_turret_unprofiled_goal(
521 vision_time_adjuster_.goal());
522 }
523 }
524
Austin Schuhd5ccb862017-03-11 22:06:36 -0800525 if (freeze_) {
526 profiled_subsystem_.ForceGoal(unsafe_indexer_goal->angular_velocity,
527 starting_goal_angle);
528 }
529 }
530
531 // ESTOP if we hit the hard limits.
532 if (profiled_subsystem_.CheckHardLimits() ||
533 profiled_subsystem_.error()) {
534 state_ = State::ESTOP;
535 }
536 } break;
537
538 case State::ESTOP:
539 LOG(ERROR, "Estop\n");
540 disable = true;
541 break;
542 }
543
544 // Start indexing at the suggested velocity.
545 // If a "stuck" event is detected, reverse. Stay reversed until either
546 // unstuck, or 0.5 seconds have elapsed.
547 // Then, start going forwards. Don't detect stuck for 0.5 seconds.
548
549 monotonic_clock::time_point monotonic_now = monotonic_clock::now();
550 switch (indexer_state_) {
551 case IndexerState::RUNNING:
552 // The velocity goal is already set above in this case, so leave it
553 // alone.
554
555 // If we are stuck and weren't just reversing, try reversing to unstick
556 // us. We don't want to chatter back and forth too fast if reversing
557 // isn't working.
558 if (profiled_subsystem_.IsIndexerStuck() &&
559 monotonic_now > kForwardTimeout + last_transition_time_) {
560 indexer_state_ = IndexerState::REVERSING;
561 last_transition_time_ = monotonic_now;
562 profiled_subsystem_.PartialIndexerReset();
563 LOG(INFO, "Partial indexer reset while going forwards\n");
564 LOG(INFO, "Indexer RUNNING -> REVERSING\n");
565 }
566 break;
567 case IndexerState::REVERSING:
568 // "Reverse" "slowly".
569 profiled_subsystem_.set_indexer_unprofiled_goal(
570 -5.0 * ::aos::sign(profiled_subsystem_.unprofiled_goal(1, 0)));
571
572 // If we've timed out or are no longer stuck, try running again.
573 if ((!profiled_subsystem_.IsIndexerStuck() &&
574 monotonic_now > last_transition_time_ + kReverseMinTimeout) ||
575 monotonic_now > kReverseTimeout + last_transition_time_) {
576 indexer_state_ = IndexerState::RUNNING;
577 LOG(INFO, "Indexer REVERSING -> RUNNING, stuck %d\n",
578 profiled_subsystem_.IsIndexerStuck());
579
580 // Only reset if we got stuck going this way too.
581 if (monotonic_now > kReverseTimeout + last_transition_time_) {
582 LOG(INFO, "Partial indexer reset while reversing\n");
583 profiled_subsystem_.PartialIndexerReset();
584 }
585 last_transition_time_ = monotonic_now;
586 }
587 break;
588 }
589
590 // Set the voltage limits.
591 const double max_voltage =
592 (state_ == State::RUNNING) ? kOperatingVoltage : kZeroingVoltage;
593
594 profiled_subsystem_.set_max_voltage({{max_voltage, max_voltage}});
595
596 // Calculate the loops for a cycle.
597 profiled_subsystem_.Update(disable);
598
599 // Write out all the voltages.
600 if (indexer_output) {
601 *indexer_output = profiled_subsystem_.indexer_voltage();
602 }
603 if (turret_output) {
604 *turret_output = profiled_subsystem_.turret_voltage();
605 }
606
607 // Save debug/internal state.
608 // TODO(austin): Save more.
609 turret_status->zeroed = profiled_subsystem_.zeroed();
610 profiled_subsystem_.PopulateTurretStatus(turret_status);
611 turret_status->estopped = (state_ == State::ESTOP);
612 turret_status->state = static_cast<int32_t>(state_);
Austin Schuhac76bb32017-03-22 22:34:26 -0700613 if (vision_time_adjuster_.valid()) {
614 turret_status->vision_angle = vision_time_adjuster_.goal();
615 turret_status->raw_vision_angle =
616 vision_time_adjuster_.most_recent_vision_reading();
617 turret_status->vision_tracking = true;
618 } else {
619 turret_status->vision_angle = ::std::numeric_limits<double>::quiet_NaN();
620 turret_status->vision_tracking = false;
621 }
Austin Schuhd5ccb862017-03-11 22:06:36 -0800622
623 profiled_subsystem_.PopulateIndexerStatus(indexer_status);
624 indexer_status->state = static_cast<int32_t>(indexer_state_);
625}
626
627} // namespace column
628} // namespace superstructure
629} // namespace control_loops
630} // namespace y2017