blob: 2ae1996a7def7d72a5b777c8d8deb60827cfd94a [file] [log] [blame]
Austin Schuhcb091712018-02-21 20:01:55 -08001#include "y2018/control_loops/superstructure/arm/arm.h"
2
3#include <chrono>
4#include <iostream>
5
John Park33858a32018-09-28 23:05:48 -07006#include "aos/logging/logging.h"
7#include "aos/logging/queue_logging.h"
Austin Schuhcb091712018-02-21 20:01:55 -08008#include "y2018/constants.h"
9#include "y2018/control_loops/superstructure/arm/demo_path.h"
10#include "y2018/control_loops/superstructure/arm/dynamics.h"
Austin Schuh7dfccf62018-03-03 21:28:14 -080011#include "y2018/control_loops/superstructure/arm/generated_graph.h"
Austin Schuhcb091712018-02-21 20:01:55 -080012
13namespace y2018 {
14namespace control_loops {
15namespace superstructure {
16namespace arm {
17
Austin Schuh7afcc232018-09-16 16:33:47 -070018namespace {
19
Austin Schuhcb091712018-02-21 20:01:55 -080020namespace chrono = ::std::chrono;
21using ::aos::monotonic_clock;
22
Austin Schuh7afcc232018-09-16 16:33:47 -070023constexpr int kMaxBrownoutCount = 4;
24
25} // namespace
26
Austin Schuhcb091712018-02-21 20:01:55 -080027Arm::Arm()
28 : proximal_zeroing_estimator_(constants::GetValues().arm_proximal.zeroing),
29 distal_zeroing_estimator_(constants::GetValues().arm_distal.zeroing),
30 alpha_unitizer_((::Eigen::Matrix<double, 2, 2>() << 1.0 / kAlpha0Max(),
31 0.0, 0.0, 1.0 / kAlpha1Max())
32 .finished()),
Austin Schuh7dfccf62018-03-03 21:28:14 -080033 search_graph_(MakeSearchGraph(&trajectories_, alpha_unitizer_, kVMax())),
Austin Schuhcb091712018-02-21 20:01:55 -080034 // Go to the start of the first trajectory.
Austin Schuhb874fd32018-03-05 00:27:10 -080035 follower_(ReadyAboveBoxPoint()),
36 points_(PointList()) {
Austin Schuh7dfccf62018-03-03 21:28:14 -080037 int i = 0;
38 for (const auto &trajectory : trajectories_) {
Austin Schuhf257f3c2019-10-27 21:00:43 -070039 AOS_LOG(INFO, "trajectory length for edge node %d: %f\n", i,
40 trajectory.trajectory.path().length());
Austin Schuh7dfccf62018-03-03 21:28:14 -080041 ++i;
Austin Schuhcb091712018-02-21 20:01:55 -080042 }
43}
44
45void Arm::Reset() { state_ = State::UNINITIALIZED; }
46
Austin Schuh20177c92019-07-07 20:48:24 -070047void Arm::Iterate(const ::aos::monotonic_clock::time_point monotonic_now,
48 const uint32_t *unsafe_goal, bool grab_box, bool open_claw,
Neil Balchba9cbba2018-04-06 22:26:38 -070049 bool close_claw, const control_loops::ArmPosition *position,
Austin Schuh96341532018-03-09 21:17:24 -080050 const bool claw_beambreak_triggered,
51 const bool box_back_beambreak_triggered,
Austin Schuhd76546a2018-07-08 16:05:14 -070052 const bool intake_clear_of_box, bool suicide,
53 bool trajectory_override, double *proximal_output,
Austin Schuh96341532018-03-09 21:17:24 -080054 double *distal_output, bool *release_arm_brake,
Austin Schuhd76546a2018-07-08 16:05:14 -070055 bool *claw_closed, control_loops::ArmStatus *status) {
Austin Schuhcb091712018-02-21 20:01:55 -080056 ::Eigen::Matrix<double, 2, 1> Y;
Austin Schuh96341532018-03-09 21:17:24 -080057 const bool outputs_disabled =
58 ((proximal_output == nullptr) || (distal_output == nullptr) ||
59 (release_arm_brake == nullptr) || (claw_closed == nullptr));
Austin Schuh7afcc232018-09-16 16:33:47 -070060 if (outputs_disabled) {
61 ++brownout_count_;
62 } else {
63 brownout_count_ = 0;
64 }
Austin Schuh96341532018-03-09 21:17:24 -080065
66 uint32_t filtered_goal = 0;
67 if (unsafe_goal != nullptr) {
68 filtered_goal = *unsafe_goal;
69 }
70
71 if (open_claw) {
72 claw_closed_ = false;
73 }
Neil Balchba9cbba2018-04-06 22:26:38 -070074 if (close_claw) {
Austin Schuh96341532018-03-09 21:17:24 -080075 claw_closed_ = true;
76 }
Neil Balchba9cbba2018-04-06 22:26:38 -070077 if (outputs_disabled) {
78 if (claw_closed_count_ == 0) {
79 claw_closed_ = true;
80 } else {
81 --claw_closed_count_;
82 }
83 } else {
84 // Wait this many iterations before closing the claw. That prevents
85 // brownouts from closing the claw.
86 claw_closed_count_ = 50;
87 }
Austin Schuhcb091712018-02-21 20:01:55 -080088
89 Y << position->proximal.encoder + proximal_offset_,
90 position->distal.encoder + distal_offset_;
91
92 proximal_zeroing_estimator_.UpdateEstimate(position->proximal);
93 distal_zeroing_estimator_.UpdateEstimate(position->distal);
94
95 if (proximal_output != nullptr) {
96 *proximal_output = 0.0;
97 }
98 if (distal_output != nullptr) {
99 *distal_output = 0.0;
100 }
101
102 arm_ekf_.Correct(Y, kDt());
103
104 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) <= 0.05 &&
105 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) <= 0.05) {
106 close_enough_for_full_power_ = true;
107 }
108 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) >= 1.10 ||
109 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) >= 1.10) {
110 close_enough_for_full_power_ = false;
111 }
112
113 switch (state_) {
114 case State::UNINITIALIZED:
115 // Wait in the uninitialized state until the intake is initialized.
Austin Schuhf257f3c2019-10-27 21:00:43 -0700116 AOS_LOG(DEBUG, "Uninitialized, waiting for intake\n");
Austin Schuhcb091712018-02-21 20:01:55 -0800117 state_ = State::ZEROING;
118 proximal_zeroing_estimator_.Reset();
119 distal_zeroing_estimator_.Reset();
Austin Schuhcb091712018-02-21 20:01:55 -0800120 break;
121
122 case State::ZEROING:
123 // Zero by not moving.
124 if (proximal_zeroing_estimator_.zeroed() &&
125 distal_zeroing_estimator_.zeroed()) {
Austin Schuh96341532018-03-09 21:17:24 -0800126 state_ = State::DISABLED;
Austin Schuhcb091712018-02-21 20:01:55 -0800127
128 proximal_offset_ = proximal_zeroing_estimator_.offset();
129 distal_offset_ = distal_zeroing_estimator_.offset();
130
131 Y << position->proximal.encoder + proximal_offset_,
132 position->distal.encoder + distal_offset_;
133
134 // TODO(austin): Offset ekf rather than reset it. Since we aren't
135 // moving at this point, it's pretty safe to do this.
136 ::Eigen::Matrix<double, 4, 1> X;
137 X << Y(0), 0.0, Y(1), 0.0;
138 arm_ekf_.Reset(X);
139 } else {
140 break;
141 }
142
Austin Schuhb874fd32018-03-05 00:27:10 -0800143 case State::DISABLED: {
144 follower_.SwitchTrajectory(nullptr);
145 close_enough_for_full_power_ = false;
Austin Schuh96341532018-03-09 21:17:24 -0800146
Austin Schuhb874fd32018-03-05 00:27:10 -0800147 const ::Eigen::Matrix<double, 2, 1> current_theta =
148 (::Eigen::Matrix<double, 2, 1>() << arm_ekf_.X_hat(0),
149 arm_ekf_.X_hat(2))
150 .finished();
151 uint32_t best_index = 0;
152 double best_distance = (points_[0] - current_theta).norm();
153 uint32_t current_index = 0;
154 for (const ::Eigen::Matrix<double, 2, 1> &point : points_) {
155 const double new_distance = (point - current_theta).norm();
156 if (new_distance < best_distance) {
157 best_distance = new_distance;
158 best_index = current_index;
159 }
160 ++current_index;
161 }
162 follower_.set_theta(points_[best_index]);
163 current_node_ = best_index;
164
165 if (!outputs_disabled) {
Austin Schuh96341532018-03-09 21:17:24 -0800166 state_ = State::GOTO_PATH;
167 } else {
168 break;
169 }
Austin Schuhb874fd32018-03-05 00:27:10 -0800170 }
Austin Schuh96341532018-03-09 21:17:24 -0800171
172 case State::GOTO_PATH:
173 if (outputs_disabled) {
174 state_ = State::DISABLED;
Austin Schuhd76546a2018-07-08 16:05:14 -0700175 } else if (trajectory_override) {
176 follower_.SwitchTrajectory(nullptr);
177 current_node_ = filtered_goal;
178 follower_.set_theta(points_[current_node_]);
179 state_ = State::GOTO_PATH;
Austin Schuh96341532018-03-09 21:17:24 -0800180 } else if (close_enough_for_full_power_) {
181 state_ = State::RUNNING;
182 grab_state_ = GrabState::NORMAL;
183 }
184 break;
185
Austin Schuhcb091712018-02-21 20:01:55 -0800186 case State::RUNNING:
187 // ESTOP if we hit the hard limits.
188 // TODO(austin): Pick some sane limits.
189 if (proximal_zeroing_estimator_.error() ||
190 distal_zeroing_estimator_.error()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700191 AOS_LOG(ERROR, "Zeroing error ESTOP\n");
Austin Schuhcb091712018-02-21 20:01:55 -0800192 state_ = State::ESTOP;
Austin Schuh7afcc232018-09-16 16:33:47 -0700193 } else if (outputs_disabled && brownout_count_ > kMaxBrownoutCount) {
Austin Schuh96341532018-03-09 21:17:24 -0800194 state_ = State::DISABLED;
Austin Schuhd76546a2018-07-08 16:05:14 -0700195 } else if (trajectory_override) {
196 follower_.SwitchTrajectory(nullptr);
197 current_node_ = filtered_goal;
198 follower_.set_theta(points_[current_node_]);
199 state_ = State::GOTO_PATH;
Austin Schuh17e484e2018-03-11 01:11:36 -0800200 } else if (suicide) {
201 state_ = State::PREP_CLIMB;
202 climb_count_ = 50;
203 }
204 break;
205
206 case State::PREP_CLIMB:
207 --climb_count_;
208 if (climb_count_ <= 0) {
209 state_ = State::ESTOP;
210 } else if (!suicide) {
211 state_ = State::RUNNING;
Austin Schuhcb091712018-02-21 20:01:55 -0800212 }
213 break;
214
215 case State::ESTOP:
Austin Schuhf257f3c2019-10-27 21:00:43 -0700216 AOS_LOG(ERROR, "Estop\n");
Austin Schuhcb091712018-02-21 20:01:55 -0800217 break;
218 }
219
Austin Schuh17e484e2018-03-11 01:11:36 -0800220 const bool disable = outputs_disabled || (state_ != State::RUNNING &&
221 state_ != State::GOTO_PATH &&
222 state_ != State::PREP_CLIMB);
Austin Schuhcb091712018-02-21 20:01:55 -0800223 if (disable) {
224 close_enough_for_full_power_ = false;
225 }
226
Austin Schuhb874fd32018-03-05 00:27:10 -0800227 // TODO(austin): Do we need to debounce box_back_beambreak_triggered ?
Austin Schuh96341532018-03-09 21:17:24 -0800228 if (claw_closed_) {
229 if ((filtered_goal == ReadyAboveBoxIndex()) ||
230 (filtered_goal == TallBoxGrabIndex()) ||
231 (filtered_goal == ShortBoxGrabIndex())) {
232 filtered_goal = NeutralIndex();
233 }
234 }
235
236 // TODO(austin): Do we need to debounce box_back_beambreak_triggered ?
237 switch (grab_state_) {
238 case GrabState::NORMAL:
239 if (grab_box && !claw_closed_) {
240 grab_state_ = GrabState::WAIT_FOR_BOX;
241 } else {
242 break;
243 }
244 case GrabState::WAIT_FOR_BOX:
245 if (!grab_box) {
246 grab_state_ = GrabState::NORMAL;
247 } else {
248 if (AtState(ReadyAboveBoxIndex()) && NearEnd()) {
249 // We are being asked to grab the box, and the claw is near the box.
250 if (box_back_beambreak_triggered) {
251 // And we now see the box! Try for a tall box.
252 grab_state_ = GrabState::TALL_BOX;
253 }
254 }
255 }
256 break;
257 case GrabState::TALL_BOX:
258 if (!grab_box) {
259 grab_state_ = GrabState::NORMAL;
Austin Schuh96341532018-03-09 21:17:24 -0800260 } else if (AtState(TallBoxGrabIndex()) && NearEnd()) {
261 // We are being asked to grab the box, and the claw is near the box.
262 if (claw_beambreak_triggered) {
263 grab_state_ = GrabState::CLAW_CLOSE;
264 // Snap time for the delay here.
Austin Schuh20177c92019-07-07 20:48:24 -0700265 claw_close_start_time_ = monotonic_now;
Austin Schuh96341532018-03-09 21:17:24 -0800266 } else {
267 grab_state_ = GrabState::SHORT_BOX;
268 }
269 }
270 break;
271 case GrabState::SHORT_BOX:
272 if (!grab_box) {
273 grab_state_ = GrabState::NORMAL;
Austin Schuh96341532018-03-09 21:17:24 -0800274 } else if (AtState(ShortBoxGrabIndex()) && NearEnd()) {
275 // We are being asked to grab the box, and the claw is near the box.
276 if (claw_beambreak_triggered) {
277 grab_state_ = GrabState::CLAW_CLOSE;
278 // Snap time for the delay here.
Austin Schuh20177c92019-07-07 20:48:24 -0700279 claw_close_start_time_ = monotonic_now;
Austin Schuh96341532018-03-09 21:17:24 -0800280 } else {
281 grab_state_ = GrabState::WAIT_FOR_BOX;
282 }
283 }
284 break;
285 case GrabState::CLAW_CLOSE:
Austin Schuh20177c92019-07-07 20:48:24 -0700286 if (monotonic_now >
Austin Schuhb874fd32018-03-05 00:27:10 -0800287 claw_close_start_time_ + ::std::chrono::milliseconds(300)) {
Austin Schuh96341532018-03-09 21:17:24 -0800288 grab_state_ = GrabState::OPEN_INTAKE;
289 }
290 break;
291 case GrabState::OPEN_INTAKE:
292 if (intake_clear_of_box) {
293 grab_state_ = GrabState::NORMAL;
294 }
295 break;
296 }
297
298 // Now, based out our current state, go to the right state.
299 switch (grab_state_) {
300 case GrabState::NORMAL:
301 // Don't let the intake close fully with the claw closed.
302 // TODO(austin): If we want to transfer the box from the claw to the
303 // intake, we'll need to change this.
304 if (claw_closed_) {
305 max_intake_override_ = -0.5;
306 } else {
307 max_intake_override_ = 1000.0;
308 }
309 break;
310 case GrabState::WAIT_FOR_BOX:
311 filtered_goal = ReadyAboveBoxIndex();
312 claw_closed_ = false;
313 max_intake_override_ = 1000.0;
314 break;
315 case GrabState::TALL_BOX:
316 filtered_goal = TallBoxGrabIndex();
317 claw_closed_ = false;
318 max_intake_override_ = 1000.0;
319 break;
320 case GrabState::SHORT_BOX:
321 filtered_goal = ShortBoxGrabIndex();
322 claw_closed_ = false;
323 max_intake_override_ = 1000.0;
324 break;
325 case GrabState::CLAW_CLOSE:
326 // Don't move.
327 filtered_goal = current_node_;
328 claw_closed_ = true;
329 max_intake_override_ = 1000.0;
330 break;
331 case GrabState::OPEN_INTAKE:
332 // Don't move.
333 filtered_goal = current_node_;
334 claw_closed_ = true;
335 max_intake_override_ = -0.5;
336 break;
337 }
338
339 if (state_ == State::RUNNING && unsafe_goal != nullptr) {
340 if (current_node_ != filtered_goal) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700341 AOS_LOG(INFO, "Goal is different\n");
Austin Schuh96341532018-03-09 21:17:24 -0800342 if (filtered_goal >= search_graph_.num_vertexes()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700343 AOS_LOG(ERROR, "goal node out of range ESTOP\n");
Austin Schuh96341532018-03-09 21:17:24 -0800344 state_ = State::ESTOP;
345 } else if (follower_.path_distance_to_go() > 1e-3) {
346 // Still on the old path segment. Can't change yet.
347 } else {
348 search_graph_.SetGoal(filtered_goal);
349
350 size_t min_edge = 0;
351 double min_cost = ::std::numeric_limits<double>::infinity();
352 for (const SearchGraph::HalfEdge &edge :
353 search_graph_.Neighbors(current_node_)) {
354 const double cost = search_graph_.GetCostToGoal(edge.dest);
355 if (cost < min_cost) {
356 min_edge = edge.edge_id;
357 min_cost = cost;
358 }
359 }
360 // Ok, now we know which edge we are on. Figure out the path and
361 // trajectory.
362 const SearchGraph::Edge &next_edge = search_graph_.edges()[min_edge];
Austin Schuhf257f3c2019-10-27 21:00:43 -0700363 AOS_LOG(INFO, "Switching from node %d to %d along edge %d\n",
364 static_cast<int>(current_node_),
365 static_cast<int>(next_edge.end), static_cast<int>(min_edge));
Austin Schuh41c71e42018-04-04 20:11:20 -0700366 vmax_ = trajectories_[min_edge].vmax;
367 follower_.SwitchTrajectory(&trajectories_[min_edge].trajectory);
Austin Schuh96341532018-03-09 21:17:24 -0800368 current_node_ = next_edge.end;
369 }
370 }
371 }
372
Austin Schuh345a3732018-03-21 20:49:32 -0700373 const double max_operating_voltage =
374 close_enough_for_full_power_
375 ? kOperatingVoltage()
376 : (state_ == State::GOTO_PATH ? kGotoPathVMax() : kPathlessVMax());
Austin Schuh41c71e42018-04-04 20:11:20 -0700377 follower_.Update(arm_ekf_.X_hat(), disable, kDt(), vmax_,
Austin Schuh345a3732018-03-21 20:49:32 -0700378 max_operating_voltage);
Austin Schuhf257f3c2019-10-27 21:00:43 -0700379 AOS_LOG(INFO, "Max voltage: %f\n", max_operating_voltage);
Austin Schuhcb091712018-02-21 20:01:55 -0800380 status->goal_theta0 = follower_.theta(0);
381 status->goal_theta1 = follower_.theta(1);
382 status->goal_omega0 = follower_.omega(0);
383 status->goal_omega1 = follower_.omega(1);
384
385 status->theta0 = arm_ekf_.X_hat(0);
386 status->theta1 = arm_ekf_.X_hat(2);
387 status->omega0 = arm_ekf_.X_hat(1);
388 status->omega1 = arm_ekf_.X_hat(3);
389 status->voltage_error0 = arm_ekf_.X_hat(4);
390 status->voltage_error1 = arm_ekf_.X_hat(5);
391
392 if (!disable) {
393 *proximal_output = ::std::max(
394 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(0)));
395 *distal_output = ::std::max(
396 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(1)));
Austin Schuh17e484e2018-03-11 01:11:36 -0800397 if (state_ != State::PREP_CLIMB) {
398 *release_arm_brake = true;
399 } else {
400 *release_arm_brake = false;
401 }
Austin Schuh96341532018-03-09 21:17:24 -0800402 *claw_closed = claw_closed_;
Austin Schuhcb091712018-02-21 20:01:55 -0800403 }
404
405 status->proximal_estimator_state =
406 proximal_zeroing_estimator_.GetEstimatorState();
407 status->distal_estimator_state =
408 distal_zeroing_estimator_.GetEstimatorState();
409
410 status->path_distance_to_go = follower_.path_distance_to_go();
411 status->current_node = current_node_;
412
413 status->zeroed = (proximal_zeroing_estimator_.zeroed() &&
414 distal_zeroing_estimator_.zeroed());
415 status->estopped = (state_ == State::ESTOP);
416 status->state = static_cast<int32_t>(state_);
Austin Schuh96341532018-03-09 21:17:24 -0800417 status->grab_state = static_cast<int32_t>(grab_state_);
Austin Schuhcb091712018-02-21 20:01:55 -0800418 status->failed_solutions = follower_.failed_solutions();
419
420 arm_ekf_.Predict(follower_.U(), kDt());
421}
422
423} // namespace arm
424} // namespace superstructure
425} // namespace control_loops
426} // namespace y2018