blob: 61152e4a72142fbce1e7c513fdd64bc2df6b7bc5 [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)
59 : 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 Schuh703b8d42015-02-01 14:56:34 -080066 left_elevator_estimator_(
Daniel Pettia7827412015-02-13 20:55:57 -080067 constants::GetValues().fridge.left_elev_zeroing),
Austin Schuh703b8d42015-02-01 14:56:34 -080068 right_elevator_estimator_(
Daniel Pettia7827412015-02-13 20:55:57 -080069 constants::GetValues().fridge.right_elev_zeroing) {}
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080070
Austin Schuh703b8d42015-02-01 14:56:34 -080071void Fridge::UpdateZeroingState() {
72 if (left_elevator_estimator_.offset_ratio_ready() < 0.5 ||
73 right_elevator_estimator_.offset_ratio_ready() < 0.5 ||
74 left_arm_estimator_.offset_ratio_ready() < 0.5 ||
75 right_arm_estimator_.offset_ratio_ready() < 0.5) {
76 state_ = INITIALIZING;
77 } else if (!left_elevator_estimator_.zeroed() ||
78 !right_elevator_estimator_.zeroed()) {
79 state_ = ZEROING_ELEVATOR;
80 } else if (!left_arm_estimator_.zeroed() ||
81 !right_arm_estimator_.zeroed()) {
82 state_ = ZEROING_ARM;
83 } else {
84 state_ = RUNNING;
85 }
86}
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080087
Austin Schuh703b8d42015-02-01 14:56:34 -080088void Fridge::Correct() {
89 {
90 Eigen::Matrix<double, 2, 1> Y;
91 Y << left_elevator(), right_elevator();
92 elevator_loop_->Correct(Y);
93 }
94
95 {
96 Eigen::Matrix<double, 2, 1> Y;
97 Y << left_arm(), right_arm();
98 arm_loop_->Correct(Y);
99 }
100}
101
102void Fridge::SetElevatorOffset(double left_offset, double right_offset) {
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800103 LOG(INFO, "Changing Elevator offset from %f, %f to %f, %f\n",
104 left_elevator_offset_, right_elevator_offset_, left_offset, right_offset);
Austin Schuh703b8d42015-02-01 14:56:34 -0800105 double left_doffset = left_offset - left_elevator_offset_;
106 double right_doffset = right_offset - right_elevator_offset_;
107
108 // Adjust the average height and height difference between the two sides.
109 // The derivatives of both should not need to be updated since the speeds
110 // haven't changed.
111 // The height difference is calculated as left - right, not right - left.
112 elevator_loop_->mutable_X_hat(0, 0) += (left_doffset + right_doffset) / 2;
113 elevator_loop_->mutable_X_hat(2, 0) += (left_doffset - right_doffset) / 2;
114
115 // Modify the zeroing goal.
116 elevator_goal_ += (left_doffset + right_doffset) / 2;
117
118 // Update the cached offset values to the actual values.
119 left_elevator_offset_ = left_offset;
120 right_elevator_offset_ = right_offset;
121}
122
123void Fridge::SetArmOffset(double left_offset, double right_offset) {
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800124 LOG(INFO, "Changing Arm offset from %f, %f to %f, %f\n", left_arm_offset_,
125 right_arm_offset_, left_offset, right_offset);
Austin Schuh703b8d42015-02-01 14:56:34 -0800126 double left_doffset = left_offset - left_arm_offset_;
127 double right_doffset = right_offset - right_arm_offset_;
128
129 // Adjust the average angle and angle difference between the two sides.
130 // The derivatives of both should not need to be updated since the speeds
131 // haven't changed.
132 arm_loop_->mutable_X_hat(0, 0) += (left_doffset + right_doffset) / 2;
133 arm_loop_->mutable_X_hat(2, 0) += (left_doffset - right_doffset) / 2;
134
135 // Modify the zeroing goal.
136 arm_goal_ += (left_doffset + right_doffset) / 2;
137
138 // Update the cached offset values to the actual values.
139 left_arm_offset_ = left_offset;
140 right_arm_offset_ = right_offset;
141}
142
143double Fridge::estimated_left_elevator() {
144 return current_position_.elevator.left.encoder +
145 left_elevator_estimator_.offset();
146}
147double Fridge::estimated_right_elevator() {
148 return current_position_.elevator.right.encoder +
149 right_elevator_estimator_.offset();
150}
151
152double Fridge::estimated_elevator() {
153 return (estimated_left_elevator() + estimated_right_elevator()) / 2.0;
154}
155
156double Fridge::estimated_left_arm() {
157 return current_position_.elevator.left.encoder + left_arm_estimator_.offset();
158}
159double Fridge::estimated_right_arm() {
160 return current_position_.elevator.right.encoder +
161 right_arm_estimator_.offset();
162}
163double Fridge::estimated_arm() {
164 return (estimated_left_arm() + estimated_right_arm()) / 2.0;
165}
166
167double Fridge::left_elevator() {
168 return current_position_.elevator.left.encoder + left_elevator_offset_;
169}
170double Fridge::right_elevator() {
171 return current_position_.elevator.right.encoder + right_elevator_offset_;
172}
173
174double Fridge::elevator() { return (left_elevator() + right_elevator()) / 2.0; }
175
176double Fridge::left_arm() {
177 return current_position_.arm.left.encoder + left_arm_offset_;
178}
179double Fridge::right_arm() {
180 return current_position_.arm.right.encoder + right_arm_offset_;
181}
182double Fridge::arm() { return (left_arm() + right_arm()) / 2.0; }
183
184double Fridge::elevator_zeroing_velocity() {
185 double average_elevator =
186 (constants::GetValues().fridge.elevator.lower_limit +
187 constants::GetValues().fridge.elevator.upper_limit) /
188 2.0;
189
190 const double pulse_width = ::std::max(
Daniel Pettia7827412015-02-13 20:55:57 -0800191 constants::GetValues().fridge.left_elev_zeroing.index_difference,
192 constants::GetValues().fridge.right_elev_zeroing.index_difference);
Austin Schuh703b8d42015-02-01 14:56:34 -0800193
194 if (elevator_zeroing_velocity_ == 0) {
195 if (estimated_elevator() > average_elevator) {
196 elevator_zeroing_velocity_ = -kElevatorZeroingVelocity;
197 } else {
198 elevator_zeroing_velocity_ = kElevatorZeroingVelocity;
199 }
200 } else if (elevator_zeroing_velocity_ > 0 &&
201 estimated_elevator() > average_elevator + 2 * pulse_width) {
202 elevator_zeroing_velocity_ = -kElevatorZeroingVelocity;
203 } else if (elevator_zeroing_velocity_ < 0 &&
204 estimated_elevator() < average_elevator - 2 * pulse_width) {
205 elevator_zeroing_velocity_ = kElevatorZeroingVelocity;
206 }
207 return elevator_zeroing_velocity_;
208}
209
210double Fridge::arm_zeroing_velocity() {
211 const double average_arm = (constants::GetValues().fridge.arm.lower_limit +
212 constants::GetValues().fridge.arm.upper_limit) /
213 2.0;
214 const double pulse_width = ::std::max(
Daniel Pettia7827412015-02-13 20:55:57 -0800215 constants::GetValues().fridge.right_arm_zeroing.index_difference,
216 constants::GetValues().fridge.left_arm_zeroing.index_difference);
Austin Schuh703b8d42015-02-01 14:56:34 -0800217
218 if (arm_zeroing_velocity_ == 0) {
219 if (estimated_arm() > average_arm) {
220 arm_zeroing_velocity_ = -kArmZeroingVelocity;
221 } else {
222 arm_zeroing_velocity_ = kArmZeroingVelocity;
223 }
224 } else if (arm_zeroing_velocity_ > 0.0 &&
225 estimated_arm() > average_arm + 2.0 * pulse_width) {
226 arm_zeroing_velocity_ = -kArmZeroingVelocity;
227 } else if (arm_zeroing_velocity_ < 0.0 &&
228 estimated_arm() < average_arm - 2.0 * pulse_width) {
229 arm_zeroing_velocity_ = kArmZeroingVelocity;
230 }
231 return arm_zeroing_velocity_;
232}
233
234void Fridge::RunIteration(const control_loops::FridgeQueue::Goal *goal,
235 const control_loops::FridgeQueue::Position *position,
236 control_loops::FridgeQueue::Output *output,
237 control_loops::FridgeQueue::Status *status) {
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800238 if (WasReset()) {
239 LOG(ERROR, "WPILib reset, restarting\n");
240 left_elevator_estimator_.Reset();
241 right_elevator_estimator_.Reset();
242 left_arm_estimator_.Reset();
243 right_arm_estimator_.Reset();
244 state_ = UNINITIALIZED;
245 }
246
Austin Schuh703b8d42015-02-01 14:56:34 -0800247 // Get a reference to the constants struct since we use it so often in this
248 // code.
249 auto &values = constants::GetValues();
250
251 // Bool to track if we should turn the motors on or not.
252 bool disable = output == nullptr;
253 double elevator_goal_velocity = 0.0;
254 double arm_goal_velocity = 0.0;
255
256 // Save the current position so it can be used easily in the class.
257 current_position_ = *position;
258
259 left_elevator_estimator_.UpdateEstimate(position->elevator.left);
260 right_elevator_estimator_.UpdateEstimate(position->elevator.right);
261 left_arm_estimator_.UpdateEstimate(position->arm.left);
262 right_arm_estimator_.UpdateEstimate(position->arm.right);
263
264 if (state_ != UNINITIALIZED) {
265 Correct();
266 }
267
268 // Zeroing will work as follows:
269 // At startup, record the offset of the two halves of the two subsystems.
270 // Then, start moving the elevator towards the center until both halves are
271 // zeroed.
272 // Then, start moving the claw towards the center until both halves are
273 // zeroed.
274 // Then, done!
275
276 // We'll then need code to do sanity checking on values.
277
278 // Now, we need to figure out which way to go.
279
280 switch (state_) {
281 case UNINITIALIZED:
282 LOG(DEBUG, "Uninitialized\n");
283 // Startup. Assume that we are at the origin everywhere.
284 // This records the encoder offset between the two sides of the elevator.
285 left_elevator_offset_ = -position->elevator.left.encoder;
286 right_elevator_offset_ = -position->elevator.right.encoder;
287 left_arm_offset_ = -position->arm.left.encoder;
288 right_arm_offset_ = -position->arm.right.encoder;
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800289 LOG(INFO, "Initializing arm offsets to %f, %f\n", left_arm_offset_,
290 right_arm_offset_);
291 LOG(INFO, "Initializing elevator offsets to %f, %f\n",
292 left_elevator_offset_, right_elevator_offset_);
Austin Schuh703b8d42015-02-01 14:56:34 -0800293 Correct();
294 state_ = INITIALIZING;
295 disable = true;
296 break;
297
298 case INITIALIZING:
299 LOG(DEBUG, "Waiting for accurate initial position.\n");
300 disable = true;
301 // Update state_ to accurately represent the state of the zeroing
302 // estimators.
303 UpdateZeroingState();
304 if (state_ != INITIALIZING) {
305 // Set the goals to where we are now.
306 elevator_goal_ = elevator();
307 arm_goal_ = arm();
308 }
309 break;
310
311 case ZEROING_ELEVATOR:
312 LOG(DEBUG, "Zeroing elevator\n");
Austin Schuh703b8d42015-02-01 14:56:34 -0800313
314 // Update state_ to accurately represent the state of the zeroing
315 // estimators.
316 UpdateZeroingState();
317 if (left_elevator_estimator_.zeroed() &&
318 right_elevator_estimator_.zeroed()) {
319 SetElevatorOffset(left_elevator_estimator_.offset(),
320 right_elevator_estimator_.offset());
321 LOG(DEBUG, "Zeroed the elevator!\n");
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800322 } else if (!disable) {
323 elevator_goal_velocity = elevator_zeroing_velocity();
324 elevator_goal_ += elevator_goal_velocity * dt;
Austin Schuh703b8d42015-02-01 14:56:34 -0800325 }
326 break;
327
328 case ZEROING_ARM:
329 LOG(DEBUG, "Zeroing the arm\n");
Austin Schuh703b8d42015-02-01 14:56:34 -0800330
331 // Update state_ to accurately represent the state of the zeroing
332 // estimators.
333 UpdateZeroingState();
334 if (left_arm_estimator_.zeroed() && right_arm_estimator_.zeroed()) {
335 SetArmOffset(left_arm_estimator_.offset(),
336 right_arm_estimator_.offset());
337 LOG(DEBUG, "Zeroed the arm!\n");
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800338 } else if (!disable) {
339 arm_goal_velocity = arm_zeroing_velocity();
340 arm_goal_ += arm_goal_velocity * dt;
Austin Schuh703b8d42015-02-01 14:56:34 -0800341 }
342 break;
343
344 case RUNNING:
345 LOG(DEBUG, "Running!\n");
346 arm_goal_velocity = goal->angular_velocity;
347 elevator_goal_velocity = goal->velocity;
348
349 // Update state_ to accurately represent the state of the zeroing
350 // estimators.
351 UpdateZeroingState();
352 arm_goal_ = goal->angle;
353 elevator_goal_ = goal->height;
354
355 if (state_ != RUNNING && state_ != ESTOP) {
356 state_ = UNINITIALIZED;
357 }
358 break;
359
360 case ESTOP:
361 LOG(ERROR, "Estop\n");
362 disable = true;
363 break;
364 }
365
366 // Commence death if either left/right tracking error gets too big. This
367 // should run immediately after the SetArmOffset and SetElevatorOffset
368 // functions to double-check that the hardware is in a sane state.
369 if (::std::abs(left_arm() - right_arm()) >=
Austin Schuhdbd6bfa2015-02-14 21:25:16 -0800370 values.max_allowed_left_right_arm_difference) {
371 LOG(ERROR, "The arms are too far apart. |%f - %f| > %f\n", left_arm(),
372 right_arm(), values.max_allowed_left_right_arm_difference);
373
374 // Indicate an ESTOP condition and stop the motors.
375 state_ = ESTOP;
376 disable = true;
377 }
378
379 if (::std::abs(left_elevator() - right_elevator()) >=
380 values.max_allowed_left_right_elevator_difference) {
381 LOG(ERROR, "The elevators are too far apart. |%f - %f| > %f\n",
382 left_elevator(), right_elevator(),
383 values.max_allowed_left_right_elevator_difference);
Austin Schuh703b8d42015-02-01 14:56:34 -0800384
385 // Indicate an ESTOP condition and stop the motors.
386 state_ = ESTOP;
387 disable = true;
388 }
389
390 // Limit the goals so we can't exceed the hardware limits if we are RUNNING.
391 if (state_ == RUNNING) {
392 // Limit the arm goal to min/max allowable angles.
393 if (arm_goal_ >= values.fridge.arm.upper_limit) {
394 LOG(WARNING, "Arm goal above limit, %f > %f\n", arm_goal_,
395 values.fridge.arm.upper_limit);
396 arm_goal_ = values.fridge.arm.upper_limit;
397 }
398 if (arm_goal_ <= values.fridge.arm.lower_limit) {
399 LOG(WARNING, "Arm goal below limit, %f < %f\n", arm_goal_,
400 values.fridge.arm.lower_limit);
401 arm_goal_ = values.fridge.arm.lower_limit;
402 }
403
404 // Limit the elevator goal to min/max allowable heights.
405 if (elevator_goal_ >= values.fridge.elevator.upper_limit) {
406 LOG(WARNING, "Elevator goal above limit, %f > %f\n", elevator_goal_,
407 values.fridge.elevator.upper_limit);
408 elevator_goal_ = values.fridge.elevator.upper_limit;
409 }
410 if (elevator_goal_ <= values.fridge.elevator.lower_limit) {
411 LOG(WARNING, "Elevator goal below limit, %f < %f\n", elevator_goal_,
412 values.fridge.elevator.lower_limit);
413 elevator_goal_ = values.fridge.elevator.lower_limit;
414 }
415 }
416
417 // Check the lower level hardware limit as well.
418 if (state_ == RUNNING) {
419 if (left_arm() >= values.fridge.arm.upper_hard_limit ||
420 left_arm() <= values.fridge.arm.lower_hard_limit) {
421 LOG(ERROR, "Left arm at %f out of bounds [%f, %f], ESTOPing\n",
422 left_arm(), values.fridge.arm.lower_hard_limit,
423 values.fridge.arm.upper_hard_limit);
424 state_ = ESTOP;
425 }
426
427 if (right_arm() >= values.fridge.arm.upper_hard_limit ||
428 right_arm() <= values.fridge.arm.lower_hard_limit) {
429 LOG(ERROR, "Right arm at %f out of bounds [%f, %f], ESTOPing\n",
430 right_arm(), values.fridge.arm.lower_hard_limit,
431 values.fridge.arm.upper_hard_limit);
432 state_ = ESTOP;
433 }
434
435 if (left_elevator() >= values.fridge.elevator.upper_hard_limit ||
436 left_elevator() <= values.fridge.elevator.lower_hard_limit) {
437 LOG(ERROR, "Left elevator at %f out of bounds [%f, %f], ESTOPing\n",
438 left_elevator(), values.fridge.elevator.lower_hard_limit,
439 values.fridge.elevator.upper_hard_limit);
440 state_ = ESTOP;
441 }
442
443 if (right_elevator() >= values.fridge.elevator.upper_hard_limit ||
444 right_elevator() <= values.fridge.elevator.lower_hard_limit) {
445 LOG(ERROR, "Right elevator at %f out of bounds [%f, %f], ESTOPing\n",
446 right_elevator(), values.fridge.elevator.lower_hard_limit,
447 values.fridge.elevator.upper_hard_limit);
448 state_ = ESTOP;
449 }
450 }
451
452 // Set the goals.
453 arm_loop_->mutable_R() << arm_goal_, arm_goal_velocity, 0.0, 0.0;
454 elevator_loop_->mutable_R() << elevator_goal_, elevator_goal_velocity, 0.0,
455 0.0;
456
457 const double max_voltage = state_ == RUNNING ? 12.0 : kZeroingVoltage;
458 arm_loop_->set_max_voltage(max_voltage);
459 elevator_loop_->set_max_voltage(max_voltage);
460
461 if (state_ == ESTOP) {
462 disable = true;
463 }
464 arm_loop_->Update(disable);
465 elevator_loop_->Update(disable);
466
467 if (state_ == INITIALIZING || state_ == ZEROING_ELEVATOR ||
468 state_ == ZEROING_ARM) {
469 if (arm_loop_->U() != arm_loop_->U_uncapped()) {
470 Eigen::Matrix<double, 2, 1> deltaR =
471 arm_loop_->UnsaturateOutputGoalChange();
472
473 // Move the average arm goal by the amount observed.
474 LOG(WARNING, "Moving arm goal by %f to handle saturation\n",
475 deltaR(0, 0));
476 arm_goal_ += deltaR(0, 0);
477 }
478
479 if (elevator_loop_->U() != elevator_loop_->U_uncapped()) {
480 Eigen::Matrix<double, 2, 1> deltaR =
481 elevator_loop_->UnsaturateOutputGoalChange();
482
483 // Move the average elevator goal by the amount observed.
484 LOG(WARNING, "Moving elevator goal by %f to handle saturation\n",
485 deltaR(0, 0));
486 elevator_goal_ += deltaR(0, 0);
487 }
488 }
489
490 if (output) {
491 output->left_arm = arm_loop_->U(0, 0);
492 output->right_arm = arm_loop_->U(1, 0);
493 output->left_elevator = elevator_loop_->U(0, 0);
494 output->right_elevator = elevator_loop_->U(1, 0);
495 output->grabbers = goal->grabbers;
496 }
497
498 // TODO(austin): Populate these fully.
499 status->zeroed = false;
500 status->done = false;
501 status->angle = arm_loop_->X_hat(0, 0);
502 status->height = elevator_loop_->X_hat(0, 0);
503 status->grabbers = goal->grabbers;
504 status->estopped = (state_ == ESTOP);
505 last_state_ = state_;
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800506}
507
508} // namespace control_loops
509} // namespace frc971