blob: 65ee72ed8dd237e4f4fa458fbf31bf6e5873be3a [file] [log] [blame]
Ben Fredrickson6b5ba792015-01-25 17:14:40 -08001#include "frc971/control_loops/fridge/fridge.h"
2
Austin Schuh703b8d42015-02-01 14:56:34 -08003#include <cmath>
4
Ben Fredrickson6b5ba792015-01-25 17:14:40 -08005#include "aos/common/controls/control_loops.q.h"
6#include "aos/common/logging/logging.h"
7
8#include "frc971/control_loops/fridge/elevator_motor_plant.h"
9#include "frc971/control_loops/fridge/arm_motor_plant.h"
Austin Schuh703b8d42015-02-01 14:56:34 -080010#include "frc971/zeroing/zeroing.h"
11
12#include "frc971/constants.h"
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080013
14namespace frc971 {
15namespace control_loops {
16
Austin Schuh703b8d42015-02-01 14:56:34 -080017constexpr double Fridge::dt;
18
19namespace {
Austin Schuhdbd6bfa2015-02-14 21:25:16 -080020constexpr double kZeroingVoltage = 5.0;
Austin Schuh703b8d42015-02-01 14:56:34 -080021constexpr double kElevatorZeroingVelocity = 0.1;
Austin Schuhdbd6bfa2015-02-14 21:25:16 -080022constexpr double kArmZeroingVelocity = 0.2;
Austin Schuh703b8d42015-02-01 14:56:34 -080023} // namespace
24
25
26void CappedStateFeedbackLoop::CapU() {
27 // TODO(austin): Use Campbell's code.
28 if (mutable_U(0, 0) > max_voltage_) {
29 mutable_U(0, 0) = max_voltage_;
30 }
31 if (mutable_U(1, 0) > max_voltage_) {
32 mutable_U(1, 0) = max_voltage_;
33 }
34 if (mutable_U(0, 0) < -max_voltage_) {
35 mutable_U(0, 0) = -max_voltage_;
36 }
37 if (mutable_U(1, 0) < -max_voltage_) {
38 mutable_U(1, 0) = -max_voltage_;
39 }
40}
41
42Eigen::Matrix<double, 2, 1>
43CappedStateFeedbackLoop::UnsaturateOutputGoalChange() {
44 // Compute the K matrix used to compensate for position errors.
45 Eigen::Matrix<double, 2, 2> Kp;
46 Kp.setZero();
47 Kp.col(0) = K().col(0);
48 Kp.col(1) = K().col(2);
49
50 Eigen::Matrix<double, 2, 2> Kp_inv = Kp.inverse();
51
52 // Compute how much we need to change R in order to achieve the change in U
53 // that was observed.
54 Eigen::Matrix<double, 2, 1> deltaR = -Kp_inv * (U_uncapped() - U());
55 return deltaR;
56}
57
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080058Fridge::Fridge(control_loops::FridgeQueue *fridge)
Brian Silverman089f5812015-02-15 01:58:19 -050059 : aos::controls::ControlLoop<control_loops::FridgeQueue>(fridge),
Austin Schuh703b8d42015-02-01 14:56:34 -080060 arm_loop_(new CappedStateFeedbackLoop(
61 StateFeedbackLoop<4, 2, 2>(MakeArmLoop()))),
62 elevator_loop_(new CappedStateFeedbackLoop(
63 StateFeedbackLoop<4, 2, 2>(MakeElevatorLoop()))),
Daniel Pettia7827412015-02-13 20:55:57 -080064 left_arm_estimator_(constants::GetValues().fridge.left_arm_zeroing),
65 right_arm_estimator_(constants::GetValues().fridge.right_arm_zeroing),
Austin Schuh482a4e12015-02-14 22:43:43 -080066 left_elevator_estimator_(constants::GetValues().fridge.left_elev_zeroing),
Austin Schuh703b8d42015-02-01 14:56:34 -080067 right_elevator_estimator_(
Daniel Pettia7827412015-02-13 20:55:57 -080068 constants::GetValues().fridge.right_elev_zeroing) {}
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080069
Austin Schuh703b8d42015-02-01 14:56:34 -080070void Fridge::UpdateZeroingState() {
Austin Schuh47bf6902015-02-14 22:46:22 -080071 if (left_elevator_estimator_.offset_ratio_ready() < 1.0 ||
72 right_elevator_estimator_.offset_ratio_ready() < 1.0 ||
73 left_arm_estimator_.offset_ratio_ready() < 1.0 ||
74 right_arm_estimator_.offset_ratio_ready() < 1.0) {
Austin Schuh703b8d42015-02-01 14:56:34 -080075 state_ = INITIALIZING;
76 } else if (!left_elevator_estimator_.zeroed() ||
77 !right_elevator_estimator_.zeroed()) {
78 state_ = ZEROING_ELEVATOR;
79 } else if (!left_arm_estimator_.zeroed() ||
80 !right_arm_estimator_.zeroed()) {
81 state_ = ZEROING_ARM;
82 } else {
83 state_ = RUNNING;
84 }
85}
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080086
Austin Schuh703b8d42015-02-01 14:56:34 -080087void Fridge::Correct() {
88 {
89 Eigen::Matrix<double, 2, 1> Y;
90 Y << left_elevator(), right_elevator();
91 elevator_loop_->Correct(Y);
92 }
93
94 {
95 Eigen::Matrix<double, 2, 1> Y;
96 Y << left_arm(), right_arm();
97 arm_loop_->Correct(Y);
98 }
99}
100
101void Fridge::SetElevatorOffset(double left_offset, double right_offset) {
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800102 LOG(INFO, "Changing Elevator offset from %f, %f to %f, %f\n",
103 left_elevator_offset_, right_elevator_offset_, left_offset, right_offset);
Austin Schuh703b8d42015-02-01 14:56:34 -0800104 double left_doffset = left_offset - left_elevator_offset_;
105 double right_doffset = right_offset - right_elevator_offset_;
106
107 // Adjust the average height and height difference between the two sides.
108 // The derivatives of both should not need to be updated since the speeds
109 // haven't changed.
110 // The height difference is calculated as left - right, not right - left.
111 elevator_loop_->mutable_X_hat(0, 0) += (left_doffset + right_doffset) / 2;
112 elevator_loop_->mutable_X_hat(2, 0) += (left_doffset - right_doffset) / 2;
113
114 // Modify the zeroing goal.
115 elevator_goal_ += (left_doffset + right_doffset) / 2;
116
117 // Update the cached offset values to the actual values.
118 left_elevator_offset_ = left_offset;
119 right_elevator_offset_ = right_offset;
120}
121
122void Fridge::SetArmOffset(double left_offset, double right_offset) {
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800123 LOG(INFO, "Changing Arm offset from %f, %f to %f, %f\n", left_arm_offset_,
124 right_arm_offset_, left_offset, right_offset);
Austin Schuh703b8d42015-02-01 14:56:34 -0800125 double left_doffset = left_offset - left_arm_offset_;
126 double right_doffset = right_offset - right_arm_offset_;
127
128 // Adjust the average angle and angle difference between the two sides.
129 // The derivatives of both should not need to be updated since the speeds
130 // haven't changed.
131 arm_loop_->mutable_X_hat(0, 0) += (left_doffset + right_doffset) / 2;
132 arm_loop_->mutable_X_hat(2, 0) += (left_doffset - right_doffset) / 2;
133
134 // Modify the zeroing goal.
135 arm_goal_ += (left_doffset + right_doffset) / 2;
136
137 // Update the cached offset values to the actual values.
138 left_arm_offset_ = left_offset;
139 right_arm_offset_ = right_offset;
140}
141
142double Fridge::estimated_left_elevator() {
143 return current_position_.elevator.left.encoder +
144 left_elevator_estimator_.offset();
145}
146double Fridge::estimated_right_elevator() {
147 return current_position_.elevator.right.encoder +
148 right_elevator_estimator_.offset();
149}
150
151double Fridge::estimated_elevator() {
152 return (estimated_left_elevator() + estimated_right_elevator()) / 2.0;
153}
154
155double Fridge::estimated_left_arm() {
156 return current_position_.elevator.left.encoder + left_arm_estimator_.offset();
157}
158double Fridge::estimated_right_arm() {
159 return current_position_.elevator.right.encoder +
160 right_arm_estimator_.offset();
161}
162double Fridge::estimated_arm() {
163 return (estimated_left_arm() + estimated_right_arm()) / 2.0;
164}
165
166double Fridge::left_elevator() {
167 return current_position_.elevator.left.encoder + left_elevator_offset_;
168}
169double Fridge::right_elevator() {
170 return current_position_.elevator.right.encoder + right_elevator_offset_;
171}
172
173double Fridge::elevator() { return (left_elevator() + right_elevator()) / 2.0; }
174
175double Fridge::left_arm() {
176 return current_position_.arm.left.encoder + left_arm_offset_;
177}
178double Fridge::right_arm() {
179 return current_position_.arm.right.encoder + right_arm_offset_;
180}
181double Fridge::arm() { return (left_arm() + right_arm()) / 2.0; }
182
183double Fridge::elevator_zeroing_velocity() {
184 double average_elevator =
185 (constants::GetValues().fridge.elevator.lower_limit +
186 constants::GetValues().fridge.elevator.upper_limit) /
187 2.0;
188
189 const double pulse_width = ::std::max(
Daniel Pettia7827412015-02-13 20:55:57 -0800190 constants::GetValues().fridge.left_elev_zeroing.index_difference,
191 constants::GetValues().fridge.right_elev_zeroing.index_difference);
Austin Schuh703b8d42015-02-01 14:56:34 -0800192
193 if (elevator_zeroing_velocity_ == 0) {
194 if (estimated_elevator() > average_elevator) {
195 elevator_zeroing_velocity_ = -kElevatorZeroingVelocity;
196 } else {
197 elevator_zeroing_velocity_ = kElevatorZeroingVelocity;
198 }
199 } else if (elevator_zeroing_velocity_ > 0 &&
200 estimated_elevator() > average_elevator + 2 * pulse_width) {
201 elevator_zeroing_velocity_ = -kElevatorZeroingVelocity;
202 } else if (elevator_zeroing_velocity_ < 0 &&
203 estimated_elevator() < average_elevator - 2 * pulse_width) {
204 elevator_zeroing_velocity_ = kElevatorZeroingVelocity;
205 }
206 return elevator_zeroing_velocity_;
207}
208
209double Fridge::arm_zeroing_velocity() {
210 const double average_arm = (constants::GetValues().fridge.arm.lower_limit +
211 constants::GetValues().fridge.arm.upper_limit) /
212 2.0;
213 const double pulse_width = ::std::max(
Daniel Pettia7827412015-02-13 20:55:57 -0800214 constants::GetValues().fridge.right_arm_zeroing.index_difference,
215 constants::GetValues().fridge.left_arm_zeroing.index_difference);
Austin Schuh703b8d42015-02-01 14:56:34 -0800216
217 if (arm_zeroing_velocity_ == 0) {
218 if (estimated_arm() > average_arm) {
219 arm_zeroing_velocity_ = -kArmZeroingVelocity;
220 } else {
221 arm_zeroing_velocity_ = kArmZeroingVelocity;
222 }
223 } else if (arm_zeroing_velocity_ > 0.0 &&
224 estimated_arm() > average_arm + 2.0 * pulse_width) {
225 arm_zeroing_velocity_ = -kArmZeroingVelocity;
226 } else if (arm_zeroing_velocity_ < 0.0 &&
227 estimated_arm() < average_arm - 2.0 * pulse_width) {
228 arm_zeroing_velocity_ = kArmZeroingVelocity;
229 }
230 return arm_zeroing_velocity_;
231}
232
Austin Schuh482a4e12015-02-14 22:43:43 -0800233void Fridge::RunIteration(const control_loops::FridgeQueue::Goal *unsafe_goal,
Austin Schuh703b8d42015-02-01 14:56:34 -0800234 const control_loops::FridgeQueue::Position *position,
235 control_loops::FridgeQueue::Output *output,
236 control_loops::FridgeQueue::Status *status) {
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800237 if (WasReset()) {
238 LOG(ERROR, "WPILib reset, restarting\n");
239 left_elevator_estimator_.Reset();
240 right_elevator_estimator_.Reset();
241 left_arm_estimator_.Reset();
242 right_arm_estimator_.Reset();
243 state_ = UNINITIALIZED;
244 }
245
Austin Schuh703b8d42015-02-01 14:56:34 -0800246 // Get a reference to the constants struct since we use it so often in this
247 // code.
248 auto &values = constants::GetValues();
249
250 // Bool to track if we should turn the motors on or not.
251 bool disable = output == nullptr;
252 double elevator_goal_velocity = 0.0;
253 double arm_goal_velocity = 0.0;
254
255 // Save the current position so it can be used easily in the class.
256 current_position_ = *position;
257
258 left_elevator_estimator_.UpdateEstimate(position->elevator.left);
259 right_elevator_estimator_.UpdateEstimate(position->elevator.right);
260 left_arm_estimator_.UpdateEstimate(position->arm.left);
261 right_arm_estimator_.UpdateEstimate(position->arm.right);
262
263 if (state_ != UNINITIALIZED) {
264 Correct();
265 }
266
267 // Zeroing will work as follows:
268 // At startup, record the offset of the two halves of the two subsystems.
269 // Then, start moving the elevator towards the center until both halves are
270 // zeroed.
271 // Then, start moving the claw towards the center until both halves are
272 // zeroed.
273 // Then, done!
274
275 // We'll then need code to do sanity checking on values.
276
277 // Now, we need to figure out which way to go.
278
279 switch (state_) {
280 case UNINITIALIZED:
281 LOG(DEBUG, "Uninitialized\n");
282 // Startup. Assume that we are at the origin everywhere.
283 // This records the encoder offset between the two sides of the elevator.
284 left_elevator_offset_ = -position->elevator.left.encoder;
285 right_elevator_offset_ = -position->elevator.right.encoder;
286 left_arm_offset_ = -position->arm.left.encoder;
287 right_arm_offset_ = -position->arm.right.encoder;
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800288 LOG(INFO, "Initializing arm offsets to %f, %f\n", left_arm_offset_,
289 right_arm_offset_);
290 LOG(INFO, "Initializing elevator offsets to %f, %f\n",
291 left_elevator_offset_, right_elevator_offset_);
Austin Schuh703b8d42015-02-01 14:56:34 -0800292 Correct();
293 state_ = INITIALIZING;
294 disable = true;
295 break;
296
297 case INITIALIZING:
298 LOG(DEBUG, "Waiting for accurate initial position.\n");
299 disable = true;
300 // Update state_ to accurately represent the state of the zeroing
301 // estimators.
302 UpdateZeroingState();
303 if (state_ != INITIALIZING) {
304 // Set the goals to where we are now.
305 elevator_goal_ = elevator();
306 arm_goal_ = arm();
307 }
308 break;
309
310 case ZEROING_ELEVATOR:
311 LOG(DEBUG, "Zeroing elevator\n");
Austin Schuh703b8d42015-02-01 14:56:34 -0800312
313 // Update state_ to accurately represent the state of the zeroing
314 // estimators.
315 UpdateZeroingState();
316 if (left_elevator_estimator_.zeroed() &&
317 right_elevator_estimator_.zeroed()) {
318 SetElevatorOffset(left_elevator_estimator_.offset(),
319 right_elevator_estimator_.offset());
320 LOG(DEBUG, "Zeroed the elevator!\n");
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800321 } else if (!disable) {
322 elevator_goal_velocity = elevator_zeroing_velocity();
323 elevator_goal_ += elevator_goal_velocity * dt;
Austin Schuh703b8d42015-02-01 14:56:34 -0800324 }
325 break;
326
327 case ZEROING_ARM:
328 LOG(DEBUG, "Zeroing the arm\n");
Austin Schuh703b8d42015-02-01 14:56:34 -0800329
330 // Update state_ to accurately represent the state of the zeroing
331 // estimators.
332 UpdateZeroingState();
333 if (left_arm_estimator_.zeroed() && right_arm_estimator_.zeroed()) {
334 SetArmOffset(left_arm_estimator_.offset(),
335 right_arm_estimator_.offset());
336 LOG(DEBUG, "Zeroed the arm!\n");
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800337 } else if (!disable) {
338 arm_goal_velocity = arm_zeroing_velocity();
339 arm_goal_ += arm_goal_velocity * dt;
Austin Schuh703b8d42015-02-01 14:56:34 -0800340 }
341 break;
342
343 case RUNNING:
344 LOG(DEBUG, "Running!\n");
Austin Schuh482a4e12015-02-14 22:43:43 -0800345 if (unsafe_goal) {
346 arm_goal_velocity = unsafe_goal->angular_velocity;
347 elevator_goal_velocity = unsafe_goal->velocity;
348 }
Austin Schuh703b8d42015-02-01 14:56:34 -0800349
350 // Update state_ to accurately represent the state of the zeroing
351 // estimators.
352 UpdateZeroingState();
Austin Schuh482a4e12015-02-14 22:43:43 -0800353 if (unsafe_goal) {
354 arm_goal_ = unsafe_goal->angle;
355 elevator_goal_ = unsafe_goal->height;
356 }
Austin Schuh703b8d42015-02-01 14:56:34 -0800357
358 if (state_ != RUNNING && state_ != ESTOP) {
359 state_ = UNINITIALIZED;
360 }
361 break;
362
363 case ESTOP:
364 LOG(ERROR, "Estop\n");
365 disable = true;
366 break;
367 }
368
369 // Commence death if either left/right tracking error gets too big. This
370 // should run immediately after the SetArmOffset and SetElevatorOffset
371 // functions to double-check that the hardware is in a sane state.
372 if (::std::abs(left_arm() - right_arm()) >=
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800373 values.max_allowed_left_right_arm_difference) {
374 LOG(ERROR, "The arms are too far apart. |%f - %f| > %f\n", left_arm(),
375 right_arm(), values.max_allowed_left_right_arm_difference);
376
377 // Indicate an ESTOP condition and stop the motors.
378 state_ = ESTOP;
379 disable = true;
380 }
381
382 if (::std::abs(left_elevator() - right_elevator()) >=
383 values.max_allowed_left_right_elevator_difference) {
384 LOG(ERROR, "The elevators are too far apart. |%f - %f| > %f\n",
385 left_elevator(), right_elevator(),
386 values.max_allowed_left_right_elevator_difference);
Austin Schuh703b8d42015-02-01 14:56:34 -0800387
388 // Indicate an ESTOP condition and stop the motors.
389 state_ = ESTOP;
390 disable = true;
391 }
392
393 // Limit the goals so we can't exceed the hardware limits if we are RUNNING.
394 if (state_ == RUNNING) {
395 // Limit the arm goal to min/max allowable angles.
396 if (arm_goal_ >= values.fridge.arm.upper_limit) {
397 LOG(WARNING, "Arm goal above limit, %f > %f\n", arm_goal_,
398 values.fridge.arm.upper_limit);
399 arm_goal_ = values.fridge.arm.upper_limit;
400 }
401 if (arm_goal_ <= values.fridge.arm.lower_limit) {
402 LOG(WARNING, "Arm goal below limit, %f < %f\n", arm_goal_,
403 values.fridge.arm.lower_limit);
404 arm_goal_ = values.fridge.arm.lower_limit;
405 }
406
407 // Limit the elevator goal to min/max allowable heights.
408 if (elevator_goal_ >= values.fridge.elevator.upper_limit) {
409 LOG(WARNING, "Elevator goal above limit, %f > %f\n", elevator_goal_,
410 values.fridge.elevator.upper_limit);
411 elevator_goal_ = values.fridge.elevator.upper_limit;
412 }
413 if (elevator_goal_ <= values.fridge.elevator.lower_limit) {
414 LOG(WARNING, "Elevator goal below limit, %f < %f\n", elevator_goal_,
415 values.fridge.elevator.lower_limit);
416 elevator_goal_ = values.fridge.elevator.lower_limit;
417 }
418 }
419
420 // Check the lower level hardware limit as well.
421 if (state_ == RUNNING) {
422 if (left_arm() >= values.fridge.arm.upper_hard_limit ||
423 left_arm() <= values.fridge.arm.lower_hard_limit) {
424 LOG(ERROR, "Left arm at %f out of bounds [%f, %f], ESTOPing\n",
425 left_arm(), values.fridge.arm.lower_hard_limit,
426 values.fridge.arm.upper_hard_limit);
427 state_ = ESTOP;
428 }
429
430 if (right_arm() >= values.fridge.arm.upper_hard_limit ||
431 right_arm() <= values.fridge.arm.lower_hard_limit) {
432 LOG(ERROR, "Right arm at %f out of bounds [%f, %f], ESTOPing\n",
433 right_arm(), values.fridge.arm.lower_hard_limit,
434 values.fridge.arm.upper_hard_limit);
435 state_ = ESTOP;
436 }
437
438 if (left_elevator() >= values.fridge.elevator.upper_hard_limit ||
439 left_elevator() <= values.fridge.elevator.lower_hard_limit) {
440 LOG(ERROR, "Left elevator at %f out of bounds [%f, %f], ESTOPing\n",
441 left_elevator(), values.fridge.elevator.lower_hard_limit,
442 values.fridge.elevator.upper_hard_limit);
443 state_ = ESTOP;
444 }
445
446 if (right_elevator() >= values.fridge.elevator.upper_hard_limit ||
447 right_elevator() <= values.fridge.elevator.lower_hard_limit) {
448 LOG(ERROR, "Right elevator at %f out of bounds [%f, %f], ESTOPing\n",
449 right_elevator(), values.fridge.elevator.lower_hard_limit,
450 values.fridge.elevator.upper_hard_limit);
451 state_ = ESTOP;
452 }
453 }
454
455 // Set the goals.
456 arm_loop_->mutable_R() << arm_goal_, arm_goal_velocity, 0.0, 0.0;
457 elevator_loop_->mutable_R() << elevator_goal_, elevator_goal_velocity, 0.0,
458 0.0;
459
460 const double max_voltage = state_ == RUNNING ? 12.0 : kZeroingVoltage;
461 arm_loop_->set_max_voltage(max_voltage);
462 elevator_loop_->set_max_voltage(max_voltage);
463
464 if (state_ == ESTOP) {
465 disable = true;
466 }
467 arm_loop_->Update(disable);
468 elevator_loop_->Update(disable);
469
470 if (state_ == INITIALIZING || state_ == ZEROING_ELEVATOR ||
471 state_ == ZEROING_ARM) {
472 if (arm_loop_->U() != arm_loop_->U_uncapped()) {
473 Eigen::Matrix<double, 2, 1> deltaR =
474 arm_loop_->UnsaturateOutputGoalChange();
475
476 // Move the average arm goal by the amount observed.
477 LOG(WARNING, "Moving arm goal by %f to handle saturation\n",
478 deltaR(0, 0));
479 arm_goal_ += deltaR(0, 0);
480 }
481
482 if (elevator_loop_->U() != elevator_loop_->U_uncapped()) {
483 Eigen::Matrix<double, 2, 1> deltaR =
484 elevator_loop_->UnsaturateOutputGoalChange();
485
486 // Move the average elevator goal by the amount observed.
487 LOG(WARNING, "Moving elevator goal by %f to handle saturation\n",
488 deltaR(0, 0));
489 elevator_goal_ += deltaR(0, 0);
490 }
491 }
492
493 if (output) {
494 output->left_arm = arm_loop_->U(0, 0);
495 output->right_arm = arm_loop_->U(1, 0);
496 output->left_elevator = elevator_loop_->U(0, 0);
497 output->right_elevator = elevator_loop_->U(1, 0);
Austin Schuh482a4e12015-02-14 22:43:43 -0800498 if (unsafe_goal) {
499 output->grabbers = unsafe_goal->grabbers;
500 } else {
501 output->grabbers.top_front = false;
502 output->grabbers.top_back = false;
503 output->grabbers.bottom_front = false;
504 output->grabbers.bottom_back = false;
505 }
Austin Schuh703b8d42015-02-01 14:56:34 -0800506 }
507
508 // TODO(austin): Populate these fully.
509 status->zeroed = false;
510 status->done = false;
511 status->angle = arm_loop_->X_hat(0, 0);
512 status->height = elevator_loop_->X_hat(0, 0);
Austin Schuh482a4e12015-02-14 22:43:43 -0800513 if (unsafe_goal) {
514 status->grabbers = unsafe_goal->grabbers;
515 } else {
516 status->grabbers.top_front = false;
517 status->grabbers.top_back = false;
518 status->grabbers.bottom_front = false;
519 status->grabbers.bottom_back = false;
520 }
Austin Schuh703b8d42015-02-01 14:56:34 -0800521 status->estopped = (state_ == ESTOP);
Austin Schuh482a4e12015-02-14 22:43:43 -0800522 status->state = state_;
Austin Schuh703b8d42015-02-01 14:56:34 -0800523 last_state_ = state_;
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800524}
525
526} // namespace control_loops
527} // namespace frc971