blob: 5e9a21c11e65863a5467d53f4d2f94eab4774c5e [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
385 switch (state_) {
386 case State::UNINITIALIZED:
387 // Wait in the uninitialized state until the turret is initialized.
388 // Set the goals to where we are now so when we start back up, we don't
389 // jump.
390 profiled_subsystem_.ForceGoal(0.0, profiled_subsystem_.turret_position());
391 state_ = State::ZEROING_UNINITIALIZED;
392
393 // Fall through so we can start the zeroing process immediately.
394
395 case State::ZEROING_UNINITIALIZED:
396 // Set up the profile to be the zeroing profile.
397 profiled_subsystem_.AdjustProfile(0.50, 3);
398
399 // Force the intake out.
400 intake->set_min_position(kIntakeZeroingMinDistance);
401
402 if (disable) {
403 // If we are disabled, we want to reset the turret to stay where it
404 // currently is.
405 profiled_subsystem_.ForceGoal(0.0,
406 profiled_subsystem_.turret_position());
407 } else if (profiled_subsystem_.initialized()) {
408 // If we are initialized, there's no value in continuing to move so stop
409 // and wait on the intake.
410 profiled_subsystem_.set_indexer_unprofiled_goal(0.0);
411 } else {
412 // Spin slowly backwards.
413 profiled_subsystem_.set_indexer_unprofiled_goal(2.0);
414 }
415
416 // See if we are zeroed or initialized and far enough out and execute the
417 // proper state transition.
418 if (profiled_subsystem_.zeroed()) {
419 intake->clear_min_position();
420 state_ = State::RUNNING;
421 } else if (profiled_subsystem_.initialized() &&
422 intake->position() >
423 kIntakeZeroingMinDistance - kIntakeTolerance) {
424 if (profiled_subsystem_.turret_position() > 0) {
425 // We need to move in the negative direction.
426 state_ = State::ZEROING_NEGATIVE;
427 } else {
428 // We need to move in the positive direction.
429 state_ = State::ZEROING_POSITIVE;
430 }
431 }
432 break;
433
434 case State::ZEROING_POSITIVE:
435 // We are now going to be moving in the positive direction towards 0. If
436 // we get close enough, we'll zero.
437 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
438 intake->set_min_position(kIntakeZeroingMinDistance);
439
440 if (profiled_subsystem_.zeroed()) {
441 intake->clear_min_position();
442 state_ = State::RUNNING;
443 } else if (disable) {
444 // We are disabled, so pick back up from the current position.
445 profiled_subsystem_.ForceGoal(0.0,
446 profiled_subsystem_.turret_position());
447 } else if (profiled_subsystem_.turret_position() <
448 profiled_subsystem_.goal(2, 0) -
449 kStuckZeroingTrackingError ||
450 profiled_subsystem_.saturated()) {
451 LOG(INFO,
452 "Turret stuck going positive, switching directions. At %f, goal "
453 "%f\n",
454 profiled_subsystem_.turret_position(),
455 profiled_subsystem_.goal(2, 0));
456 // The turret got too far behind. Declare it stuck and reverse.
457 profiled_subsystem_.AddOffset(0.0, 2.0 * M_PI);
458 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
459 profiled_subsystem_.ForceGoal(0.0,
460 profiled_subsystem_.turret_position());
461 profiled_subsystem_.PartialTurretReset();
462 profiled_subsystem_.PartialIndexerReset();
463 state_ = State::ZEROING_NEGATIVE;
464 }
465 break;
466
467 case State::ZEROING_NEGATIVE:
468 // We are now going to be moving in the negative direction towards 0. If
469 // we get close enough, we'll zero.
470 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
471 intake->set_min_position(kIntakeZeroingMinDistance);
472
473 if (profiled_subsystem_.zeroed()) {
474 intake->clear_min_position();
475 state_ = State::RUNNING;
476 } else if (disable) {
477 // We are disabled, so pick back up from the current position.
478 profiled_subsystem_.ForceGoal(0.0,
479 profiled_subsystem_.turret_position());
480 } else if (profiled_subsystem_.turret_position() >
481 profiled_subsystem_.goal(2, 0) +
482 kStuckZeroingTrackingError ||
483 profiled_subsystem_.saturated()) {
484 // The turret got too far behind. Declare it stuck and reverse.
485 LOG(INFO,
486 "Turret stuck going negative, switching directions. At %f, goal "
487 "%f\n",
488 profiled_subsystem_.turret_position(),
489 profiled_subsystem_.goal(2, 0));
490 profiled_subsystem_.AddOffset(0.0, -2.0 * M_PI);
491 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
492 profiled_subsystem_.ForceGoal(0.0,
493 profiled_subsystem_.turret_position());
494 profiled_subsystem_.PartialTurretReset();
495 profiled_subsystem_.PartialIndexerReset();
496 state_ = State::ZEROING_POSITIVE;
497 }
498 break;
499
500 case State::RUNNING: {
501 double starting_goal_angle = profiled_subsystem_.goal(2, 0);
502 if (disable) {
503 // Reset the profile to the current position so it starts from here when
504 // we get re-enabled.
505 profiled_subsystem_.ForceGoal(0.0,
506 profiled_subsystem_.turret_position());
507 }
508
509 if (unsafe_turret_goal && unsafe_indexer_goal) {
510 profiled_subsystem_.AdjustProfile(unsafe_turret_goal->profile_params);
511 profiled_subsystem_.set_unprofiled_goal(
512 unsafe_indexer_goal->angular_velocity, unsafe_turret_goal->angle);
513
514 if (freeze_) {
515 profiled_subsystem_.ForceGoal(unsafe_indexer_goal->angular_velocity,
516 starting_goal_angle);
517 }
518 }
519
520 // ESTOP if we hit the hard limits.
521 if (profiled_subsystem_.CheckHardLimits() ||
522 profiled_subsystem_.error()) {
523 state_ = State::ESTOP;
524 }
525 } break;
526
527 case State::ESTOP:
528 LOG(ERROR, "Estop\n");
529 disable = true;
530 break;
531 }
532
533 // Start indexing at the suggested velocity.
534 // If a "stuck" event is detected, reverse. Stay reversed until either
535 // unstuck, or 0.5 seconds have elapsed.
536 // Then, start going forwards. Don't detect stuck for 0.5 seconds.
537
538 monotonic_clock::time_point monotonic_now = monotonic_clock::now();
539 switch (indexer_state_) {
540 case IndexerState::RUNNING:
541 // The velocity goal is already set above in this case, so leave it
542 // alone.
543
544 // If we are stuck and weren't just reversing, try reversing to unstick
545 // us. We don't want to chatter back and forth too fast if reversing
546 // isn't working.
547 if (profiled_subsystem_.IsIndexerStuck() &&
548 monotonic_now > kForwardTimeout + last_transition_time_) {
549 indexer_state_ = IndexerState::REVERSING;
550 last_transition_time_ = monotonic_now;
551 profiled_subsystem_.PartialIndexerReset();
552 LOG(INFO, "Partial indexer reset while going forwards\n");
553 LOG(INFO, "Indexer RUNNING -> REVERSING\n");
554 }
555 break;
556 case IndexerState::REVERSING:
557 // "Reverse" "slowly".
558 profiled_subsystem_.set_indexer_unprofiled_goal(
559 -5.0 * ::aos::sign(profiled_subsystem_.unprofiled_goal(1, 0)));
560
561 // If we've timed out or are no longer stuck, try running again.
562 if ((!profiled_subsystem_.IsIndexerStuck() &&
563 monotonic_now > last_transition_time_ + kReverseMinTimeout) ||
564 monotonic_now > kReverseTimeout + last_transition_time_) {
565 indexer_state_ = IndexerState::RUNNING;
566 LOG(INFO, "Indexer REVERSING -> RUNNING, stuck %d\n",
567 profiled_subsystem_.IsIndexerStuck());
568
569 // Only reset if we got stuck going this way too.
570 if (monotonic_now > kReverseTimeout + last_transition_time_) {
571 LOG(INFO, "Partial indexer reset while reversing\n");
572 profiled_subsystem_.PartialIndexerReset();
573 }
574 last_transition_time_ = monotonic_now;
575 }
576 break;
577 }
578
579 // Set the voltage limits.
580 const double max_voltage =
581 (state_ == State::RUNNING) ? kOperatingVoltage : kZeroingVoltage;
582
583 profiled_subsystem_.set_max_voltage({{max_voltage, max_voltage}});
584
585 // Calculate the loops for a cycle.
586 profiled_subsystem_.Update(disable);
587
588 // Write out all the voltages.
589 if (indexer_output) {
590 *indexer_output = profiled_subsystem_.indexer_voltage();
591 }
592 if (turret_output) {
593 *turret_output = profiled_subsystem_.turret_voltage();
594 }
595
596 // Save debug/internal state.
597 // TODO(austin): Save more.
598 turret_status->zeroed = profiled_subsystem_.zeroed();
599 profiled_subsystem_.PopulateTurretStatus(turret_status);
600 turret_status->estopped = (state_ == State::ESTOP);
601 turret_status->state = static_cast<int32_t>(state_);
602
603 profiled_subsystem_.PopulateIndexerStatus(indexer_status);
604 indexer_status->state = static_cast<int32_t>(indexer_state_);
605}
606
607} // namespace column
608} // namespace superstructure
609} // namespace control_loops
610} // namespace y2017