blob: 5c1b84311a6eb9b4ba76bd8e581bcc4a875ccf85 [file] [log] [blame]
Brian Silverman17f503e2015-08-02 18:17:18 -07001#include "y2014/control_loops/claw/claw.h"
2
3#include <algorithm>
4
5#include "aos/common/controls/control_loops.q.h"
6#include "aos/common/logging/logging.h"
7#include "aos/common/logging/queue_logging.h"
8#include "aos/common/logging/matrix_logging.h"
9#include "aos/common/commonmath.h"
10
11#include "y2014/constants.h"
12#include "y2014/control_loops/claw/claw_motor_plant.h"
13
14// Zeroing plan.
15// There are 2 types of zeros. Enabled and disabled ones.
16// Disabled ones are only valid during auto mode, and can be used to speed up
17// the enabled zero process. We need to re-zero during teleop in case the auto
18// zero was poor and causes us to miss all our shots.
19//
20// We need to be able to zero manually while disabled by moving the joint over
21// the zeros.
22// Zero on the down edge when disabled (gravity in the direction of motion)
23//
24// When enabled, zero on the up edge (gravity opposing the direction of motion)
25// The enabled sequence needs to work as follows. We can crash the claw if we
26// bring them too close to each other or too far from each other. The only safe
27// thing to do is to move them in unison.
28//
29// Start by moving them both towards the front of the bot to either find either
30// the middle hall effect on either jaw, or the front hall effect on the bottom
31// jaw. Any edge that isn't the desired edge will provide an approximate edge
32// location that can be used for the fine tuning step.
33// Once an edge is found on the front claw, move back the other way with both
34// claws until an edge is found for the other claw.
35// Now that we have an approximate zero, we can robustify the limits to keep
36// both claws safe. Then, we can move both claws to a position that is the
37// correct side of the zero and go zero.
38
39// Valid region plan.
40// Difference between the arms has a range, and the values of each arm has a
41// range.
42// If a claw runs up against a static limit, don't let the goal change outside
43// the limit.
44// If a claw runs up against a movable limit, move both claws outwards to get
45// out of the condition.
46
47namespace frc971 {
48namespace control_loops {
49
Austin Schuh0e997732015-11-08 15:14:53 -080050using ::y2014::control_loops::claw::kDt;
51
Brian Silverman17f503e2015-08-02 18:17:18 -070052static const double kZeroingVoltage = 4.0;
53static const double kMaxVoltage = 12.0;
54const double kRezeroThreshold = 0.07;
55
56ClawLimitedLoop::ClawLimitedLoop(StateFeedbackLoop<4, 2, 2> &&loop)
57 : StateFeedbackLoop<4, 2, 2>(::std::move(loop)),
58 uncapped_average_voltage_(0.0),
59 is_zeroing_(true),
60 U_Poly_((Eigen::Matrix<double, 4, 2>() << 1, 0,
61 -1, 0,
62 0, 1,
63 0, -1).finished(),
64 (Eigen::Matrix<double, 4, 1>() << kMaxVoltage, kMaxVoltage,
65 kMaxVoltage, kMaxVoltage).finished()),
66 U_Poly_zeroing_((Eigen::Matrix<double, 4, 2>() << 1, 0,
67 -1, 0,
68 0, 1,
69 0, -1).finished(),
70 (Eigen::Matrix<double, 4, 1>() <<
71 kZeroingVoltage, kZeroingVoltage,
72 kZeroingVoltage, kZeroingVoltage).finished()) {
73 ::aos::controls::HPolytope<0>::Init();
74}
75
76// Caps the voltage prioritizing reducing velocity error over reducing
77// positional error.
78// Uses the polytope libararies which we used to just use for the drivetrain.
79// Uses a region representing the maximum voltage and then transforms it such
80// that the points represent different amounts of positional error and
81// constrains the region such that, if at all possible, it will maintain its
82// current efforts to reduce velocity error.
83void ClawLimitedLoop::CapU() {
84 const Eigen::Matrix<double, 4, 1> error = R() - X_hat();
85
86 double u_top = U(1, 0);
87 double u_bottom = U(0, 0);
88
89 uncapped_average_voltage_ = (u_top + u_bottom) / 2;
90
91 double max_voltage = is_zeroing_ ? kZeroingVoltage : kMaxVoltage;
92
93 if (::std::abs(u_bottom) > max_voltage || ::std::abs(u_top) > max_voltage) {
94 LOG_MATRIX(DEBUG, "U at start", U());
95 // H * U <= k
96 // U = UPos + UVel
97 // H * (UPos + UVel) <= k
98 // H * UPos <= k - H * UVel
99
100 // Now, we can do a coordinate transformation and say the following.
101
102 // UPos = position_K * position_error
103 // (H * position_K) * position_error <= k - H * UVel
104
105 Eigen::Matrix<double, 2, 2> position_K;
106 position_K << K(0, 0), K(0, 1),
107 K(1, 0), K(1, 1);
108 Eigen::Matrix<double, 2, 2> velocity_K;
109 velocity_K << K(0, 2), K(0, 3),
110 K(1, 2), K(1, 3);
111
112 Eigen::Matrix<double, 2, 1> position_error;
113 position_error << error(0, 0), error(1, 0);
114 Eigen::Matrix<double, 2, 1> velocity_error;
115 velocity_error << error(2, 0), error(3, 0);
116 LOG_MATRIX(DEBUG, "error", error);
117
118 const auto &poly = is_zeroing_ ? U_Poly_zeroing_ : U_Poly_;
119 const Eigen::Matrix<double, 4, 2> pos_poly_H = poly.H() * position_K;
120 const Eigen::Matrix<double, 4, 1> pos_poly_k =
121 poly.k() - poly.H() * velocity_K * velocity_error;
122 const ::aos::controls::HPolytope<2> pos_poly(pos_poly_H, pos_poly_k);
123
124 Eigen::Matrix<double, 2, 1> adjusted_pos_error;
125 {
126 const auto &P = position_error;
127
128 // This line was at 45 degrees but is now at some angle steeper than the
129 // straight one between the points.
130 Eigen::Matrix<double, 1, 2> angle_45;
131 // If the top claw is above its soft upper limit, make the line actually
132 // 45 degrees to avoid smashing it into the limit in an attempt to fix the
133 // separation error faster than the bottom position one.
134 if (X_hat(0, 0) + X_hat(1, 0) >
135 constants::GetValues().claw.upper_claw.upper_limit) {
136 angle_45 << 1, 1;
137 } else {
138 // Fixing separation error half as fast as positional error works well
139 // because it means they both close evenly.
140 angle_45 << ::std::sqrt(3), 1;
141 }
142 Eigen::Matrix<double, 1, 2> L45_quadrant;
143 L45_quadrant << ::aos::sign(P(1, 0)), -::aos::sign(P(0, 0));
144 const auto L45 = L45_quadrant.cwiseProduct(angle_45);
145 const double w45 = 0;
146
147 Eigen::Matrix<double, 1, 2> LH;
148 if (::std::abs(P(0, 0)) > ::std::abs(P(1, 0))) {
149 LH << 0, 1;
150 } else {
151 LH << 1, 0;
152 }
153 const double wh = LH.dot(P);
154
155 Eigen::Matrix<double, 2, 2> standard;
156 standard << L45, LH;
157 Eigen::Matrix<double, 2, 1> W;
158 W << w45, wh;
159 const Eigen::Matrix<double, 2, 1> intersection = standard.inverse() * W;
160
161 bool is_inside_h;
162 const auto adjusted_pos_error_h =
163 DoCoerceGoal(pos_poly, LH, wh, position_error, &is_inside_h);
164 const auto adjusted_pos_error_45 =
165 DoCoerceGoal(pos_poly, L45, w45, intersection, nullptr);
166 if (pos_poly.IsInside(intersection)) {
167 adjusted_pos_error = adjusted_pos_error_h;
168 } else {
169 if (is_inside_h) {
170 if (adjusted_pos_error_h.norm() > adjusted_pos_error_45.norm()) {
171 adjusted_pos_error = adjusted_pos_error_h;
172 } else {
173 adjusted_pos_error = adjusted_pos_error_45;
174 }
175 } else {
176 adjusted_pos_error = adjusted_pos_error_45;
177 }
178 }
179 }
180
181 LOG_MATRIX(DEBUG, "adjusted_pos_error", adjusted_pos_error);
182 mutable_U() = velocity_K * velocity_error + position_K * adjusted_pos_error;
183 LOG_MATRIX(DEBUG, "U is now", U());
184
185 {
186 const auto values = constants::GetValues().claw;
187 if (top_known_) {
188 if (X_hat(0, 0) + X_hat(1, 0) > values.upper_claw.upper_limit && U(1, 0) > 0) {
189 LOG(WARNING, "upper claw too high and moving up\n");
190 mutable_U(1, 0) = 0;
191 } else if (X_hat(0, 0) + X_hat(1, 0) < values.upper_claw.lower_limit &&
192 U(1, 0) < 0) {
193 LOG(WARNING, "upper claw too low and moving down\n");
194 mutable_U(1, 0) = 0;
195 }
196 }
197 if (bottom_known_) {
198 if (X_hat(0, 0) > values.lower_claw.upper_limit && U(0, 0) > 0) {
199 LOG(WARNING, "lower claw too high and moving up\n");
200 mutable_U(0, 0) = 0;
201 } else if (X_hat(0, 0) < values.lower_claw.lower_limit && U(0, 0) < 0) {
202 LOG(WARNING, "lower claw too low and moving down\n");
203 mutable_U(0, 0) = 0;
204 }
205 }
206 }
207 }
208}
209
210ZeroedStateFeedbackLoop::ZeroedStateFeedbackLoop(const char *name,
211 ClawMotor *motor)
212 : offset_(0.0),
213 name_(name),
214 motor_(motor),
215 zeroing_state_(UNKNOWN_POSITION),
Brian Silverman17f503e2015-08-02 18:17:18 -0700216 encoder_(0.0),
217 last_encoder_(0.0) {}
218
219void ZeroedStateFeedbackLoop::SetPositionValues(const HalfClawPosition &claw) {
220 front_.Update(claw.front);
221 calibration_.Update(claw.calibration);
222 back_.Update(claw.back);
223
224 bool any_sensor_triggered = any_triggered();
225 if (any_sensor_triggered && any_triggered_last_) {
226 // We are still on the hall effect and nothing has changed.
227 min_hall_effect_on_angle_ =
228 ::std::min(min_hall_effect_on_angle_, claw.position);
229 max_hall_effect_on_angle_ =
230 ::std::max(max_hall_effect_on_angle_, claw.position);
231 } else if (!any_sensor_triggered && !any_triggered_last_) {
232 // We are still off the hall effect and nothing has changed.
233 min_hall_effect_off_angle_ =
234 ::std::min(min_hall_effect_off_angle_, claw.position);
235 max_hall_effect_off_angle_ =
236 ::std::max(max_hall_effect_off_angle_, claw.position);
Brian Silverman17f503e2015-08-02 18:17:18 -0700237 }
238
Brian Silvermand3efb182015-05-13 23:04:29 -0400239 if (front_.is_posedge()) {
240 // Saw a posedge on the hall effect. Reset the limits.
241 min_hall_effect_on_angle_ =
242 ::std::min(claw.front.posedge_value, claw.position);
243 max_hall_effect_on_angle_ =
244 ::std::max(claw.front.posedge_value, claw.position);
245 }
246 if (calibration_.is_posedge()) {
247 // Saw a posedge on the hall effect. Reset the limits.
248 min_hall_effect_on_angle_ =
249 ::std::min(claw.calibration.posedge_value, claw.position);
250 max_hall_effect_on_angle_ =
251 ::std::max(claw.calibration.posedge_value, claw.position);
252 }
253 if (back_.is_posedge()) {
254 // Saw a posedge on the hall effect. Reset the limits.
255 min_hall_effect_on_angle_ =
256 ::std::min(claw.back.posedge_value, claw.position);
257 max_hall_effect_on_angle_ =
258 ::std::max(claw.back.posedge_value, claw.position);
259 }
260
261 if (front_.is_negedge()) {
262 // Saw a negedge on the hall effect. Reset the limits.
263 min_hall_effect_off_angle_ =
264 ::std::min(claw.front.negedge_value, claw.position);
265 max_hall_effect_off_angle_ =
266 ::std::max(claw.front.negedge_value, claw.position);
267 }
268 if (calibration_.is_negedge()) {
269 // Saw a negedge on the hall effect. Reset the limits.
270 min_hall_effect_off_angle_ =
271 ::std::min(claw.calibration.negedge_value, claw.position);
272 max_hall_effect_off_angle_ =
273 ::std::max(claw.calibration.negedge_value, claw.position);
274 }
275 if (back_.is_negedge()) {
276 // Saw a negedge on the hall effect. Reset the limits.
277 min_hall_effect_off_angle_ =
278 ::std::min(claw.back.negedge_value, claw.position);
279 max_hall_effect_off_angle_ =
280 ::std::max(claw.back.negedge_value, claw.position);
281 }
282
Brian Silverman17f503e2015-08-02 18:17:18 -0700283 last_encoder_ = encoder_;
284 if (front().value() || calibration().value() || back().value()) {
285 last_on_encoder_ = encoder_;
286 } else {
287 last_off_encoder_ = encoder_;
288 }
289 encoder_ = claw.position;
290 any_triggered_last_ = any_sensor_triggered;
291}
292
293void ZeroedStateFeedbackLoop::Reset(const HalfClawPosition &claw) {
294 set_zeroing_state(ZeroedStateFeedbackLoop::UNKNOWN_POSITION);
295
296 front_.Reset(claw.front);
297 calibration_.Reset(claw.calibration);
298 back_.Reset(claw.back);
299 // close up the min and max edge positions as they are no longer valid and
300 // will be expanded in future iterations
301 min_hall_effect_on_angle_ = claw.position;
302 max_hall_effect_on_angle_ = claw.position;
303 min_hall_effect_off_angle_ = claw.position;
304 max_hall_effect_off_angle_ = claw.position;
305 any_triggered_last_ = any_triggered();
306}
307
308bool TopZeroedStateFeedbackLoop::SetCalibrationOnEdge(
309 const constants::Values::Claws::Claw &claw_values,
310 JointZeroingState zeroing_state) {
311 double edge_encoder;
312 double edge_angle;
313 if (GetPositionOfEdge(claw_values, &edge_encoder, &edge_angle)) {
314 LOG(INFO, "Calibration edge edge should be %f.\n", edge_angle);
315 SetCalibration(edge_encoder, edge_angle);
316 set_zeroing_state(zeroing_state);
317 return true;
318 }
319 return false;
320}
321
322void TopZeroedStateFeedbackLoop::HandleCalibrationError(
323 const constants::Values::Claws::Claw &claw_values) {
324 double edge_encoder;
325 double edge_angle;
326 if (GetPositionOfEdge(claw_values, &edge_encoder, &edge_angle)) {
327 const double calibration_error =
328 ComputeCalibrationChange(edge_encoder, edge_angle);
329 LOG(INFO, "Top calibration error is %f\n", calibration_error);
330 if (::std::abs(calibration_error) > kRezeroThreshold) {
331 LOG(WARNING, "rezeroing top\n");
332 SetCalibration(edge_encoder, edge_angle);
333 set_zeroing_state(ZeroedStateFeedbackLoop::UNKNOWN_POSITION);
334 }
335 }
336}
337
338
339void BottomZeroedStateFeedbackLoop::HandleCalibrationError(
340 const constants::Values::Claws::Claw &claw_values) {
341 double edge_encoder;
342 double edge_angle;
343 if (GetPositionOfEdge(claw_values, &edge_encoder, &edge_angle)) {
344 const double calibration_error =
345 ComputeCalibrationChange(edge_encoder, edge_angle);
346 LOG(INFO, "Bottom calibration error is %f\n", calibration_error);
347 if (::std::abs(calibration_error) > kRezeroThreshold) {
348 LOG(WARNING, "rezeroing bottom\n");
349 SetCalibration(edge_encoder, edge_angle);
350 set_zeroing_state(ZeroedStateFeedbackLoop::UNKNOWN_POSITION);
351 }
352 }
353}
354
355bool BottomZeroedStateFeedbackLoop::SetCalibrationOnEdge(
356 const constants::Values::Claws::Claw &claw_values,
357 JointZeroingState zeroing_state) {
358 double edge_encoder;
359 double edge_angle;
360 if (GetPositionOfEdge(claw_values, &edge_encoder, &edge_angle)) {
361 LOG(INFO, "Calibration edge.\n");
362 SetCalibration(edge_encoder, edge_angle);
363 set_zeroing_state(zeroing_state);
364 return true;
365 }
366 return false;
367}
368
Brian Silvermanbe5ded62015-05-14 00:23:49 -0400369ClawMotor::ClawMotor(control_loops::ClawQueue *my_claw)
370 : aos::controls::ControlLoop<control_loops::ClawQueue>(my_claw),
Brian Silverman17f503e2015-08-02 18:17:18 -0700371 has_top_claw_goal_(false),
372 top_claw_goal_(0.0),
373 top_claw_(this),
374 has_bottom_claw_goal_(false),
375 bottom_claw_goal_(0.0),
376 bottom_claw_(this),
Austin Schuhedc317c2015-11-08 14:07:42 -0800377 claw_(::y2014::control_loops::claw::MakeClawLoop()),
Brian Silverman17f503e2015-08-02 18:17:18 -0700378 was_enabled_(false),
379 doing_calibration_fine_tune_(false),
380 capped_goal_(false),
381 mode_(UNKNOWN_LOCATION) {}
382
383const int ZeroedStateFeedbackLoop::kZeroingMaxVoltage;
384
385bool ZeroedStateFeedbackLoop::SawFilteredPosedge(
386 const HallEffectTracker &this_sensor, const HallEffectTracker &sensorA,
387 const HallEffectTracker &sensorB) {
388 if (posedge_filter_ == nullptr && this_sensor.posedge_count_changed() &&
389 !sensorA.posedge_count_changed() && !sensorB.posedge_count_changed() &&
390 this_sensor.value() && !this_sensor.last_value()) {
391 posedge_filter_ = &this_sensor;
392 } else if (posedge_filter_ == &this_sensor &&
393 !this_sensor.posedge_count_changed() &&
394 !sensorA.posedge_count_changed() &&
395 !sensorB.posedge_count_changed() && this_sensor.value()) {
396 posedge_filter_ = nullptr;
397 return true;
398 } else if (posedge_filter_ == &this_sensor) {
399 posedge_filter_ = nullptr;
400 }
401 return false;
402}
403
404bool ZeroedStateFeedbackLoop::SawFilteredNegedge(
405 const HallEffectTracker &this_sensor, const HallEffectTracker &sensorA,
406 const HallEffectTracker &sensorB) {
407 if (negedge_filter_ == nullptr && this_sensor.negedge_count_changed() &&
408 !sensorA.negedge_count_changed() && !sensorB.negedge_count_changed() &&
409 !this_sensor.value() && this_sensor.last_value()) {
410 negedge_filter_ = &this_sensor;
411 } else if (negedge_filter_ == &this_sensor &&
412 !this_sensor.negedge_count_changed() &&
413 !sensorA.negedge_count_changed() &&
414 !sensorB.negedge_count_changed() && !this_sensor.value()) {
415 negedge_filter_ = nullptr;
416 return true;
417 } else if (negedge_filter_ == &this_sensor) {
418 negedge_filter_ = nullptr;
419 }
420 return false;
421}
422
423bool ZeroedStateFeedbackLoop::DoGetPositionOfEdge(
424 const constants::Values::Claws::AnglePair &angles, double *edge_encoder,
425 double *edge_angle, const HallEffectTracker &this_sensor,
426 const HallEffectTracker &sensorA, const HallEffectTracker &sensorB,
427 const char *hall_effect_name) {
428 bool found_edge = false;
429
430 if (SawFilteredPosedge(this_sensor, sensorA, sensorB)) {
431 if (min_hall_effect_off_angle_ == max_hall_effect_off_angle_) {
432 LOG(WARNING, "%s: Uncertain which side, rejecting posedge\n", name_);
433 } else {
434 const double average_last_encoder =
435 (min_hall_effect_off_angle_ + max_hall_effect_off_angle_) / 2.0;
Brian Silvermand3efb182015-05-13 23:04:29 -0400436 if (this_sensor.posedge_value() < average_last_encoder) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700437 *edge_angle = angles.upper_decreasing_angle;
438 LOG(INFO, "%s Posedge upper of %s -> %f posedge: %f avg_encoder: %f\n",
Brian Silvermand3efb182015-05-13 23:04:29 -0400439 name_, hall_effect_name, *edge_angle, this_sensor.posedge_value(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700440 average_last_encoder);
441 } else {
442 *edge_angle = angles.lower_angle;
443 LOG(INFO, "%s Posedge lower of %s -> %f posedge: %f avg_encoder: %f\n",
Brian Silvermand3efb182015-05-13 23:04:29 -0400444 name_, hall_effect_name, *edge_angle, this_sensor.posedge_value(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700445 average_last_encoder);
446 }
Brian Silvermand3efb182015-05-13 23:04:29 -0400447 *edge_encoder = this_sensor.posedge_value();
Brian Silverman17f503e2015-08-02 18:17:18 -0700448 found_edge = true;
449 }
450 }
451
452 if (SawFilteredNegedge(this_sensor, sensorA, sensorB)) {
453 if (min_hall_effect_on_angle_ == max_hall_effect_on_angle_) {
454 LOG(WARNING, "%s: Uncertain which side, rejecting negedge\n", name_);
455 } else {
456 const double average_last_encoder =
457 (min_hall_effect_on_angle_ + max_hall_effect_on_angle_) / 2.0;
Brian Silvermand3efb182015-05-13 23:04:29 -0400458 if (this_sensor.negedge_value() > average_last_encoder) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700459 *edge_angle = angles.upper_angle;
460 LOG(INFO, "%s Negedge upper of %s -> %f negedge: %f avg_encoder: %f\n",
Brian Silvermand3efb182015-05-13 23:04:29 -0400461 name_, hall_effect_name, *edge_angle, this_sensor.negedge_value(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700462 average_last_encoder);
463 } else {
464 *edge_angle = angles.lower_decreasing_angle;
465 LOG(INFO, "%s Negedge lower of %s -> %f negedge: %f avg_encoder: %f\n",
Brian Silvermand3efb182015-05-13 23:04:29 -0400466 name_, hall_effect_name, *edge_angle, this_sensor.negedge_value(),
Brian Silverman17f503e2015-08-02 18:17:18 -0700467 average_last_encoder);
468 }
Brian Silvermand3efb182015-05-13 23:04:29 -0400469 *edge_encoder = this_sensor.negedge_value();
Brian Silverman17f503e2015-08-02 18:17:18 -0700470 found_edge = true;
471 }
472 }
473
474 return found_edge;
475}
476
477bool ZeroedStateFeedbackLoop::GetPositionOfEdge(
478 const constants::Values::Claws::Claw &claw_values, double *edge_encoder,
479 double *edge_angle) {
480 if (DoGetPositionOfEdge(claw_values.front, edge_encoder, edge_angle, front_,
481 calibration_, back_, "front")) {
482 return true;
483 }
484 if (DoGetPositionOfEdge(claw_values.calibration, edge_encoder, edge_angle,
485 calibration_, front_, back_, "calibration")) {
486 return true;
487 }
488 if (DoGetPositionOfEdge(claw_values.back, edge_encoder, edge_angle, back_,
489 calibration_, front_, "back")) {
490 return true;
491 }
492 return false;
493}
494
495void TopZeroedStateFeedbackLoop::SetCalibration(double edge_encoder,
496 double edge_angle) {
497 double old_offset = offset_;
498 offset_ = edge_angle - edge_encoder;
499 const double doffset = offset_ - old_offset;
500 motor_->ChangeTopOffset(doffset);
501}
502
503double TopZeroedStateFeedbackLoop::ComputeCalibrationChange(double edge_encoder,
504 double edge_angle) {
505 const double offset = edge_angle - edge_encoder;
506 const double doffset = offset - offset_;
507 return doffset;
508}
509
510void BottomZeroedStateFeedbackLoop::SetCalibration(double edge_encoder,
511 double edge_angle) {
512 double old_offset = offset_;
513 offset_ = edge_angle - edge_encoder;
514 const double doffset = offset_ - old_offset;
515 motor_->ChangeBottomOffset(doffset);
516}
517
518double BottomZeroedStateFeedbackLoop::ComputeCalibrationChange(
519 double edge_encoder, double edge_angle) {
520 const double offset = edge_angle - edge_encoder;
521 const double doffset = offset - offset_;
522 return doffset;
523}
524
525void ClawMotor::ChangeTopOffset(double doffset) {
526 claw_.ChangeTopOffset(doffset);
527 if (has_top_claw_goal_) {
528 top_claw_goal_ += doffset;
529 }
530}
531
532void ClawMotor::ChangeBottomOffset(double doffset) {
533 claw_.ChangeBottomOffset(doffset);
534 if (has_bottom_claw_goal_) {
535 bottom_claw_goal_ += doffset;
536 }
537}
538
539void ClawLimitedLoop::ChangeTopOffset(double doffset) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700540 mutable_X_hat()(1, 0) += doffset;
541 LOG(INFO, "Changing top offset by %f\n", doffset);
542}
543void ClawLimitedLoop::ChangeBottomOffset(double doffset) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700544 mutable_X_hat()(0, 0) += doffset;
545 mutable_X_hat()(1, 0) -= doffset;
546 LOG(INFO, "Changing bottom offset by %f\n", doffset);
547}
548
549void LimitClawGoal(double *bottom_goal, double *top_goal,
550 const frc971::constants::Values &values) {
551 // first update position based on angle limit
552 const double separation = *top_goal - *bottom_goal;
553 if (separation > values.claw.soft_max_separation) {
554 LOG_STRUCT(DEBUG, "before", ClawPositionToLog(*top_goal, *bottom_goal));
555 const double dsep = (separation - values.claw.soft_max_separation) / 2.0;
556 *bottom_goal += dsep;
557 *top_goal -= dsep;
558 LOG_STRUCT(DEBUG, "after", ClawPositionToLog(*top_goal, *bottom_goal));
559 }
560 if (separation < values.claw.soft_min_separation) {
561 LOG_STRUCT(DEBUG, "before", ClawPositionToLog(*top_goal, *bottom_goal));
562 const double dsep = (separation - values.claw.soft_min_separation) / 2.0;
563 *bottom_goal += dsep;
564 *top_goal -= dsep;
565 LOG_STRUCT(DEBUG, "after", ClawPositionToLog(*top_goal, *bottom_goal));
566 }
567
568 // now move both goals in unison
569 if (*bottom_goal < values.claw.lower_claw.lower_limit) {
570 LOG_STRUCT(DEBUG, "before", ClawPositionToLog(*top_goal, *bottom_goal));
571 *top_goal += values.claw.lower_claw.lower_limit - *bottom_goal;
572 *bottom_goal = values.claw.lower_claw.lower_limit;
573 LOG_STRUCT(DEBUG, "after", ClawPositionToLog(*top_goal, *bottom_goal));
574 }
575 if (*bottom_goal > values.claw.lower_claw.upper_limit) {
576 LOG_STRUCT(DEBUG, "before", ClawPositionToLog(*top_goal, *bottom_goal));
577 *top_goal -= *bottom_goal - values.claw.lower_claw.upper_limit;
578 *bottom_goal = values.claw.lower_claw.upper_limit;
579 LOG_STRUCT(DEBUG, "after", ClawPositionToLog(*top_goal, *bottom_goal));
580 }
581
582 if (*top_goal < values.claw.upper_claw.lower_limit) {
583 LOG_STRUCT(DEBUG, "before", ClawPositionToLog(*top_goal, *bottom_goal));
584 *bottom_goal += values.claw.upper_claw.lower_limit - *top_goal;
585 *top_goal = values.claw.upper_claw.lower_limit;
586 LOG_STRUCT(DEBUG, "after", ClawPositionToLog(*top_goal, *bottom_goal));
587 }
588 if (*top_goal > values.claw.upper_claw.upper_limit) {
589 LOG_STRUCT(DEBUG, "before", ClawPositionToLog(*top_goal, *bottom_goal));
590 *bottom_goal -= *top_goal - values.claw.upper_claw.upper_limit;
591 *top_goal = values.claw.upper_claw.upper_limit;
592 LOG_STRUCT(DEBUG, "after", ClawPositionToLog(*top_goal, *bottom_goal));
593 }
594}
595
596bool ClawMotor::is_ready() const {
597 return (
598 (top_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED &&
599 bottom_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED) ||
600 (((::aos::joystick_state.get() == NULL)
601 ? true
602 : ::aos::joystick_state->autonomous) &&
603 ((top_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED ||
604 top_claw_.zeroing_state() ==
605 ZeroedStateFeedbackLoop::DISABLED_CALIBRATION) &&
606 (bottom_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED ||
607 bottom_claw_.zeroing_state() ==
608 ZeroedStateFeedbackLoop::DISABLED_CALIBRATION))));
609}
610
611bool ClawMotor::is_zeroing() const { return !is_ready(); }
612
613// Positive angle is up, and positive power is up.
Brian Silvermanbe5ded62015-05-14 00:23:49 -0400614void ClawMotor::RunIteration(const control_loops::ClawQueue::Goal *goal,
615 const control_loops::ClawQueue::Position *position,
616 control_loops::ClawQueue::Output *output,
617 control_loops::ClawQueue::Status *status) {
Brian Silverman17f503e2015-08-02 18:17:18 -0700618 // Disable the motors now so that all early returns will return with the
619 // motors disabled.
620 if (output) {
621 output->top_claw_voltage = 0;
622 output->bottom_claw_voltage = 0;
623 output->intake_voltage = 0;
624 output->tusk_voltage = 0;
625 }
626
627 if (goal) {
628 if (::std::isnan(goal->bottom_angle) ||
629 ::std::isnan(goal->separation_angle) || ::std::isnan(goal->intake) ||
630 ::std::isnan(goal->centering)) {
631 return;
632 }
633 }
634
635 if (WasReset()) {
636 top_claw_.Reset(position->top);
637 bottom_claw_.Reset(position->bottom);
638 }
639
640 const frc971::constants::Values &values = constants::GetValues();
641
642 if (position) {
643 Eigen::Matrix<double, 2, 1> Y;
644 Y << position->bottom.position + bottom_claw_.offset(),
645 position->top.position + top_claw_.offset();
646 claw_.Correct(Y);
647
648 top_claw_.SetPositionValues(position->top);
649 bottom_claw_.SetPositionValues(position->bottom);
650
651 if (!has_top_claw_goal_) {
652 has_top_claw_goal_ = true;
653 top_claw_goal_ = top_claw_.absolute_position();
654 initial_separation_ =
655 top_claw_.absolute_position() - bottom_claw_.absolute_position();
656 }
657 if (!has_bottom_claw_goal_) {
658 has_bottom_claw_goal_ = true;
659 bottom_claw_goal_ = bottom_claw_.absolute_position();
660 initial_separation_ =
661 top_claw_.absolute_position() - bottom_claw_.absolute_position();
662 }
663 LOG_STRUCT(DEBUG, "absolute position",
664 ClawPositionToLog(top_claw_.absolute_position(),
665 bottom_claw_.absolute_position()));
666 }
667
668 bool autonomous, enabled;
669 if (::aos::joystick_state.get() == nullptr) {
670 autonomous = true;
671 enabled = false;
672 } else {
673 autonomous = ::aos::joystick_state->autonomous;
674 enabled = ::aos::joystick_state->enabled;
675 }
676
677 double bottom_claw_velocity_ = 0.0;
678 double top_claw_velocity_ = 0.0;
679
680 if (goal != NULL &&
681 ((top_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED &&
682 bottom_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED) ||
683 (autonomous &&
684 ((top_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED ||
685 top_claw_.zeroing_state() ==
686 ZeroedStateFeedbackLoop::DISABLED_CALIBRATION) &&
687 (bottom_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED ||
688 bottom_claw_.zeroing_state() ==
689 ZeroedStateFeedbackLoop::DISABLED_CALIBRATION))))) {
690 // Ready to use the claw.
691 // Limit the goals here.
692 bottom_claw_goal_ = goal->bottom_angle;
693 top_claw_goal_ = goal->bottom_angle + goal->separation_angle;
694 has_bottom_claw_goal_ = true;
695 has_top_claw_goal_ = true;
696 doing_calibration_fine_tune_ = false;
697 mode_ = READY;
698
699 bottom_claw_.HandleCalibrationError(values.claw.lower_claw);
700 top_claw_.HandleCalibrationError(values.claw.upper_claw);
701 } else if (top_claw_.zeroing_state() !=
702 ZeroedStateFeedbackLoop::UNKNOWN_POSITION &&
703 bottom_claw_.zeroing_state() !=
704 ZeroedStateFeedbackLoop::UNKNOWN_POSITION) {
705 // Time to fine tune the zero.
706 // Limit the goals here.
707 if (!enabled) {
708 // If we are disabled, start the fine tune process over again.
709 doing_calibration_fine_tune_ = false;
710 }
711 if (bottom_claw_.zeroing_state() != ZeroedStateFeedbackLoop::CALIBRATED) {
712 // always get the bottom claw to calibrated first
713 LOG(DEBUG, "Calibrating the bottom of the claw\n");
714 if (!doing_calibration_fine_tune_) {
715 if (::std::abs(bottom_absolute_position() -
716 values.claw.start_fine_tune_pos) <
717 values.claw.claw_unimportant_epsilon) {
718 doing_calibration_fine_tune_ = true;
Austin Schuh0e997732015-11-08 15:14:53 -0800719 bottom_claw_goal_ += values.claw.claw_zeroing_speed * kDt;
Brian Silverman17f503e2015-08-02 18:17:18 -0700720 top_claw_velocity_ = bottom_claw_velocity_ =
721 values.claw.claw_zeroing_speed;
722 LOG(DEBUG, "Ready to fine tune the bottom\n");
723 mode_ = FINE_TUNE_BOTTOM;
724 } else {
725 // send bottom to zeroing start
726 bottom_claw_goal_ = values.claw.start_fine_tune_pos;
727 LOG(DEBUG, "Going to the start position for the bottom\n");
728 mode_ = PREP_FINE_TUNE_BOTTOM;
729 }
730 } else {
731 mode_ = FINE_TUNE_BOTTOM;
Austin Schuh0e997732015-11-08 15:14:53 -0800732 bottom_claw_goal_ += values.claw.claw_zeroing_speed * kDt;
Brian Silverman17f503e2015-08-02 18:17:18 -0700733 top_claw_velocity_ = bottom_claw_velocity_ =
734 values.claw.claw_zeroing_speed;
735 if (top_claw_.front_or_back_triggered() ||
736 bottom_claw_.front_or_back_triggered()) {
737 // We shouldn't hit a limit, but if we do, go back to the zeroing
738 // point and try again.
739 doing_calibration_fine_tune_ = false;
740 bottom_claw_goal_ = values.claw.start_fine_tune_pos;
741 top_claw_velocity_ = bottom_claw_velocity_ = 0.0;
742 LOG(DEBUG, "Found a limit, starting over.\n");
743 mode_ = PREP_FINE_TUNE_BOTTOM;
744 }
745
746 if (position && bottom_claw_.SawFilteredPosedge(
747 bottom_claw_.calibration(), bottom_claw_.front(),
748 bottom_claw_.back())) {
749 // do calibration
750 bottom_claw_.SetCalibration(
Brian Silvermand3efb182015-05-13 23:04:29 -0400751 position->bottom.calibration.posedge_value,
Brian Silverman17f503e2015-08-02 18:17:18 -0700752 values.claw.lower_claw.calibration.lower_angle);
753 bottom_claw_.set_zeroing_state(ZeroedStateFeedbackLoop::CALIBRATED);
754 // calibrated so we are done fine tuning bottom
755 doing_calibration_fine_tune_ = false;
756 LOG(DEBUG, "Calibrated the bottom correctly!\n");
757 } else if (bottom_claw_.calibration().last_value()) {
758 LOG(DEBUG, "Aborting bottom fine tune because sensor triggered\n");
759 doing_calibration_fine_tune_ = false;
760 bottom_claw_.set_zeroing_state(
761 ZeroedStateFeedbackLoop::UNKNOWN_POSITION);
762 } else {
763 LOG(DEBUG, "Fine tuning\n");
764 }
765 }
766 // now set the top claw to track
767
768 top_claw_goal_ = bottom_claw_goal_ + values.claw.claw_zeroing_separation;
769 } else {
770 // bottom claw must be calibrated, start on the top
771 if (!doing_calibration_fine_tune_) {
772 if (::std::abs(top_absolute_position() -
773 values.claw.start_fine_tune_pos) <
774 values.claw.claw_unimportant_epsilon) {
775 doing_calibration_fine_tune_ = true;
Austin Schuh0e997732015-11-08 15:14:53 -0800776 top_claw_goal_ += values.claw.claw_zeroing_speed * kDt;
Brian Silverman17f503e2015-08-02 18:17:18 -0700777 top_claw_velocity_ = bottom_claw_velocity_ =
778 values.claw.claw_zeroing_speed;
779 LOG(DEBUG, "Ready to fine tune the top\n");
780 mode_ = FINE_TUNE_TOP;
781 } else {
782 // send top to zeroing start
783 top_claw_goal_ = values.claw.start_fine_tune_pos;
784 LOG(DEBUG, "Going to the start position for the top\n");
785 mode_ = PREP_FINE_TUNE_TOP;
786 }
787 } else {
788 mode_ = FINE_TUNE_TOP;
Austin Schuh0e997732015-11-08 15:14:53 -0800789 top_claw_goal_ += values.claw.claw_zeroing_speed * kDt;
Brian Silverman17f503e2015-08-02 18:17:18 -0700790 top_claw_velocity_ = bottom_claw_velocity_ =
791 values.claw.claw_zeroing_speed;
792 if (top_claw_.front_or_back_triggered() ||
793 bottom_claw_.front_or_back_triggered()) {
794 // this should not happen, but now we know it won't
795 doing_calibration_fine_tune_ = false;
796 top_claw_goal_ = values.claw.start_fine_tune_pos;
797 top_claw_velocity_ = bottom_claw_velocity_ = 0.0;
798 LOG(DEBUG, "Found a limit, starting over.\n");
799 mode_ = PREP_FINE_TUNE_TOP;
800 }
801
802 if (position &&
803 top_claw_.SawFilteredPosedge(top_claw_.calibration(),
804 top_claw_.front(), top_claw_.back())) {
805 // do calibration
806 top_claw_.SetCalibration(
Brian Silvermand3efb182015-05-13 23:04:29 -0400807 position->top.calibration.posedge_value,
Brian Silverman17f503e2015-08-02 18:17:18 -0700808 values.claw.upper_claw.calibration.lower_angle);
809 top_claw_.set_zeroing_state(ZeroedStateFeedbackLoop::CALIBRATED);
810 // calibrated so we are done fine tuning top
811 doing_calibration_fine_tune_ = false;
812 LOG(DEBUG, "Calibrated the top correctly!\n");
813 } else if (top_claw_.calibration().last_value()) {
814 LOG(DEBUG, "Aborting top fine tune because sensor triggered\n");
815 doing_calibration_fine_tune_ = false;
816 top_claw_.set_zeroing_state(
817 ZeroedStateFeedbackLoop::UNKNOWN_POSITION);
818 }
819 }
820 // now set the bottom claw to track
821 bottom_claw_goal_ = top_claw_goal_ - values.claw.claw_zeroing_separation;
822 }
823 } else {
824 doing_calibration_fine_tune_ = false;
825 if (!was_enabled_ && enabled) {
826 if (position) {
827 top_claw_goal_ = position->top.position + top_claw_.offset();
828 bottom_claw_goal_ = position->bottom.position + bottom_claw_.offset();
829 initial_separation_ =
830 position->top.position - position->bottom.position;
831 } else {
832 has_top_claw_goal_ = false;
833 has_bottom_claw_goal_ = false;
834 }
835 }
836
837 if ((bottom_claw_.zeroing_state() !=
838 ZeroedStateFeedbackLoop::UNKNOWN_POSITION ||
839 bottom_claw_.front().value() || top_claw_.front().value()) &&
840 !top_claw_.back().value() && !bottom_claw_.back().value()) {
841 if (enabled) {
842 // Time to slowly move back up to find any position to narrow down the
843 // zero.
Austin Schuh0e997732015-11-08 15:14:53 -0800844 top_claw_goal_ += values.claw.claw_zeroing_off_speed * kDt;
845 bottom_claw_goal_ += values.claw.claw_zeroing_off_speed * kDt;
Brian Silverman17f503e2015-08-02 18:17:18 -0700846 top_claw_velocity_ = bottom_claw_velocity_ =
847 values.claw.claw_zeroing_off_speed;
848 LOG(DEBUG, "Bottom is known.\n");
849 }
850 } else {
851 // We don't know where either claw is. Slowly start moving down to find
852 // any hall effect.
853 if (enabled) {
Austin Schuh0e997732015-11-08 15:14:53 -0800854 top_claw_goal_ -= values.claw.claw_zeroing_off_speed * kDt;
855 bottom_claw_goal_ -= values.claw.claw_zeroing_off_speed * kDt;
Brian Silverman17f503e2015-08-02 18:17:18 -0700856 top_claw_velocity_ = bottom_claw_velocity_ =
857 -values.claw.claw_zeroing_off_speed;
858 LOG(DEBUG, "Both are unknown.\n");
859 }
860 }
861
862 if (position) {
863 if (enabled) {
864 top_claw_.SetCalibrationOnEdge(
865 values.claw.upper_claw,
866 ZeroedStateFeedbackLoop::APPROXIMATE_CALIBRATION);
867 bottom_claw_.SetCalibrationOnEdge(
868 values.claw.lower_claw,
869 ZeroedStateFeedbackLoop::APPROXIMATE_CALIBRATION);
870 } else {
871 // TODO(austin): Only calibrate on the predetermined edge.
872 // We might be able to just ignore this since the backlash is soooo
873 // low.
874 // :)
875 top_claw_.SetCalibrationOnEdge(
876 values.claw.upper_claw,
877 ZeroedStateFeedbackLoop::DISABLED_CALIBRATION);
878 bottom_claw_.SetCalibrationOnEdge(
879 values.claw.lower_claw,
880 ZeroedStateFeedbackLoop::DISABLED_CALIBRATION);
881 }
882 }
883 mode_ = UNKNOWN_LOCATION;
884 }
885
886 // Limit the goals if both claws have been (mostly) found.
887 if (mode_ != UNKNOWN_LOCATION) {
888 LimitClawGoal(&bottom_claw_goal_, &top_claw_goal_, values);
889 }
890
891 claw_.set_positions_known(
892 top_claw_.zeroing_state() != ZeroedStateFeedbackLoop::UNKNOWN_POSITION,
893 bottom_claw_.zeroing_state() !=
894 ZeroedStateFeedbackLoop::UNKNOWN_POSITION);
895 if (has_top_claw_goal_ && has_bottom_claw_goal_) {
896 claw_.mutable_R() << bottom_claw_goal_, top_claw_goal_ - bottom_claw_goal_,
897 bottom_claw_velocity_, top_claw_velocity_ - bottom_claw_velocity_;
898 LOG_MATRIX(DEBUG, "actual goal", claw_.R());
899
900 // Only cap power when one of the halves of the claw is moving slowly and
901 // could wind up.
902 claw_.set_is_zeroing(mode_ == UNKNOWN_LOCATION || mode_ == FINE_TUNE_TOP ||
903 mode_ == FINE_TUNE_BOTTOM);
904 claw_.Update(output == nullptr);
905 } else {
906 claw_.Update(true);
907 }
908
909 capped_goal_ = false;
910 switch (mode_) {
911 case READY:
912 case PREP_FINE_TUNE_TOP:
913 case PREP_FINE_TUNE_BOTTOM:
914 break;
915 case FINE_TUNE_BOTTOM:
916 case FINE_TUNE_TOP:
917 case UNKNOWN_LOCATION: {
918 if (claw_.uncapped_average_voltage() > values.claw.max_zeroing_voltage) {
919 double dx_bot = (claw_.U_uncapped(0, 0) -
920 values.claw.max_zeroing_voltage) /
921 claw_.K(0, 0);
922 double dx_top = (claw_.U_uncapped(1, 0) -
923 values.claw.max_zeroing_voltage) /
924 claw_.K(0, 0);
925 double dx = ::std::max(dx_top, dx_bot);
926 bottom_claw_goal_ -= dx;
927 top_claw_goal_ -= dx;
928 Eigen::Matrix<double, 4, 1> R;
929 R << bottom_claw_goal_, top_claw_goal_ - bottom_claw_goal_, claw_.R(2, 0),
930 claw_.R(3, 0);
931 claw_.mutable_U() = claw_.K() * (R - claw_.X_hat());
932 capped_goal_ = true;
933 LOG(DEBUG, "Moving the goal by %f to prevent windup."
934 " Uncapped is %f, max is %f, difference is %f\n",
935 dx,
936 claw_.uncapped_average_voltage(), values.claw.max_zeroing_voltage,
937 (claw_.uncapped_average_voltage() -
938 values.claw.max_zeroing_voltage));
939 } else if (claw_.uncapped_average_voltage() <
940 -values.claw.max_zeroing_voltage) {
941 double dx_bot = (claw_.U_uncapped(0, 0) +
942 values.claw.max_zeroing_voltage) /
943 claw_.K(0, 0);
944 double dx_top = (claw_.U_uncapped(1, 0) +
945 values.claw.max_zeroing_voltage) /
946 claw_.K(0, 0);
947 double dx = ::std::min(dx_top, dx_bot);
948 bottom_claw_goal_ -= dx;
949 top_claw_goal_ -= dx;
950 Eigen::Matrix<double, 4, 1> R;
951 R << bottom_claw_goal_, top_claw_goal_ - bottom_claw_goal_, claw_.R(2, 0),
952 claw_.R(3, 0);
953 claw_.mutable_U() = claw_.K() * (R - claw_.X_hat());
954 capped_goal_ = true;
955 LOG(DEBUG, "Moving the goal by %f to prevent windup\n", dx);
956 }
957 } break;
958 }
959
960 if (output) {
961 if (goal) {
962 //setup the intake
963 output->intake_voltage =
964 (goal->intake > 12.0) ? 12 : (goal->intake < -12.0) ? -12.0
965 : goal->intake;
966 output->tusk_voltage = goal->centering;
967 output->tusk_voltage =
968 (goal->centering > 12.0) ? 12 : (goal->centering < -12.0)
969 ? -12.0
970 : goal->centering;
971 }
972 output->top_claw_voltage = claw_.U(1, 0);
973 output->bottom_claw_voltage = claw_.U(0, 0);
974
975 if (output->top_claw_voltage > kMaxVoltage) {
976 output->top_claw_voltage = kMaxVoltage;
977 } else if (output->top_claw_voltage < -kMaxVoltage) {
978 output->top_claw_voltage = -kMaxVoltage;
979 }
980
981 if (output->bottom_claw_voltage > kMaxVoltage) {
982 output->bottom_claw_voltage = kMaxVoltage;
983 } else if (output->bottom_claw_voltage < -kMaxVoltage) {
984 output->bottom_claw_voltage = -kMaxVoltage;
985 }
986 }
987
988 status->bottom = bottom_absolute_position();
989 status->separation = top_absolute_position() - bottom_absolute_position();
990 status->bottom_velocity = claw_.X_hat(2, 0);
991 status->separation_velocity = claw_.X_hat(3, 0);
992
993 if (goal) {
994 bool bottom_done =
995 ::std::abs(bottom_absolute_position() - goal->bottom_angle) < 0.020;
996 bool bottom_velocity_done = ::std::abs(status->bottom_velocity) < 0.2;
997 bool separation_done =
998 ::std::abs((top_absolute_position() - bottom_absolute_position()) -
999 goal->separation_angle) < 0.020;
1000 bool separation_done_with_ball =
1001 ::std::abs((top_absolute_position() - bottom_absolute_position()) -
1002 goal->separation_angle) < 0.06;
1003 status->done = is_ready() && separation_done && bottom_done && bottom_velocity_done;
1004 status->done_with_ball =
1005 is_ready() && separation_done_with_ball && bottom_done && bottom_velocity_done;
1006 } else {
1007 status->done = status->done_with_ball = false;
1008 }
1009
1010 status->zeroed = is_ready();
1011 status->zeroed_for_auto =
1012 (top_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED ||
1013 top_claw_.zeroing_state() ==
1014 ZeroedStateFeedbackLoop::DISABLED_CALIBRATION) &&
1015 (bottom_claw_.zeroing_state() == ZeroedStateFeedbackLoop::CALIBRATED ||
1016 bottom_claw_.zeroing_state() ==
1017 ZeroedStateFeedbackLoop::DISABLED_CALIBRATION);
1018
1019 was_enabled_ = enabled;
1020}
1021
1022} // namespace control_loops
1023} // namespace frc971
1024