blob: 6583357e437c76ee41c3d6fa1cbbae9acb567a75 [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"
Philipp Schrader790cb542023-07-05 21:06:52 -07009
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"
James Kuszmaulec635d22023-08-12 18:39:24 -070014#include "frc971/zeroing/pulse_index.h"
Austin Schuhd5ccb862017-03-11 22:06:36 -080015#include "y2017/control_loops/superstructure/column/column_integral_plant.h"
16#include "y2017/control_loops/superstructure/column/stuck_column_integral_plant.h"
17
Stephan Pleinesf63bde82024-01-13 15:59:33 -080018namespace y2017::control_loops::superstructure::column {
Austin Schuhd5ccb862017-03-11 22:06:36 -080019
20namespace chrono = ::std::chrono;
21using ::aos::monotonic_clock;
22using ::frc971::zeroing::PulseIndexZeroingEstimator;
23
24namespace {
25constexpr double kTolerance = 10.0;
Austin Schuh4af3ac12017-04-09 18:34:01 -070026constexpr double kIndexerAcceleration = 50.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -080027constexpr chrono::milliseconds kForwardTimeout{500};
Austin Schuh4af3ac12017-04-09 18:34:01 -070028constexpr chrono::milliseconds kReverseTimeout{1000};
29constexpr chrono::milliseconds kReverseMinTimeout{500};
Austin Schuhd5ccb862017-03-11 22:06:36 -080030} // namespace
31
32constexpr double Column::kZeroingVoltage;
33constexpr double Column::kOperatingVoltage;
34constexpr double Column::kIntakeZeroingMinDistance;
35constexpr double Column::kIntakeTolerance;
36constexpr double Column::kStuckZeroingTrackingError;
37
38ColumnProfiledSubsystem::ColumnProfiledSubsystem(
39 ::std::unique_ptr<
40 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>
41 loop,
42 const ::y2017::constants::Values::Column &zeroing_constants,
43 const ::frc971::constants::Range &range, double default_velocity,
44 double default_acceleration)
45 : ProfiledSubsystem<6, 1, ColumnZeroingEstimator, 2, 2>(
46 ::std::move(loop), {{zeroing_constants}}),
47 stuck_indexer_detector_(new StateFeedbackLoop<6, 2, 2>(
48 column::MakeStuckIntegralColumnLoop())),
James Kuszmaul61750662021-06-21 21:32:33 -070049 profile_(::frc971::controls::kLoopFrequency),
Austin Schuhd5ccb862017-03-11 22:06:36 -080050 range_(range),
51 default_velocity_(default_velocity),
52 default_acceleration_(default_acceleration) {
53 Y_.setZero();
54 offset_.setZero();
55 X_hat_current_.setZero();
56 stuck_indexer_X_hat_current_.setZero();
57 indexer_history_.fill(0);
58 AdjustProfile(0.0, 0.0);
59}
60
61void ColumnProfiledSubsystem::AddOffset(double indexer_offset_delta,
62 double turret_offset_delta) {
63 UpdateOffset(offset_(0, 0) + indexer_offset_delta,
64 offset_(1, 0) + turret_offset_delta);
65}
66
67void ColumnProfiledSubsystem::UpdateOffset(double indexer_offset,
68 double turret_offset) {
69 const double indexer_doffset = indexer_offset - offset_(0, 0);
70 const double turret_doffset = turret_offset - offset_(1, 0);
71
Austin Schuhf257f3c2019-10-27 21:00:43 -070072 AOS_LOG(INFO, "Adjusting indexer offset from %f to %f\n", offset_(0, 0),
73 indexer_offset);
74 AOS_LOG(INFO, "Adjusting turret offset from %f to %f\n", offset_(1, 0),
75 turret_offset);
Austin Schuhd5ccb862017-03-11 22:06:36 -080076
77 loop_->mutable_X_hat()(0, 0) += indexer_doffset;
78 loop_->mutable_X_hat()(2, 0) += turret_doffset + indexer_doffset;
79
80 stuck_indexer_detector_->mutable_X_hat()(0, 0) += indexer_doffset;
81 stuck_indexer_detector_->mutable_X_hat()(2, 0) +=
82 turret_doffset + indexer_doffset;
83 Y_(0, 0) += indexer_doffset;
84 Y_(1, 0) += turret_doffset;
85 turret_last_position_ += turret_doffset + indexer_doffset;
86 loop_->mutable_R(0, 0) += indexer_doffset;
87 loop_->mutable_R(2, 0) += turret_doffset + indexer_doffset;
88
89 profile_.MoveGoal(turret_doffset + indexer_doffset);
90 offset_(0, 0) = indexer_offset;
91 offset_(1, 0) = turret_offset;
92
93 CapGoal("R", &loop_->mutable_R());
94}
95
96void ColumnProfiledSubsystem::Correct(const ColumnPosition &new_position) {
97 estimators_[0].UpdateEstimate(new_position);
98
99 if (estimators_[0].error()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700100 AOS_LOG(ERROR, "zeroing error\n");
Austin Schuh2669ece2022-03-11 18:30:57 -0800101 X_hat_ = loop_->X_hat();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800102 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();
Alex Perrycb7da4b2019-08-28 19:35:56 -0700120 Y_ << new_position.indexer()->encoder(), new_position.turret()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800121 Y_ += offset_;
122 loop_->Correct(Y_);
Austin Schuh2669ece2022-03-11 18:30:57 -0800123 X_hat_ = loop_->X_hat();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800124
Alex Perrycb7da4b2019-08-28 19:35:56 -0700125 indexer_history_[indexer_history_position_] =
126 new_position.indexer()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800127 indexer_history_position_ = (indexer_history_position_ + 1) % kHistoryLength;
128
129 indexer_dt_velocity_ =
Alex Perrycb7da4b2019-08-28 19:35:56 -0700130 (new_position.indexer()->encoder() - indexer_last_position_) /
James Kuszmaul61750662021-06-21 21:32:33 -0700131 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700132 indexer_last_position_ = new_position.indexer()->encoder();
Austin Schuhd5ccb862017-03-11 22:06:36 -0800133
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_]) /
James Kuszmaul61750662021-06-21 21:32:33 -0700146 (::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency) *
Austin Schuhd5ccb862017-03-11 22:06:36 -0800147 static_cast<double>(kHistoryLength - 1));
148
149 // Ready if average angular velocity is close to the goal.
150 indexer_error_ = indexer_average_angular_velocity_ - unprofiled_goal_(1, 0);
151
152 indexer_ready_ =
153 std::abs(indexer_error_) < kTolerance && unprofiled_goal_(1, 0) > 0.1;
154
155 // Pull state from the profiled subsystem.
156 X_hat_current_ = controller().X_hat();
157 stuck_indexer_X_hat_current_ = stuck_indexer_detector_->X_hat();
158 indexer_position_error_ = X_hat_current_(0, 0) - Y(0, 0);
159}
160
161void ColumnProfiledSubsystem::CapGoal(const char *name,
162 Eigen::Matrix<double, 6, 1> *goal) {
163 // Limit the goal to min/max allowable positions.
164 if (zeroed()) {
165 if ((*goal)(2, 0) > range_.upper) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700166 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
167 range_.upper);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800168 (*goal)(2, 0) = range_.upper;
169 }
170 if ((*goal)(2, 0) < range_.lower) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700171 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
172 range_.lower);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800173 (*goal)(2, 0) = range_.lower;
174 }
175 } else {
176 const double kMaxRange = range().upper_hard - range().lower_hard;
177
178 // Limit the goal to min/max allowable positions much less agressively.
179 // We don't know where the limits are, so we have to let the user move far
180 // enough to find them (and the index pulse which might be right next to
181 // one).
182 // Upper - lower hard may be a bit generous, but we are moving slow.
183
184 if ((*goal)(2, 0) > kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700185 AOS_LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(2, 0),
186 kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800187 (*goal)(2, 0) = kMaxRange;
188 }
189 if ((*goal)(2, 0) < -kMaxRange) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700190 AOS_LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(2, 0),
191 -kMaxRange);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800192 (*goal)(2, 0) = -kMaxRange;
193 }
194 }
195}
196
197void ColumnProfiledSubsystem::ForceGoal(double goal_velocity, double goal) {
198 set_unprofiled_goal(goal_velocity, goal);
199 loop_->mutable_R() = unprofiled_goal_;
200 loop_->mutable_next_R() = loop_->R();
201
202 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
203 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
204}
205
206void ColumnProfiledSubsystem::set_unprofiled_goal(double goal_velocity,
207 double unprofiled_goal) {
208 unprofiled_goal_(0, 0) = 0.0;
209 unprofiled_goal_(1, 0) = goal_velocity;
210 unprofiled_goal_(2, 0) = unprofiled_goal;
211 unprofiled_goal_(3, 0) = 0.0;
212 unprofiled_goal_(4, 0) = 0.0;
213 unprofiled_goal_(5, 0) = 0.0;
214 CapGoal("unprofiled R", &unprofiled_goal_);
215}
216
217void ColumnProfiledSubsystem::set_indexer_unprofiled_goal(
218 double goal_velocity) {
219 unprofiled_goal_(0, 0) = 0.0;
220 unprofiled_goal_(1, 0) = goal_velocity;
221 unprofiled_goal_(4, 0) = 0.0;
222 CapGoal("unprofiled R", &unprofiled_goal_);
223}
224
225void ColumnProfiledSubsystem::set_turret_unprofiled_goal(
226 double unprofiled_goal) {
227 unprofiled_goal_(2, 0) = unprofiled_goal;
228 unprofiled_goal_(3, 0) = 0.0;
229 unprofiled_goal_(5, 0) = 0.0;
230 CapGoal("unprofiled R", &unprofiled_goal_);
231}
232
233void ColumnProfiledSubsystem::Update(bool disable) {
234 // TODO(austin): If we really need to reset, reset the profiles, etc. That'll
235 // be covered by the layer above when disabled though, so we can get away with
236 // not doing it yet.
237 if (should_reset_) {
238 loop_->mutable_X_hat(0, 0) = Y_(0, 0);
239 loop_->mutable_X_hat(1, 0) = 0.0;
240 loop_->mutable_X_hat(2, 0) = Y_(0, 0) + Y_(1, 0);
241 loop_->mutable_X_hat(3, 0) = 0.0;
242 loop_->mutable_X_hat(4, 0) = 0.0;
243 loop_->mutable_X_hat(5, 0) = 0.0;
244
Austin Schuhf257f3c2019-10-27 21:00:43 -0700245 AOS_LOG(INFO, "Resetting\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800246 stuck_indexer_detector_->mutable_X_hat() = loop_->X_hat();
247 should_reset_ = false;
248 saturated_ = false;
249 }
250
251 if (!disable) {
252 ::Eigen::Matrix<double, 2, 1> goal_state =
253 profile_.Update(unprofiled_goal_(2, 0), unprofiled_goal_(3, 0));
254
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700255 constexpr double kDt =
James Kuszmaul61750662021-06-21 21:32:33 -0700256 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency);
Austin Schuh471d3b12017-03-25 20:49:14 -0700257
Austin Schuhd5ccb862017-03-11 22:06:36 -0800258 loop_->mutable_next_R(0, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700259 // TODO(austin): This might not handle saturation right, but I'm not sure I
260 // really care.
261 loop_->mutable_next_R(1, 0) = ::aos::Clip(
262 unprofiled_goal_(1, 0), loop_->R(1, 0) - kIndexerAcceleration * kDt,
263 loop_->R(1, 0) + kIndexerAcceleration * kDt);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800264 loop_->mutable_next_R(2, 0) = goal_state(0, 0);
265 loop_->mutable_next_R(3, 0) = goal_state(1, 0);
266 loop_->mutable_next_R(4, 0) = 0.0;
267 loop_->mutable_next_R(5, 0) = 0.0;
268 CapGoal("next R", &loop_->mutable_next_R());
269 }
270
271 // If the indexer goal velocity is low, switch to the indexer controller which
272 // won't fight to keep it moving at 0.
273 if (::std::abs(unprofiled_goal_(1, 0)) < 0.1) {
274 loop_->set_index(1);
275 } else {
276 loop_->set_index(0);
277 }
278 loop_->Update(disable);
279
280 if (!disable && loop_->U(1, 0) != loop_->U_uncapped(1, 0)) {
281 const ::Eigen::Matrix<double, 6, 1> &R = loop_->R();
282 profile_.MoveCurrentState(R.block<2, 1>(2, 0));
283 saturated_ = true;
284 } else {
285 saturated_ = false;
286 }
287
288 // Run the KF predict step.
289 stuck_indexer_detector_->UpdateObserver(loop_->U(),
James Kuszmaul61750662021-06-21 21:32:33 -0700290 ::frc971::controls::kLoopFrequency);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800291}
292
293bool ColumnProfiledSubsystem::CheckHardLimits() {
294 // Returns whether hard limits have been exceeded.
295
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700296 if (turret_position() > range_.upper_hard ||
297 turret_position() < range_.lower_hard) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700298 AOS_LOG(ERROR,
299 "ColumnProfiledSubsystem at %f out of bounds [%f, %f], ESTOPing\n",
300 turret_position(), range_.lower_hard, range_.upper_hard);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800301 return true;
302 }
303
304 return false;
305}
306
307void ColumnProfiledSubsystem::AdjustProfile(
Alex Perrycb7da4b2019-08-28 19:35:56 -0700308 const ::frc971::ProfileParameters *profile_parameters) {
309 AdjustProfile(
310 profile_parameters == nullptr ? 0.0 : profile_parameters->max_velocity(),
311 profile_parameters == nullptr ? 0.0
312 : profile_parameters->max_acceleration());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800313}
314
315void ColumnProfiledSubsystem::AdjustProfile(double max_angular_velocity,
316 double max_angular_acceleration) {
317 profile_.set_maximum_velocity(
318 ::frc971::control_loops::internal::UseUnlessZero(max_angular_velocity,
319 default_velocity_));
320 profile_.set_maximum_acceleration(
321 ::frc971::control_loops::internal::UseUnlessZero(max_angular_acceleration,
322 default_acceleration_));
323}
324
325double ColumnProfiledSubsystem::IndexerStuckVoltage() const {
326 // Compute the voltage from the control loop, excluding the voltage error
327 // term.
328 const double uncapped_applied_voltage =
329 uncapped_indexer_voltage() + X_hat(4, 0);
330 if (uncapped_applied_voltage < 0) {
331 return +stuck_indexer_X_hat_current_(4, 0);
332 } else {
333 return -stuck_indexer_X_hat_current_(4, 0);
334 }
335}
336bool ColumnProfiledSubsystem::IsIndexerStuck() const {
Austin Schuh546a0382017-04-16 19:10:18 -0700337 return IndexerStuckVoltage() > 4.0;
Austin Schuhd5ccb862017-03-11 22:06:36 -0800338}
339
340void ColumnProfiledSubsystem::PartialIndexerReset() {
341 mutable_X_hat(4, 0) = 0.0;
342 stuck_indexer_detector_->mutable_X_hat(4, 0) = 0.0;
Austin Schuh471d3b12017-03-25 20:49:14 -0700343 // Screw it, we are stuck. Reset the current goal to the current velocity so
344 // we start slewing faster to reverse if we have stopped.
345 loop_->mutable_R(1, 0) = X_hat(1, 0);
346 loop_->mutable_next_R(1, 0) = X_hat(1, 0);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800347}
348
349void ColumnProfiledSubsystem::PartialTurretReset() {
350 mutable_X_hat(5, 0) = 0.0;
351 stuck_indexer_detector_->mutable_X_hat(5, 0) = 0.0;
352}
353
Alex Perrycb7da4b2019-08-28 19:35:56 -0700354void ColumnProfiledSubsystem::PopulateIndexerStatus(
355 IndexerStatus::Builder *status_builder) {
356 status_builder->add_avg_angular_velocity(indexer_average_angular_velocity_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800357
Alex Perrycb7da4b2019-08-28 19:35:56 -0700358 status_builder->add_angular_velocity(X_hat_current_(1, 0));
359 status_builder->add_ready(indexer_ready_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800360
Alex Perrycb7da4b2019-08-28 19:35:56 -0700361 status_builder->add_voltage_error(X_hat_current_(4, 0));
362 status_builder->add_stuck_voltage_error(stuck_indexer_X_hat_current_(4, 0));
363 status_builder->add_position_error(indexer_position_error_);
364 status_builder->add_instantaneous_velocity(indexer_dt_velocity_);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800365
Alex Perrycb7da4b2019-08-28 19:35:56 -0700366 status_builder->add_stuck(IsIndexerStuck());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800367
Alex Perrycb7da4b2019-08-28 19:35:56 -0700368 status_builder->add_stuck_voltage(IndexerStuckVoltage());
369}
370
371void ColumnProfiledSubsystem::PopulateTurretStatus(
372 TurretProfiledSubsystemStatus::Builder *status_builder,
373 flatbuffers::Offset<ColumnZeroingEstimator::State> estimator_state_offset) {
374 status_builder->add_zeroed(zeroed());
375
376 status_builder->add_position(X_hat(2, 0));
377 status_builder->add_velocity(X_hat(3, 0));
378 status_builder->add_goal_position(goal(2, 0));
379 status_builder->add_goal_velocity(goal(3, 0));
380 status_builder->add_unprofiled_goal_position(unprofiled_goal(2, 0));
381 status_builder->add_unprofiled_goal_velocity(unprofiled_goal(3, 0));
382 status_builder->add_voltage_error(X_hat(5, 0));
383 status_builder->add_calculated_velocity(
384 (turret_position() - turret_last_position_) /
James Kuszmaul61750662021-06-21 21:32:33 -0700385 ::aos::time::DurationInSeconds(::frc971::controls::kLoopFrequency));
Alex Perrycb7da4b2019-08-28 19:35:56 -0700386
387 status_builder->add_estimator_state(estimator_state_offset);
388
389 Eigen::Matrix<double, 6, 1> error = controller().error();
390 status_builder->add_position_power(controller().controller().K(0, 0) *
391 error(0, 0));
392 status_builder->add_velocity_power(controller().controller().K(0, 1) *
393 error(1, 0));
Austin Schuhd5ccb862017-03-11 22:06:36 -0800394}
395
Austin Schuhb6c5c852019-05-19 20:13:31 -0700396Column::Column(::aos::EventLoop *event_loop)
397 : vision_time_adjuster_(event_loop),
398 profiled_subsystem_(
Austin Schuhd5ccb862017-03-11 22:06:36 -0800399 ::std::unique_ptr<
400 ::frc971::control_loops::SimpleCappedStateFeedbackLoop<6, 2, 2>>(
401 new ::frc971::control_loops::SimpleCappedStateFeedbackLoop<
402 6, 2, 2>(MakeIntegralColumnLoop())),
403 constants::GetValues().column, constants::Values::kTurretRange, 7.0,
Austin Schuh25db1262017-04-05 19:39:55 -0700404 50.0),
405 vision_error_(constants::GetValues().vision_error) {}
Austin Schuhd5ccb862017-03-11 22:06:36 -0800406
407void Column::Reset() {
408 state_ = State::UNINITIALIZED;
409 indexer_state_ = IndexerState::RUNNING;
410 profiled_subsystem_.Reset();
411 // intake will automatically clear the minimum position on reset, so we don't
412 // need to do it here.
413 freeze_ = false;
414}
415
Alex Perrycb7da4b2019-08-28 19:35:56 -0700416std::pair<flatbuffers::Offset<IndexerStatus>,
417 flatbuffers::Offset<TurretProfiledSubsystemStatus>>
418Column::Iterate(const ::aos::monotonic_clock::time_point monotonic_now,
419 const IndexerGoalT *unsafe_indexer_goal,
420 const TurretGoal *unsafe_turret_goal,
421 const ColumnPosition *position,
422 const vision::VisionStatus *vision_status,
423 double *indexer_output, double *turret_output,
424 flatbuffers::FlatBufferBuilder *fbb, intake::Intake *intake) {
Austin Schuhd5ccb862017-03-11 22:06:36 -0800425 bool disable = turret_output == nullptr || indexer_output == nullptr;
426 profiled_subsystem_.Correct(*position);
427
Austin Schuh92715362019-07-07 20:47:45 -0700428 vision_time_adjuster_.Tick(monotonic_now, profiled_subsystem_.X_hat(2, 0),
429 vision_status);
Austin Schuhac76bb32017-03-22 22:34:26 -0700430
Austin Schuhd5ccb862017-03-11 22:06:36 -0800431 switch (state_) {
432 case State::UNINITIALIZED:
433 // Wait in the uninitialized state until the turret is initialized.
434 // Set the goals to where we are now so when we start back up, we don't
435 // jump.
436 profiled_subsystem_.ForceGoal(0.0, profiled_subsystem_.turret_position());
437 state_ = State::ZEROING_UNINITIALIZED;
438
439 // Fall through so we can start the zeroing process immediately.
James Kuszmaul3ae42262019-11-08 12:33:41 -0800440 [[fallthrough]];
Austin Schuhd5ccb862017-03-11 22:06:36 -0800441
442 case State::ZEROING_UNINITIALIZED:
443 // Set up the profile to be the zeroing profile.
444 profiled_subsystem_.AdjustProfile(0.50, 3);
445
446 // Force the intake out.
447 intake->set_min_position(kIntakeZeroingMinDistance);
448
449 if (disable) {
450 // If we are disabled, we want to reset the turret to stay where it
451 // currently is.
452 profiled_subsystem_.ForceGoal(0.0,
453 profiled_subsystem_.turret_position());
454 } else if (profiled_subsystem_.initialized()) {
455 // If we are initialized, there's no value in continuing to move so stop
456 // and wait on the intake.
457 profiled_subsystem_.set_indexer_unprofiled_goal(0.0);
458 } else {
459 // Spin slowly backwards.
460 profiled_subsystem_.set_indexer_unprofiled_goal(2.0);
461 }
462
463 // See if we are zeroed or initialized and far enough out and execute the
464 // proper state transition.
465 if (profiled_subsystem_.zeroed()) {
466 intake->clear_min_position();
467 state_ = State::RUNNING;
468 } else if (profiled_subsystem_.initialized() &&
469 intake->position() >
470 kIntakeZeroingMinDistance - kIntakeTolerance) {
471 if (profiled_subsystem_.turret_position() > 0) {
472 // We need to move in the negative direction.
473 state_ = State::ZEROING_NEGATIVE;
474 } else {
475 // We need to move in the positive direction.
476 state_ = State::ZEROING_POSITIVE;
477 }
478 }
479 break;
480
481 case State::ZEROING_POSITIVE:
482 // We are now going to be moving in the positive direction towards 0. If
483 // we get close enough, we'll zero.
484 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
485 intake->set_min_position(kIntakeZeroingMinDistance);
486
487 if (profiled_subsystem_.zeroed()) {
488 intake->clear_min_position();
489 state_ = State::RUNNING;
490 } else if (disable) {
491 // We are disabled, so pick back up from the current position.
492 profiled_subsystem_.ForceGoal(0.0,
493 profiled_subsystem_.turret_position());
494 } else if (profiled_subsystem_.turret_position() <
495 profiled_subsystem_.goal(2, 0) -
496 kStuckZeroingTrackingError ||
497 profiled_subsystem_.saturated()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700498 AOS_LOG(
499 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800500 "Turret stuck going positive, switching directions. At %f, goal "
501 "%f\n",
502 profiled_subsystem_.turret_position(),
503 profiled_subsystem_.goal(2, 0));
504 // The turret got too far behind. Declare it stuck and reverse.
505 profiled_subsystem_.AddOffset(0.0, 2.0 * M_PI);
506 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
507 profiled_subsystem_.ForceGoal(0.0,
508 profiled_subsystem_.turret_position());
509 profiled_subsystem_.PartialTurretReset();
510 profiled_subsystem_.PartialIndexerReset();
511 state_ = State::ZEROING_NEGATIVE;
512 }
513 break;
514
515 case State::ZEROING_NEGATIVE:
516 // We are now going to be moving in the negative direction towards 0. If
517 // we get close enough, we'll zero.
518 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
519 intake->set_min_position(kIntakeZeroingMinDistance);
520
521 if (profiled_subsystem_.zeroed()) {
522 intake->clear_min_position();
523 state_ = State::RUNNING;
524 } else if (disable) {
525 // We are disabled, so pick back up from the current position.
526 profiled_subsystem_.ForceGoal(0.0,
527 profiled_subsystem_.turret_position());
528 } else if (profiled_subsystem_.turret_position() >
529 profiled_subsystem_.goal(2, 0) +
530 kStuckZeroingTrackingError ||
531 profiled_subsystem_.saturated()) {
532 // The turret got too far behind. Declare it stuck and reverse.
Austin Schuhf257f3c2019-10-27 21:00:43 -0700533 AOS_LOG(
534 INFO,
Austin Schuhd5ccb862017-03-11 22:06:36 -0800535 "Turret stuck going negative, switching directions. At %f, goal "
536 "%f\n",
537 profiled_subsystem_.turret_position(),
538 profiled_subsystem_.goal(2, 0));
539 profiled_subsystem_.AddOffset(0.0, -2.0 * M_PI);
540 profiled_subsystem_.set_unprofiled_goal(0.0, 0.0);
541 profiled_subsystem_.ForceGoal(0.0,
542 profiled_subsystem_.turret_position());
543 profiled_subsystem_.PartialTurretReset();
544 profiled_subsystem_.PartialIndexerReset();
545 state_ = State::ZEROING_POSITIVE;
546 }
547 break;
548
549 case State::RUNNING: {
550 double starting_goal_angle = profiled_subsystem_.goal(2, 0);
551 if (disable) {
552 // Reset the profile to the current position so it starts from here when
553 // we get re-enabled.
554 profiled_subsystem_.ForceGoal(0.0,
555 profiled_subsystem_.turret_position());
556 }
557
558 if (unsafe_turret_goal && unsafe_indexer_goal) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700559 profiled_subsystem_.AdjustProfile(unsafe_turret_goal->profile_params());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800560 profiled_subsystem_.set_unprofiled_goal(
James Kuszmaul61750662021-06-21 21:32:33 -0700561 unsafe_indexer_goal->angular_velocity, unsafe_turret_goal->angle());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800562
Alex Perrycb7da4b2019-08-28 19:35:56 -0700563 if (unsafe_turret_goal->track()) {
Austin Schuhac76bb32017-03-22 22:34:26 -0700564 if (vision_time_adjuster_.valid()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700565 AOS_LOG(INFO, "Vision aligning to %f\n",
566 vision_time_adjuster_.goal());
Austin Schuhac76bb32017-03-22 22:34:26 -0700567 profiled_subsystem_.set_turret_unprofiled_goal(
Austin Schuh25db1262017-04-05 19:39:55 -0700568 vision_time_adjuster_.goal() + vision_error_);
Austin Schuhac76bb32017-03-22 22:34:26 -0700569 }
Austin Schuhd85c66e2017-04-16 10:50:33 -0700570 } else {
571 vision_time_adjuster_.ResetTime();
Austin Schuhac76bb32017-03-22 22:34:26 -0700572 }
573
Austin Schuhd5ccb862017-03-11 22:06:36 -0800574 if (freeze_) {
575 profiled_subsystem_.ForceGoal(unsafe_indexer_goal->angular_velocity,
576 starting_goal_angle);
577 }
578 }
579
580 // ESTOP if we hit the hard limits.
581 if (profiled_subsystem_.CheckHardLimits() ||
582 profiled_subsystem_.error()) {
583 state_ = State::ESTOP;
584 }
585 } break;
586
587 case State::ESTOP:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700588 AOS_LOG(ERROR, "Estop\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800589 disable = true;
590 break;
591 }
592
593 // Start indexing at the suggested velocity.
594 // If a "stuck" event is detected, reverse. Stay reversed until either
595 // unstuck, or 0.5 seconds have elapsed.
596 // Then, start going forwards. Don't detect stuck for 0.5 seconds.
Austin Schuhd5ccb862017-03-11 22:06:36 -0800597 switch (indexer_state_) {
598 case IndexerState::RUNNING:
599 // The velocity goal is already set above in this case, so leave it
600 // alone.
601
602 // If we are stuck and weren't just reversing, try reversing to unstick
603 // us. We don't want to chatter back and forth too fast if reversing
604 // isn't working.
605 if (profiled_subsystem_.IsIndexerStuck() &&
606 monotonic_now > kForwardTimeout + last_transition_time_) {
607 indexer_state_ = IndexerState::REVERSING;
608 last_transition_time_ = monotonic_now;
609 profiled_subsystem_.PartialIndexerReset();
Austin Schuhf257f3c2019-10-27 21:00:43 -0700610 AOS_LOG(INFO, "Partial indexer reset while going forwards\n");
611 AOS_LOG(INFO, "Indexer RUNNING -> REVERSING\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800612 }
613 break;
614 case IndexerState::REVERSING:
615 // "Reverse" "slowly".
616 profiled_subsystem_.set_indexer_unprofiled_goal(
617 -5.0 * ::aos::sign(profiled_subsystem_.unprofiled_goal(1, 0)));
618
619 // If we've timed out or are no longer stuck, try running again.
620 if ((!profiled_subsystem_.IsIndexerStuck() &&
621 monotonic_now > last_transition_time_ + kReverseMinTimeout) ||
622 monotonic_now > kReverseTimeout + last_transition_time_) {
623 indexer_state_ = IndexerState::RUNNING;
Austin Schuhf257f3c2019-10-27 21:00:43 -0700624 AOS_LOG(INFO, "Indexer REVERSING -> RUNNING, stuck %d\n",
625 profiled_subsystem_.IsIndexerStuck());
Austin Schuhd5ccb862017-03-11 22:06:36 -0800626
627 // Only reset if we got stuck going this way too.
628 if (monotonic_now > kReverseTimeout + last_transition_time_) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700629 AOS_LOG(INFO, "Partial indexer reset while reversing\n");
Austin Schuhd5ccb862017-03-11 22:06:36 -0800630 profiled_subsystem_.PartialIndexerReset();
631 }
632 last_transition_time_ = monotonic_now;
633 }
634 break;
635 }
636
637 // Set the voltage limits.
638 const double max_voltage =
639 (state_ == State::RUNNING) ? kOperatingVoltage : kZeroingVoltage;
640
641 profiled_subsystem_.set_max_voltage({{max_voltage, max_voltage}});
642
643 // Calculate the loops for a cycle.
644 profiled_subsystem_.Update(disable);
645
646 // Write out all the voltages.
647 if (indexer_output) {
648 *indexer_output = profiled_subsystem_.indexer_voltage();
649 }
650 if (turret_output) {
651 *turret_output = profiled_subsystem_.turret_voltage();
652 }
653
654 // Save debug/internal state.
655 // TODO(austin): Save more.
Alex Perrycb7da4b2019-08-28 19:35:56 -0700656 flatbuffers::Offset<ColumnZeroingEstimator::State>
657 column_estimator_state_offset =
658 profiled_subsystem_.EstimatorState(fbb, 0);
659
660 TurretProfiledSubsystemStatus::Builder turret_status_builder(*fbb);
661 profiled_subsystem_.PopulateTurretStatus(&turret_status_builder,
662 column_estimator_state_offset);
663 turret_status_builder.add_estopped((state_ == State::ESTOP));
664 turret_status_builder.add_state(static_cast<int32_t>(state_));
665 turret_status_builder.add_turret_encoder_angle(
666 profiled_subsystem_.turret_position());
Austin Schuhac76bb32017-03-22 22:34:26 -0700667 if (vision_time_adjuster_.valid()) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700668 turret_status_builder.add_vision_angle(vision_time_adjuster_.goal());
669 turret_status_builder.add_raw_vision_angle(
670 vision_time_adjuster_.most_recent_vision_reading());
671 turret_status_builder.add_vision_tracking(true);
Austin Schuhac76bb32017-03-22 22:34:26 -0700672 } else {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700673 turret_status_builder.add_vision_angle(
674 ::std::numeric_limits<double>::quiet_NaN());
675 turret_status_builder.add_vision_tracking(false);
Austin Schuhac76bb32017-03-22 22:34:26 -0700676 }
Austin Schuhd5ccb862017-03-11 22:06:36 -0800677
Alex Perrycb7da4b2019-08-28 19:35:56 -0700678 flatbuffers::Offset<TurretProfiledSubsystemStatus> turret_status_offset =
679 turret_status_builder.Finish();
680
681 IndexerStatus::Builder indexer_status_builder(*fbb);
682 profiled_subsystem_.PopulateIndexerStatus(&indexer_status_builder);
683
684 indexer_status_builder.add_state(static_cast<int32_t>(indexer_state_));
685
686 flatbuffers::Offset<IndexerStatus> indexer_status_offset =
687 indexer_status_builder.Finish();
688
689 return std::make_pair(indexer_status_offset, turret_status_offset);
Austin Schuhd5ccb862017-03-11 22:06:36 -0800690}
691
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800692} // namespace y2017::control_loops::superstructure::column